diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt index a3caf1e9..784b0d37 100644 --- a/AutomaticUpdateVersionControl.txt +++ b/AutomaticUpdateVersionControl.txt @@ -1 +1 @@ -1.7.11.0 +1.7.12.0 diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index 2e4f8e12..396a012e 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -1,5 +1,6 @@ using Hardcodet.Wpf.TaskbarNotification; using Ink_Canvas.Helpers; +using Ink_Canvas.Windows; using iNKORE.UI.WPF.Modern.Controls; using Microsoft.Win32; using Newtonsoft.Json; @@ -14,12 +15,14 @@ using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Threading; using Application = System.Windows.Application; using MessageBox = System.Windows.MessageBox; +using SplashScreen = Ink_Canvas.Windows.SplashScreen; using Timer = System.Threading.Timer; namespace Ink_Canvas @@ -36,6 +39,8 @@ namespace Ink_Canvas // 新增:标记是否通过--board参数启动 public static bool StartWithBoardMode = false; + // 新增:标记是否通过--show参数启动 + public static bool StartWithShowMode = false; // 新增:保存看门狗进程对象 private static Process watchdogProcess; // 新增:标记是否为软件内主动退出 @@ -52,6 +57,9 @@ namespace Ink_Canvas private static string lastErrorMessage = string.Empty; // 新增:是否已初始化崩溃监听器 private static bool crashListenersInitialized; + // 新增:启动画面相关 + private static SplashScreen _splashScreen; + private static bool _isSplashScreenShown = false; public App() { @@ -74,7 +82,7 @@ namespace Ink_Canvas DispatcherUnhandledException += App_DispatcherUnhandledException; StartHeartbeatMonitor(); - // 新增:初始化全局异常和进程结束处理 + // 初始化全局异常和进程结束处理 InitializeCrashListeners(); // 仅在崩溃后操作为静默重启时才启动看门狗 @@ -90,7 +98,7 @@ namespace Ink_Canvas Exit += App_Exit; // 注册退出事件 } - // 新增:配置TLS协议以支持Windows 7 + // 配置TLS协议以支持Windows 7 private void ConfigureTlsForWindows7() { try @@ -125,7 +133,7 @@ namespace Ink_Canvas } } - // 新增:初始化崩溃监听器 + // 初始化崩溃监听器 private void InitializeCrashListeners() { if (crashListenersInitialized) return; @@ -173,7 +181,7 @@ namespace Ink_Canvas } } - // 新增:动态加载WMI监控(避免直接引用System.Management) + // 动态加载WMI监控 private void TrySetupWmiMonitoring() { try @@ -226,7 +234,7 @@ namespace Ink_Canvas } } - // WMI事件处理方法(通过反射调用) + // WMI事件处理方法 private void WmiEventHandler(object sender, EventArgs e) { try @@ -250,7 +258,7 @@ namespace Ink_Canvas } } - // 新增:Windows控制台控制处理程序 + // Windows控制台控制处理程序 [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add); @@ -289,43 +297,52 @@ namespace Ink_Canvas return false; } - // 新增:系统会话结束事件处理 + // 系统会话结束事件处理 private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) { string reason = e.Reason == SessionEndReasons.Logoff ? "用户注销" : "系统关机"; WriteCrashLog($"系统会话即将结束: {reason}"); - // 清理PowerPoint进程守护 + // 清理PowerPoint进程守护和悬浮窗拦截器 try { - // 获取主窗口实例并清理PowerPoint进程守护 + // 获取主窗口实例 var mainWindow = Current.MainWindow as MainWindow; if (mainWindow != null) { - // 通过反射调用StopPowerPointProcessMonitoring方法 + // 清理PowerPoint进程守护 var method = mainWindow.GetType().GetMethod("StopPowerPointProcessMonitoring", BindingFlags.NonPublic | BindingFlags.Instance); method?.Invoke(mainWindow, null); - WriteCrashLog("PowerPoint进程守护已在系统关机时清理"); + + // 清理悬浮窗拦截器 + var interceptorField = mainWindow.GetType().GetField("_floatingWindowInterceptorManager", + BindingFlags.NonPublic | BindingFlags.Instance); + var interceptorManager = interceptorField?.GetValue(mainWindow); + if (interceptorManager != null) + { + var disposeMethod = interceptorManager.GetType().GetMethod("Dispose"); + disposeMethod?.Invoke(interceptorManager, null); + } } } catch (Exception ex) { - WriteCrashLog($"清理PowerPoint进程守护失败: {ex.Message}"); + WriteCrashLog($"清理资源失败: {ex.Message}"); } DeviceIdentifier.SaveUsageStatsOnShutdown(); } - // 新增:控制台取消事件处理 + // 控制台取消事件处理 private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) { WriteCrashLog($"接收到控制台中断信号: {e.SpecialKey}"); e.Cancel = true; // 取消默认处理 } - // 新增:处理非UI线程的未处理异常 + // 处理非UI线程的未处理异常 private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { try @@ -355,7 +372,7 @@ namespace Ink_Canvas } } - // 新增:处理进程退出事件 + // 处理进程退出事件 private void CurrentDomain_ProcessExit(object sender, EventArgs e) { TimeSpan runDuration = DateTime.Now - appStartTime; @@ -369,7 +386,7 @@ namespace Ink_Canvas } } - // 新增:格式化时间跨度 + // 格式化时间跨度 private static string FormatTimeSpan(TimeSpan timeSpan) { if (timeSpan.TotalDays >= 1) @@ -390,7 +407,92 @@ namespace Ink_Canvas return $"{timeSpan.Seconds}秒"; } - // 新增:记录崩溃日志 + public static void ShowSplashScreen() + { + if (_isSplashScreenShown) + { + LogHelper.WriteLogToFile("启动画面已经显示,跳过重复显示"); + return; + } + + try + { + LogHelper.WriteLogToFile("开始创建启动画面..."); + _splashScreen = new SplashScreen(); + LogHelper.WriteLogToFile("启动画面对象创建成功,准备显示..."); + _splashScreen.Show(); + _isSplashScreenShown = true; + LogHelper.WriteLogToFile("启动画面已显示"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"显示启动画面失败: {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"异常堆栈: {ex.StackTrace}", LogHelper.LogType.Error); + } + } + + // 关闭启动画面 + public static void CloseSplashScreen() + { + if (!_isSplashScreenShown || _splashScreen == null) return; + + try + { + _splashScreen.CloseSplashScreen(); + _isSplashScreenShown = false; + LogHelper.WriteLogToFile("启动画面已关闭"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"关闭启动画面失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + // 设置启动画面进度 + public static void SetSplashProgress(int progress) + { + if (_splashScreen != null) + { + _splashScreen.SetProgress(progress); + } + } + + // 设置启动画面消息 + public static void SetSplashMessage(string message) + { + if (_splashScreen != null) + { + _splashScreen.SetLoadingMessage(message); + } + } + + private static bool ShouldShowSplashScreen() + { + try + { + // 检查设置文件中的启动动画开关 + var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json"); + if (File.Exists(settingsPath)) + { + var json = File.ReadAllText(settingsPath); + dynamic obj = JsonConvert.DeserializeObject(json); + if (obj?["appearance"]?["enableSplashScreen"] != null) + { + return (bool)obj["appearance"]["enableSplashScreen"]; + } + } + + // 如果设置文件不存在或没有该设置,返回默认值false + return false; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"检查启动动画设置失败: {ex.Message}", LogHelper.LogType.Warning); + return false; + } + } + + // 记录崩溃日志 private static void WriteCrashLog(string message) { try @@ -425,7 +527,6 @@ namespace Ink_Canvas // 增加字段保存崩溃后操作设置 public static CrashActionType CrashAction = CrashActionType.SilentRestart; - // 修正:允许静态调用 public static void SyncCrashActionFromSettings() { try @@ -440,7 +541,7 @@ namespace Ink_Canvas try { crashAction = (int)(obj["startup"]["crashAction"] ?? 0); } catch { } CrashAction = (CrashActionType)crashAction; } - // 兜底:从主窗口同步 + // 从主窗口同步 else if (Ink_Canvas.MainWindow.Settings != null && Ink_Canvas.MainWindow.Settings.Startup != null) { CrashAction = (CrashActionType)Ink_Canvas.MainWindow.Settings.Startup.CrashAction; @@ -454,13 +555,13 @@ namespace Ink_Canvas Ink_Canvas.MainWindow.ShowNewMessage("抱歉,出现未预期的异常,可能导致 InkCanvasForClass 运行不稳定。\n建议保存墨迹后重启应用。"); LogHelper.NewLog(e.Exception.ToString()); - // 新增:记录到崩溃日志 + // 记录到崩溃日志 lastErrorMessage = e.Exception.ToString(); WriteCrashLog($"UI线程未处理异常: {e.Exception}"); e.Handled = true; - SyncCrashActionFromSettings(); // 新增:崩溃时同步最新设置 + SyncCrashActionFromSettings(); // 崩溃时同步最新设置 if (CrashAction == CrashActionType.SilentRestart && !IsAppExitByUser) { @@ -484,12 +585,24 @@ namespace Ink_Canvas private TaskbarIcon _taskbar; - void App_Startup(object sender, StartupEventArgs e) + async void App_Startup(object sender, StartupEventArgs e) { // 初始化应用启动时间 appStartTime = DateTime.Now; - /*if (!StoreHelper.IsStoreApp) */ + // 根据设置决定是否显示启动画面 + if (ShouldShowSplashScreen()) + { + ShowSplashScreen(); + SetSplashMessage("正在启动 Ink Canvas..."); + SetSplashProgress(20); + await Task.Delay(500); + + // 强制刷新UI,确保启动画面显示 + Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.Render); + } + + System.Threading.Thread.Sleep(500); RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version)); @@ -506,6 +619,14 @@ namespace Ink_Canvas LogHelper.WriteLogToFile("App | 检测到--board参数,将直接进入白板模式"); } + // 检查是否通过--show参数启动 + bool hasShowArg = e.Args.Contains("--show"); + if (hasShowArg) + { + StartWithShowMode = true; + LogHelper.WriteLogToFile("App | 检测到--show参数,将退出收纳模式并恢复浮动栏"); + } + // 记录最终应用启动状态 if (isFinalApp) { @@ -513,6 +634,12 @@ namespace Ink_Canvas } // 在应用启动时自动释放IACore相关DLL + if (_isSplashScreenShown) + { + SetSplashMessage("正在初始化组件..."); + SetSplashProgress(40); + await Task.Delay(500); + } try { IACoreDllExtractor.ExtractIACoreDlls(); @@ -523,6 +650,12 @@ namespace Ink_Canvas } // 记录应用启动(设备标识符) + if (_isSplashScreenShown) + { + SetSplashMessage("正在加载配置..."); + SetSplashProgress(60); + await Task.Delay(500); + } DeviceIdentifier.RecordAppLaunch(); LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}"); LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}"); @@ -696,6 +829,21 @@ namespace Ink_Canvas LogHelper.WriteLogToFile("通过IPC发送白板模式命令失败", LogHelper.LogType.Warning); } } + // 检查是否有--show参数 + else if (hasShowArg) + { + LogHelper.WriteLogToFile("检测到已运行实例且有--show参数,尝试通过IPC发送展开浮动栏命令", LogHelper.LogType.Event); + + // 尝试通过IPC发送展开浮动栏命令给已运行实例 + if (FileAssociationManager.TrySendShowModeCommandToExistingInstance()) + { + LogHelper.WriteLogToFile("展开浮动栏命令已通过IPC发送给已运行实例", LogHelper.LogType.Event); + } + else + { + LogHelper.WriteLogToFile("通过IPC发送展开浮动栏命令失败", LogHelper.LogType.Warning); + } + } else { LogHelper.WriteLogToFile("检测到已运行实例,但无文件参数", LogHelper.LogType.Event); @@ -750,11 +898,33 @@ namespace Ink_Canvas StartArgs = e.Args; // 在非更新模式下创建主窗口 + if (_isSplashScreenShown) + { + SetSplashMessage("正在初始化主界面..."); + SetSplashProgress(80); + await Task.Delay(500); + } var mainWindow = new MainWindow(); MainWindow = mainWindow; + + // 主窗口加载完成后关闭启动画面 + mainWindow.Loaded += (s, args) => + { + if (_isSplashScreenShown) + { + SetSplashMessage("启动完成!"); + SetSplashProgress(100); + // 延迟关闭启动画面,让用户看到完成消息 + Task.Delay(500).ContinueWith(_ => + { + Dispatcher.Invoke(() => CloseSplashScreen()); + }); + } + }; + mainWindow.Show(); - // 新增:注册.icstk文件关联 + // 注册.icstk文件关联 try { LogHelper.WriteLogToFile("开始注册.icstk文件关联"); @@ -766,7 +936,7 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error); } - // 新增:启动IPC监听器 + // 启动IPC监听器 try { LogHelper.WriteLogToFile("启动IPC监听器"); @@ -777,125 +947,6 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error); } - // 新增:Office注册表检测 - try - { - LogHelper.WriteLogToFile("开始Office注册表检测"); - - // 检查Office安装 - if (!IsOfficeInstalled()) - { - LogHelper.WriteLogToFile("未检测到Office安装", LogHelper.LogType.Warning); - return; - } - - // 尝试获取所有可能的Office版本路径 - var officeVersions = GetOfficeVersions(); - if (officeVersions.Count == 0) - { - LogHelper.WriteLogToFile("未找到任何Office版本", LogHelper.LogType.Warning); - return; - } - - foreach (var version in officeVersions) - { - string regPath = $"Software\\Microsoft\\Office\\{version}\\Common\\Security"; - LogHelper.WriteLogToFile($"正在处理Office版本 {version}, 注册表路径: {regPath}"); - - try - { - using (RegistryKey baseKey = Registry.CurrentUser.OpenSubKey(regPath)) - { - if (baseKey == null) - { - LogHelper.WriteLogToFile($"注册表路径不存在: {regPath}", LogHelper.LogType.Warning); - // 尝试创建路径 - try - { - using (RegistryKey createKey = Registry.CurrentUser.CreateSubKey(regPath, true)) - { - if (createKey != null) - { - createKey.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile($"创建并设置注册表路径: {regPath}"); - } - } - } - catch (Exception createEx) - { - LogHelper.WriteLogToFile($"创建注册表路径失败: {createEx.Message}", LogHelper.LogType.Error); - } - continue; - } - - // 备份路径更改为软件根目录下的saves/RegistryBackups文件夹 - string backupPath = Path.Combine(RootPath, "Saves", "RegistryBackups"); - LogHelper.WriteLogToFile($"备份路径: {backupPath}"); - - if (!Directory.Exists(backupPath)) - { - Directory.CreateDirectory(backupPath); - LogHelper.WriteLogToFile($"创建备份目录: {backupPath}"); - } - - string backupFile = Path.Combine(backupPath, $"SecurityBackup_{version}_{DateTime.Now:yyyyMMddHHmmss}.reg"); - LogHelper.WriteLogToFile($"创建备份文件: {backupFile}"); - - // 使用UTF8编码写入注册表文件 - using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8)) - { - sw.WriteLine("Windows Registry Editor Version 5.00\n"); - sw.WriteLine(); - sw.WriteLine($"[{Registry.CurrentUser.Name}\\{regPath}]"); - - foreach (string valueName in baseKey.GetValueNames()) - { - object value = baseKey.GetValue(valueName); - sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}"); - LogHelper.WriteLogToFile($"备份注册表值: {valueName} = {value}"); - } - } - - using (RegistryKey key = Registry.CurrentUser.CreateSubKey(regPath, true)) - { - // 仅在值不存在或不等于1时更新 - object currentValue = key.GetValue("DisableProtectedView"); - if (currentValue == null || (int)currentValue != 1) - { - key.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile($"Office {version} 注册表值已设置: DisableProtectedView = 1"); - } - else - { - LogHelper.WriteLogToFile($"Office {version} 注册表值已存在且无需更改: DisableProtectedView = 1"); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理Office版本 {version} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 处理Office 365的特殊路径 - TryModifyOffice365Registry(); - } - catch (SecurityException secEx) - { - LogHelper.WriteLogToFile($"安全异常: {secEx.Message}", LogHelper.LogType.Error); - ShowPermissionError(); - } - catch (UnauthorizedAccessException authEx) - { - LogHelper.WriteLogToFile($"访问被拒绝: {authEx.Message}", LogHelper.LogType.Error); - ShowPermissionError(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"未知错误: {ex.GetType().FullName} - {ex.Message}", LogHelper.LogType.Error); - LogHelper.WriteLogToFile(ex.StackTrace); - } } private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) @@ -916,7 +967,7 @@ namespace Ink_Canvas catch { } } - // 新增:用于设置崩溃后操作类型 + // 用于设置崩溃后操作类型 public enum CrashActionType { SilentRestart, @@ -978,7 +1029,7 @@ namespace Ink_Canvas watchdogProcess = Process.Start(psi); } - // 看门狗主逻辑(在 Main 函数或 App_Startup 入口前加判断) + // 看门狗主逻辑 public static void RunWatchdogIfNeeded() { var args = Environment.GetCommandLineArgs(); @@ -1025,7 +1076,7 @@ namespace Ink_Canvas // 仅在软件内主动退出时关闭看门狗,并写入退出信号 try { - // 新增:记录应用退出状态 + // 记录应用退出状态 string exitType = IsAppExitByUser ? "用户主动退出" : "应用程序退出"; WriteCrashLog($"{exitType},退出代码: {e.ApplicationExitCode}"); @@ -1061,450 +1112,5 @@ namespace Ink_Canvas catch { } } } - - /// - /// 检查Office是否安装 - /// - private bool IsOfficeInstalled() - { - try - { - // 检查多个可能的注册表路径 - // 1. 检查传统的Office版本 - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office")) - { - if (key != null && key.GetSubKeyNames().Any(name => name.Contains(".0"))) - { - LogHelper.WriteLogToFile("检测到传统Office安装"); - return true; - } - } - - // 2. 检查64位注册表中的Office - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Microsoft\\Office")) - { - if (key != null && key.GetSubKeyNames().Any(name => name.Contains(".0"))) - { - LogHelper.WriteLogToFile("检测到64位注册表中的Office安装"); - return true; - } - } - - // 3. 检查Office 365/Click-to-Run安装 - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun")) - { - if (key != null) - { - LogHelper.WriteLogToFile("检测到Office 365 Click-to-Run"); - return true; - } - } - - // 4. 检查Office 365部署配置 - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\15.0\\ClickToRun")) - { - if (key != null) - { - LogHelper.WriteLogToFile("检测到Office 365 (15.0)"); - return true; - } - } - - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\16.0\\ClickToRun")) - { - if (key != null) - { - LogHelper.WriteLogToFile("检测到Office 365 (16.0)"); - return true; - } - } - - // 5. 检查Office 365零售订阅信息 - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration")) - { - if (key != null) - { - LogHelper.WriteLogToFile("检测到Office 365配置"); - return true; - } - } - - LogHelper.WriteLogToFile("未检测到任何Office安装", LogHelper.LogType.Warning); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查Office安装时出错: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 显示权限不足的错误提示 - /// - private void ShowPermissionError() - { - const string message = "需要管理员权限才能完成此操作\n请以管理员身份重新启动应用程序"; - LogHelper.WriteLogToFile(message, LogHelper.LogType.Error); - MessageBox.Show(message, "权限错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - - /// - /// 获取所有已安装的Office版本 - /// - private List GetOfficeVersions() - { - var versions = new List(); - try - { - // 检查HKLM - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office")) - { - if (key != null) - { - foreach (var subKeyName in key.GetSubKeyNames()) - { - if (subKeyName.Contains(".0")) - { - versions.Add(subKeyName); - LogHelper.WriteLogToFile($"在HKLM中找到Office版本: {subKeyName}"); - } - } - } - } - - // 检查HKCU - using (var key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Office")) - { - if (key != null) - { - foreach (var subKeyName in key.GetSubKeyNames()) - { - if (subKeyName.Contains(".0") && !versions.Contains(subKeyName)) - { - versions.Add(subKeyName); - LogHelper.WriteLogToFile($"在HKCU中找到Office版本: {subKeyName}"); - } - } - } - } - - // 检查64位注册表 - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Microsoft\\Office")) - { - if (key != null) - { - foreach (var subKeyName in key.GetSubKeyNames()) - { - if (subKeyName.Contains(".0") && !versions.Contains(subKeyName)) - { - versions.Add(subKeyName); - LogHelper.WriteLogToFile($"在64位注册表中找到Office版本: {subKeyName}"); - } - } - } - } - - // 检查Office 365的特殊路径 - CheckOffice365Versions(versions); - - // 如果没有找到任何版本,添加默认的Office 365版本号 - if (versions.Count == 0 && IsOffice365Installed()) - { - versions.Add("16.0"); - LogHelper.WriteLogToFile("未找到具体版本,添加默认Office 365版本: 16.0"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取Office版本时出错: {ex.Message}", LogHelper.LogType.Error); - } - - // 按版本号排序 - versions.Sort((a, b) => - { - try - { - double va = double.Parse(a.Replace(".0", "")); - double vb = double.Parse(b.Replace(".0", "")); - return vb.CompareTo(va); // 降序排列,最新版本在前 - } - catch - { - return 0; - } - }); - - return versions; - } - - /// - /// 检测Office 365是否已安装 - /// - private bool IsOffice365Installed() - { - try - { - // 检查多个Office 365特定路径 - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun")) - { - if (key != null) - return true; - } - - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\15.0\\ClickToRun")) - { - if (key != null) - return true; - } - - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\16.0\\ClickToRun")) - { - if (key != null) - return true; - } - - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration")) - { - if (key != null) - return true; - } - - return false; - } - catch - { - return false; - } - } - - /// - /// 检查Office 365特有的版本信息 - /// - private void CheckOffice365Versions(List versions) - { - try - { - // 检查Click-to-Run版本路径 - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration")) - { - if (key != null) - { - var platformVersion = key.GetValue("Platform") as string; - var clickToRunVersion = key.GetValue("VersionToReport") as string; - - if (!string.IsNullOrEmpty(platformVersion)) - { - var majorVersion = platformVersion.Split('.').FirstOrDefault(); - if (!string.IsNullOrEmpty(majorVersion) && !versions.Contains($"{majorVersion}.0")) - { - versions.Add($"{majorVersion}.0"); - LogHelper.WriteLogToFile($"在Office 365配置中找到平台版本: {majorVersion}.0"); - } - } - - if (!string.IsNullOrEmpty(clickToRunVersion)) - { - var majorVersion = clickToRunVersion.Split('.').FirstOrDefault(); - if (!string.IsNullOrEmpty(majorVersion) && !versions.Contains($"{majorVersion}.0")) - { - versions.Add($"{majorVersion}.0"); - LogHelper.WriteLogToFile($"在Office 365配置中找到报告版本: {majorVersion}.0"); - } - } - } - } - - // 检查安装路径来确认版本 - var possibleVersions = new[] { "15.0", "16.0" }; // Office 2013 (15.0) 和 Office 2016/2019/365 (16.0) - foreach (var version in possibleVersions) - { - using (var key = Registry.LocalMachine.OpenSubKey($"Software\\Microsoft\\Office\\{version}\\Common\\InstallRoot")) - { - if (key != null && key.GetValue("Path") != null && !versions.Contains(version)) - { - versions.Add(version); - LogHelper.WriteLogToFile($"在InstallRoot中找到Office版本: {version}"); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查Office 365版本时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 尝试修改Office 365的特殊注册表路径 - /// - private void TryModifyOffice365Registry() - { - try - { - // 准备备份目录 - string backupPath = Path.Combine(RootPath, "Saves", "RegistryBackups"); - if (!Directory.Exists(backupPath)) - { - Directory.CreateDirectory(backupPath); - LogHelper.WriteLogToFile($"创建Office 365备份目录: {backupPath}"); - } - - // 检查Office 365 Outlook和PowerPoint的特定路径 - string[] apps = { "outlook", "powerpoint" }; - - foreach (var app in apps) - { - // 检查用户级别的注册表 - string regPath = $"Software\\Microsoft\\Office\\16.0\\{app}\\Security"; - LogHelper.WriteLogToFile($"检查Office 365特定应用注册表: {regPath}"); - - try - { - // 先检查是否存在该路径 - using (var baseKey = Registry.CurrentUser.OpenSubKey(regPath)) - { - // 如果路径存在,先备份 - if (baseKey != null) - { - string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_{app}_{DateTime.Now:yyyyMMddHHmmss}.reg"); - LogHelper.WriteLogToFile($"创建Office 365 {app}备份文件: {backupFile}"); - - // 使用UTF8编码写入注册表文件 - using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8)) - { - sw.WriteLine("Windows Registry Editor Version 5.00\n"); - sw.WriteLine(); - sw.WriteLine($"[{Registry.CurrentUser.Name}\\{regPath}]"); - - foreach (string valueName in baseKey.GetValueNames()) - { - object value = baseKey.GetValue(valueName); - sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}"); - LogHelper.WriteLogToFile($"备份Office 365 {app}注册表值: {valueName} = {value}"); - } - } - } - } - - // 修改或创建注册表项 - using (var key = Registry.CurrentUser.CreateSubKey(regPath, true)) - { - if (key != null) - { - object currentValue = key.GetValue("DisableProtectedView"); - if (currentValue == null || (int)currentValue != 1) - { - key.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile($"Office 365 {app} 注册表值已设置: DisableProtectedView = 1"); - } - else - { - LogHelper.WriteLogToFile($"Office 365 {app} 注册表值已存在且无需更改"); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"修改 {app} 注册表时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 尝试通过Office信任中心路径修改 - string trustCenterPath = "Software\\Microsoft\\Office\\16.0\\Common\\Security\\FileValidation"; - LogHelper.WriteLogToFile($"检查信任中心路径: {trustCenterPath}"); - - try - { - // 先检查是否存在该路径 - using (var baseKey = Registry.CurrentUser.OpenSubKey(trustCenterPath)) - { - // 如果路径存在,先备份 - if (baseKey != null) - { - string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_TrustCenter_{DateTime.Now:yyyyMMddHHmmss}.reg"); - LogHelper.WriteLogToFile($"创建信任中心备份文件: {backupFile}"); - - // 使用UTF8编码写入注册表文件 - using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8)) - { - sw.WriteLine("Windows Registry Editor Version 5.00\n"); - sw.WriteLine(); - sw.WriteLine($"[{Registry.CurrentUser.Name}\\{trustCenterPath}]"); - - foreach (string valueName in baseKey.GetValueNames()) - { - object value = baseKey.GetValue(valueName); - sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}"); - LogHelper.WriteLogToFile($"备份信任中心注册表值: {valueName} = {value}"); - } - } - } - } - - using (var key = Registry.CurrentUser.CreateSubKey(trustCenterPath, true)) - { - if (key != null) - { - key.SetValue("DisableEditFromPV", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile("已禁用受保护视图中的编辑"); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"修改信任中心路径时出错: {ex.Message}", LogHelper.LogType.Error); - } - - // 尝试修改EnableEditWhileViewingPolicy - string policyPath = "Software\\Policies\\Microsoft\\Office\\16.0\\Common\\Security"; - try - { - // 先检查是否存在该路径 - using (var baseKey = Registry.CurrentUser.OpenSubKey(policyPath)) - { - // 如果路径存在,先备份 - if (baseKey != null) - { - string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_Policy_{DateTime.Now:yyyyMMddHHmmss}.reg"); - LogHelper.WriteLogToFile($"创建策略备份文件: {backupFile}"); - - // 使用UTF8编码写入注册表文件 - using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8)) - { - sw.WriteLine("Windows Registry Editor Version 5.00\n"); - sw.WriteLine(); - sw.WriteLine($"[{Registry.CurrentUser.Name}\\{policyPath}]"); - - foreach (string valueName in baseKey.GetValueNames()) - { - object value = baseKey.GetValue(valueName); - sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}"); - LogHelper.WriteLogToFile($"备份策略注册表值: {valueName} = {value}"); - } - } - } - } - - using (var key = Registry.CurrentUser.CreateSubKey(policyPath, true)) - { - if (key != null) - { - key.SetValue("EnableEditWhileViewingPolicy", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile("已启用查看时编辑策略"); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"修改策略路径时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"修改Office 365注册表时发生未知错误: {ex.Message}", LogHelper.LogType.Error); - } - } } } diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 1f1d5c72..16ecbd01 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.4")] -[assembly: AssemblyFileVersion("1.7.11.4")] +[assembly: AssemblyVersion("1.7.12.0")] +[assembly: AssemblyFileVersion("1.7.12.0")] diff --git a/Ink Canvas/Helpers/CameraService.cs b/Ink Canvas/Helpers/CameraService.cs index 449e31fe..e0c4c68c 100644 --- a/Ink Canvas/Helpers/CameraService.cs +++ b/Ink Canvas/Helpers/CameraService.cs @@ -18,6 +18,11 @@ namespace Ink_Canvas.Helpers private readonly object _frameLock = new object(); private Dispatcher _dispatcher; + // 新增属性 + private int _rotationAngle = 0; // 0=0度,1=90度,2=180度,3=270度 + private int _resolutionWidth = 640; + private int _resolutionHeight = 480; + public event EventHandler FrameReceived; public event EventHandler ErrorOccurred; @@ -25,6 +30,25 @@ namespace Ink_Canvas.Helpers public List AvailableCameras { get; private set; } public FilterInfo CurrentCamera { get; private set; } + // 新增属性 + public int RotationAngle + { + get => _rotationAngle; + set => _rotationAngle = Math.Max(0, Math.Min(3, value)); + } + + public int ResolutionWidth + { + get => _resolutionWidth; + set => _resolutionWidth = Math.Max(320, Math.Min(1920, value)); + } + + public int ResolutionHeight + { + get => _resolutionHeight; + set => _resolutionHeight = Math.Max(240, Math.Min(1080, value)); + } + public CameraService() { _dispatcher = Dispatcher.CurrentDispatcher; @@ -32,6 +56,16 @@ namespace Ink_Canvas.Helpers RefreshCameraList(); } + public CameraService(int rotationAngle, int resolutionWidth, int resolutionHeight) + { + _dispatcher = Dispatcher.CurrentDispatcher; + AvailableCameras = new List(); + _rotationAngle = rotationAngle; + _resolutionWidth = resolutionWidth; + _resolutionHeight = resolutionHeight; + RefreshCameraList(); + } + /// /// 刷新可用摄像头列表 /// @@ -242,14 +276,16 @@ namespace Ink_Canvas.Helpers var width = sourceFrame.Width; var height = sourceFrame.Height; - if (width > 0 && height > 0) - { - _currentFrame = new Bitmap(width, height, PixelFormat.Format24bppRgb); - using (var graphics = Graphics.FromImage(_currentFrame)) - { - graphics.DrawImage(sourceFrame, 0, 0); - } - } + if (width > 0 && height > 0) + { + // 应用旋转 + Bitmap rotatedFrame = ApplyRotation(sourceFrame); + + // 应用分辨率调整 + _currentFrame = ResizeImage(rotatedFrame, _resolutionWidth, _resolutionHeight); + + rotatedFrame?.Dispose(); + } else { _currentFrame = null; @@ -300,6 +336,46 @@ namespace Ink_Canvas.Helpers return AvailableCameras.Count > 0; } + /// + /// 应用旋转到图像 + /// + private Bitmap ApplyRotation(Bitmap source) + { + if (_rotationAngle == 0) + return new Bitmap(source); + + var rotationType = RotateFlipType.RotateNoneFlipNone; + switch (_rotationAngle) + { + case 1: rotationType = RotateFlipType.Rotate90FlipNone; break; + case 2: rotationType = RotateFlipType.Rotate180FlipNone; break; + case 3: rotationType = RotateFlipType.Rotate270FlipNone; break; + } + + var rotated = new Bitmap(source); + rotated.RotateFlip(rotationType); + return rotated; + } + + /// + /// 调整图像大小 + /// + private Bitmap ResizeImage(Bitmap source, int width, int height) + { + if (source.Width == width && source.Height == height) + return new Bitmap(source); + + var resized = new Bitmap(width, height, PixelFormat.Format24bppRgb); + using (var graphics = Graphics.FromImage(resized)) + { + graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + graphics.DrawImage(source, 0, 0, width, height); + } + return resized; + } + public void Dispose() { StopPreview(); diff --git a/Ink Canvas/Helpers/FileAssociationManager.cs b/Ink Canvas/Helpers/FileAssociationManager.cs index cc5460ad..89525e93 100644 --- a/Ink Canvas/Helpers/FileAssociationManager.cs +++ b/Ink Canvas/Helpers/FileAssociationManager.cs @@ -25,6 +25,7 @@ namespace Ink_Canvas.Helpers private const string IpcEventName = "InkCanvasFileAssociationEvent"; private const string IpcFilePrefix = "InkCanvasFileAssociation_"; private const string IpcBoardModePrefix = "InkCanvasBoardMode_"; + private const string IpcShowModePrefix = "InkCanvasShowMode_"; private const int IpcTimeout = 5000; // 5秒超时 /// @@ -310,6 +311,56 @@ namespace Ink_Canvas.Helpers } } + /// + /// 尝试通过IPC将展开浮动栏命令发送给已运行的实例 + /// + /// 是否成功发送 + public static bool TrySendShowModeCommandToExistingInstance() + { + try + { + LogHelper.WriteLogToFile("尝试通过IPC发送展开浮动栏命令给已运行实例", LogHelper.LogType.Event); + + // 创建IPC文件 + string tempDir = Path.GetTempPath(); + string ipcFileName = IpcShowModePrefix + Guid.NewGuid().ToString("N") + ".tmp"; + string ipcFilePath = Path.Combine(tempDir, ipcFileName); + + // 写入展开浮动栏命令到IPC文件 + File.WriteAllText(ipcFilePath, "SHOW_MODE", 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监听器,等待其他实例发送文件路径 /// @@ -470,6 +521,61 @@ namespace Ink_Canvas.Helpers catch { } } } + + // 处理展开浮动栏命令IPC文件 + string[] showModeFiles = Directory.GetFiles(tempDir, IpcShowModePrefix + "*.tmp"); + foreach (string ipcFile in showModeFiles) + { + try + { + // 读取命令内容 + string command = File.ReadAllText(ipcFile, Encoding.UTF8); + + if (command == "SHOW_MODE") + { + LogHelper.WriteLogToFile("IPC接收到展开浮动栏命令", LogHelper.LogType.Event); + + // 在UI线程中处理展开浮动栏 + Application.Current.Dispatcher.BeginInvoke(new Action(async () => + { + try + { + // 获取主窗口并展开浮动栏 + if (Application.Current.MainWindow is MainWindow mainWindow) + { + // 如果当前处于收纳模式,则展开浮动栏 + if (mainWindow.isFloatingBarFolded) + { + await mainWindow.UnFoldFloatingBar(new object()); + } + mainWindow.ShowNotification("已退出收纳模式并恢复浮动栏"); + } + } + 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) { diff --git a/Ink Canvas/Helpers/FloatingWindowInterceptor.cs b/Ink Canvas/Helpers/FloatingWindowInterceptor.cs index 665099e2..1e0e9d10 100644 --- a/Ink Canvas/Helpers/FloatingWindowInterceptor.cs +++ b/Ink Canvas/Helpers/FloatingWindowInterceptor.cs @@ -55,6 +55,27 @@ namespace Ink_Canvas.Helpers [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out ForegroundWindowInfo.RECT lpRect); + [DllImport("user32.dll")] + private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + + [DllImport("dwmapi.dll")] + private static extern int DwmGetWindowAttribute(IntPtr hWnd, int dwAttribute, out ForegroundWindowInfo.RECT pvAttribute, int cbAttribute); + + [DllImport("user32.dll")] + private static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [DllImport("gdi32.dll")] + private static extern int GetDeviceCaps(IntPtr hdc, int nIndex); + + [DllImport("user32.dll")] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool BringWindowToTop(IntPtr hWnd); + [DllImport("kernel32.dll")] private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); @@ -68,9 +89,11 @@ namespace Ink_Canvas.Helpers private const int SW_HIDE = 0; private const int SW_SHOW = 1; + private const int SW_SHOWNORMAL = 1; private const int SW_MINIMIZE = 6; private const int SW_RESTORE = 9; + private const int GWL_STYLE = -16; private const int GWL_EXSTYLE = -20; private const uint WS_EX_TOOLWINDOW = 0x00000080; private const uint WS_EX_APPWINDOW = 0x00040000; @@ -79,10 +102,17 @@ namespace Ink_Canvas.Helpers private const uint SWP_NOSIZE = 0x0001; private const uint SWP_NOZORDER = 0x0004; private const uint SWP_HIDEWINDOW = 0x0080; + private const uint SWP_SHOWWINDOW = 0x0040; private const uint PROCESS_QUERY_INFORMATION = 0x0400; private const uint PROCESS_VM_READ = 0x0010; + private const uint WM_CLOSE = 0x0010; + private const int DWMWA_EXTENDED_FRAME_BOUNDS = 9; + private const int LOGPIXELSX = 88; + private const int LOGPIXELSY = 90; + + #endregion #region 拦截规则定义 @@ -212,6 +242,19 @@ namespace Ink_Canvas.Helpers public string Description { get; set; } public InterceptType? ParentType { get; set; } public List ChildTypes { get; set; } = new List(); + + // 新增的精确匹配字段 + public bool HasWindowStyle { get; set; } + public uint WindowStyle { get; set; } + public bool HasWindowSize { get; set; } + public int WindowWidth { get; set; } + public int WindowHeight { get; set; } + public bool ExactTitleMatch { get; set; } = false; + public bool ExactClassNameMatch { get; set; } = false; + + // 运行时状态字段 + public bool foundHwnd { get; set; } = false; + public IntPtr outHwnd { get; set; } = IntPtr.Zero; } #endregion @@ -225,13 +268,9 @@ namespace Ink_Canvas.Helpers 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 @@ -275,7 +314,14 @@ namespace Ink_Canvas.Helpers ClassNamePattern = "HwndWrapper[EasiNote.exe;;", IsEnabled = true, RequiresAdmin = false, - Description = "希沃白板3 桌面悬浮窗" + Description = "希沃白板3 桌面悬浮窗", + HasWindowStyle = true, + WindowStyle = 370081792, + HasWindowSize = true, + WindowWidth = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width, + WindowHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, + ExactTitleMatch = true, + ExactClassNameMatch = false }; // 希沃白板5 桌面悬浮窗 @@ -287,7 +333,14 @@ namespace Ink_Canvas.Helpers ClassNamePattern = "HwndWrapper[EasiNote;;", IsEnabled = true, RequiresAdmin = false, - Description = "希沃白板5 桌面悬浮窗" + Description = "希沃白板5 桌面悬浮窗", + HasWindowStyle = true, + WindowStyle = 369623040, + HasWindowSize = true, + WindowWidth = 550, + WindowHeight = 200, + ExactTitleMatch = false, + ExactClassNameMatch = false }; // 希沃白板5C 桌面悬浮窗 @@ -299,7 +352,14 @@ namespace Ink_Canvas.Helpers ClassNamePattern = "HwndWrapper[EasiNote5C;;", IsEnabled = true, RequiresAdmin = false, - Description = "希沃白板5C 桌面悬浮窗" + Description = "希沃白板5C 桌面悬浮窗", + HasWindowStyle = true, + WindowStyle = 369623040, + HasWindowSize = true, + WindowWidth = 550, + WindowHeight = 200, + ExactTitleMatch = false, + ExactClassNameMatch = false }; // 希沃品课教师端 桌面悬浮窗(父规则) @@ -313,7 +373,11 @@ namespace Ink_Canvas.Helpers RequiresAdmin = false, Description = "希沃品课教师端 桌面悬浮窗", ParentType = null, - ChildTypes = new List { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService } + ChildTypes = new List { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService }, + HasWindowStyle = true, + WindowStyle = 0x16CF0000, + ExactTitleMatch = true, + ExactClassNameMatch = true }; // 希沃品课教师端 画笔悬浮窗(子规则) @@ -327,7 +391,11 @@ namespace Ink_Canvas.Helpers RequiresAdmin = false, Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)", ParentType = InterceptType.SeewoPincoSideBarFloating, - ChildTypes = new List() + ChildTypes = new List(), + HasWindowStyle = true, + WindowStyle = 335675392, + ExactTitleMatch = true, + ExactClassNameMatch = true }; // 希沃品课教师端 桌面画板(子规则) @@ -341,7 +409,14 @@ namespace Ink_Canvas.Helpers RequiresAdmin = false, Description = "希沃品课教师端 桌面画板", ParentType = InterceptType.SeewoPincoSideBarFloating, - ChildTypes = new List() + ChildTypes = new List(), + HasWindowStyle = true, + WindowStyle = 369623040, + HasWindowSize = true, + WindowWidth = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width, + WindowHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, + ExactTitleMatch = false, + ExactClassNameMatch = false }; // 希沃PPT小工具 @@ -665,6 +740,12 @@ namespace Ink_Canvas.Helpers var rule = _interceptRules[type]; rule.IsEnabled = enabled; + // 如果规则被禁用,恢复相关的被拦截窗口 + if (!enabled) + { + RestoreWindowsByType(type); + } + // 如果是父规则被禁用,则禁用所有子规则 if (!enabled && rule.ChildTypes.Count > 0) { @@ -673,6 +754,7 @@ namespace Ink_Canvas.Helpers if (_interceptRules.ContainsKey(childType)) { _interceptRules[childType].IsEnabled = false; + RestoreWindowsByType(childType); } } } @@ -726,6 +808,14 @@ namespace Ink_Canvas.Helpers return new Dictionary(_interceptRules); } + /// + /// 获取当前被拦截的窗口数量 + /// + public int GetInterceptedWindowsCount() + { + return _interceptedWindows.Count; + } + /// /// 手动扫描一次 /// @@ -740,10 +830,41 @@ namespace Ink_Canvas.Helpers public void RestoreAllWindows() { var windowsToRestore = new List(_interceptedWindows.Keys); + var restoredCount = 0; + foreach (var hWnd in windowsToRestore) { - RestoreWindow(hWnd); + if (RestoreWindow(hWnd)) + { + restoredCount++; + } } + + } + + /// + /// 恢复指定类型的被拦截窗口 + /// + public void RestoreWindowsByType(InterceptType type) + { + var windowsToRestore = new List(); + foreach (var kvp in _interceptedWindows) + { + if (kvp.Value == type) + { + windowsToRestore.Add(kvp.Key); + } + } + + var restoredCount = 0; + foreach (var hWnd in windowsToRestore) + { + if (RestoreWindow(hWnd)) + { + restoredCount++; + } + } + } /// @@ -753,15 +874,29 @@ namespace Ink_Canvas.Helpers { if (!_interceptedWindows.ContainsKey(hWnd)) return false; + var interceptType = _interceptedWindows[hWnd]; + if (IsWindow(hWnd)) { + // 使用多种方法确保窗口恢复显示 ShowWindow(hWnd, SW_RESTORE); + ShowWindow(hWnd, SW_SHOW); + ShowWindow(hWnd, SW_SHOWNORMAL); + + // 将窗口置于前台并显示 + SetWindowPos(hWnd, IntPtr.Zero, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); + + // 强制将窗口带到前台 + BringWindowToTop(hWnd); + SetForegroundWindow(hWnd); + _interceptedWindows.Remove(hWnd); WindowRestored?.Invoke(this, new WindowRestoredEventArgs { WindowHandle = hWnd, - InterceptType = _interceptedWindows[hWnd] + InterceptType = interceptType }); return true; @@ -775,187 +910,103 @@ namespace Ink_Canvas.Helpers #region 私有方法 + /// + /// 清理无效的窗口句柄 + /// + private void CleanupInvalidWindows() + { + var invalidWindows = new List(); + + foreach (var kvp in _interceptedWindows) + { + var hWnd = kvp.Key; + if (!IsWindow(hWnd)) + { + invalidWindows.Add(hWnd); + } + } + foreach (var hWnd in invalidWindows) + { + _interceptedWindows.Remove(hWnd); + } + } + private void ScanForWindows(object state) { if (!_isRunning) return; - lock (_scanLock) + try { - try + // 简化的扫描逻辑 + var interceptedCount = 0; + CleanupInvalidWindows(); + + // 重置所有规则的发现状态 + foreach (var rule in _interceptRules.Values) { - var scanStartTime = DateTime.Now; - var windowsFound = 0; - var windowsIntercepted = 0; - - // 清理过期的缓存 - CleanupExpiredCache(); - - // 使用优化的扫描策略 - if (_consecutiveEmptyScans > 3) + if (rule.IsEnabled) { - // 如果连续多次扫描没有发现新窗口,使用快速扫描模式 - PerformQuickScan(ref windowsFound, ref windowsIntercepted); + rule.foundHwnd = false; } - else + } + + // 枚举所有窗口 + EnumWindows(EnumWindowsCallback, IntPtr.Zero); + + // 处理找到的窗口 + foreach (var rule in _interceptRules.Values) + { + if (rule.IsEnabled && rule.foundHwnd && rule.outHwnd != IntPtr.Zero) { - // 正常扫描模式 - PerformFullScan(ref windowsFound, ref windowsIntercepted); - } - - // 更新扫描统计 - UpdateScanStatistics(windowsFound, windowsIntercepted, scanStartTime); - - // 动态调整扫描间隔 - AdjustScanInterval(); - } - catch (Exception ex) - { - // 记录错误但不中断扫描 - LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error); - _consecutiveEmptyScans++; - } - } - } - - /// - /// 执行快速扫描 - 只检查已知进程 - /// - 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) + bool shouldIntercept = !_interceptedWindows.ContainsKey(rule.outHwnd) || + (_interceptedWindows.ContainsKey(rule.outHwnd) && IsWindowVisible(rule.outHwnd)); + + if (shouldIntercept) { - ProcessWindow(process.MainWindowHandle, scanData); + InterceptWindow(rule.outHwnd, rule); + interceptedCount++; } } } - catch + + // 更新统计 + if (interceptedCount == 0) { - // 忽略进程访问错误 + _consecutiveEmptyScans++; + } + else + { + _consecutiveEmptyScans = 0; + _lastSuccessfulScan = DateTime.Now; } } - - 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) => + catch (Exception ex) { - ProcessWindow(hWnd, scanData); - return true; - }, IntPtr.Zero); - - windowsFound = scanData.WindowsFound; - windowsIntercepted = scanData.WindowsIntercepted; + LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error); + _consecutiveEmptyScans++; + } } - /// - /// 处理单个窗口 - /// - private bool ProcessWindow(IntPtr hWnd, ScanData scanData) + + private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam) { try { + // 递归枚举子窗口 + EnumChildWindows(hWnd, EnumWindowsCallback, lParam); + // 基本检查 if (!IsWindow(hWnd) || !IsWindowVisible(hWnd)) return true; - - // 检查是否已经被拦截 - 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 - - // 跳过工具窗口 - if ((exStyle & WS_EX_TOOLWINDOW) != 0) - { - return true; - } - - // 跳过主窗口(有标题栏和系统菜单的窗口) - const uint WS_CAPTION = 0x00C00000; - const uint WS_SYSMENU = 0x00080000; - if ((style & WS_CAPTION) != 0 && (style & WS_SYSMENU) != 0) - { - return true; - } - - // 检查窗口大小,跳过过大的窗口 - var rect = new ForegroundWindowInfo.RECT(); - GetWindowRect(hWnd, out rect); - var width = rect.Right - rect.Left; - var height = rect.Bottom - rect.Top; - - if (width > 600 || height > 400) - { - return true; - } - - // 检查是否匹配拦截规则 + // 检查每个启用的规则 foreach (var rule in _interceptRules.Values) { - if (!rule.IsEnabled) continue; + if (!rule.IsEnabled || rule.foundHwnd) continue; - if (MatchesRule(windowInfo, rule)) + if (MatchesRulePrecise(hWnd, rule)) { - InterceptWindow(hWnd, rule); - scanData.WindowsIntercepted++; - break; + rule.outHwnd = hWnd; + rule.foundHwnd = true; } } @@ -963,107 +1014,11 @@ namespace Ink_Canvas.Helpers } catch (Exception ex) { - LogHelper.WriteLogToFile($"处理窗口时发生错误: {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"枚举窗口回调错误: {ex.Message}", LogHelper.LogType.Error); return true; } } - /// - /// 清理过期缓存 - /// - 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 @@ -1100,62 +1055,119 @@ namespace Ink_Canvas.Helpers } } - private bool MatchesRule(WindowInfo windowInfo, InterceptRule rule) + /// + /// 精确匹配规则 + /// + private bool MatchesRulePrecise(IntPtr hWnd, InterceptRule rule) { try { - // 检查进程名(如果指定了进程名) - if (!string.IsNullOrEmpty(rule.ProcessName)) - { - if (!windowInfo.ProcessName.ToLower().Contains(rule.ProcessName.ToLower())) - { - return false; - } - } - - // 检查窗口标题(如果指定了模式) - if (!string.IsNullOrEmpty(rule.WindowTitlePattern)) - { - if (!windowInfo.WindowTitle.ToLower().Contains(rule.WindowTitlePattern.ToLower())) - { - return false; - } - } - - // 检查类名(如果指定了模式) + // 检查类名 if (!string.IsNullOrEmpty(rule.ClassNamePattern)) { - if (!windowInfo.ClassName.ToLower().Contains(rule.ClassNamePattern.ToLower())) + var className = new StringBuilder(256); + GetClassName(hWnd, className, className.Capacity); + var classNameStr = className.ToString(); + + if (rule.ExactClassNameMatch) { + if (!classNameStr.Equals(rule.ClassNamePattern, StringComparison.OrdinalIgnoreCase)) + return false; + } + else + { + if (!classNameStr.Contains(rule.ClassNamePattern)) + return false; + } + } + + // 检查窗口标题 + if (!string.IsNullOrEmpty(rule.WindowTitlePattern)) + { + var windowTitle = new StringBuilder(256); + GetWindowText(hWnd, windowTitle, windowTitle.Capacity); + var titleStr = windowTitle.ToString(); + + if (rule.ExactTitleMatch) + { + if (!titleStr.Equals(rule.WindowTitlePattern, StringComparison.OrdinalIgnoreCase)) + return false; + } + else + { + if (!titleStr.Contains(rule.WindowTitlePattern)) + return false; + } + } + + // 检查窗口样式 + if (rule.HasWindowStyle) + { + var style = GetWindowLong(hWnd, GWL_STYLE); + if (style != rule.WindowStyle) + return false; + } + + // 检查窗口尺寸 + if (rule.HasWindowSize) + { + var rect = new ForegroundWindowInfo.RECT(); + if (DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out rect, Marshal.SizeOf(rect)) == 0) + { + var width = rect.Right - rect.Left; + var height = rect.Bottom - rect.Top; + + // 检查精确匹配 + if (rule.WindowWidth == width && rule.WindowHeight == height) + return true; + + // 检查缩放匹配 + var hdc = GetDC(IntPtr.Zero); + var horizontalDPI = GetDeviceCaps(hdc, LOGPIXELSX); + var verticalDPI = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(IntPtr.Zero, hdc); + + var scale = (horizontalDPI + verticalDPI) / 2.0f / 96.0f; + var scaledWidth = (int)(rule.WindowWidth * scale); + var scaledHeight = (int)(rule.WindowHeight * scale); + + if (Math.Abs(scaledWidth - width) <= 1 && Math.Abs(scaledHeight - height) <= 1) + return true; + return false; } } - // 如果所有检查都通过,就认为是目标窗口 return true; } catch (Exception ex) { - LogHelper.WriteLogToFile($"匹配规则时发生错误: {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"精确匹配规则时发生错误: {ex.Message}", LogHelper.LogType.Error); return false; } } + private bool MatchesRule(WindowInfo windowInfo, InterceptRule rule) + { + return MatchesRulePrecise(windowInfo.Handle, rule); + } + private void InterceptWindow(IntPtr hWnd, InterceptRule rule) { try { - // 使用多种方法隐藏窗口 - // 方法1:移动到屏幕外 - SetWindowPos(hWnd, IntPtr.Zero, -2000, -2000, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_HIDEWINDOW); - - // 方法2:最小化窗口 - ShowWindow(hWnd, SW_MINIMIZE); - - // 方法3:隐藏窗口 + if (!IsWindow(hWnd) || !IsWindowVisible(hWnd)) + { + if (_interceptedWindows.ContainsKey(hWnd)) + { + _interceptedWindows.Remove(hWnd); + } + return; + } + + // 直接隐藏窗口,不发送关闭消息 ShowWindow(hWnd, SW_HIDE); - + // 记录拦截的窗口 _interceptedWindows[hWnd] = rule.Type; @@ -1167,6 +1179,7 @@ namespace Ink_Canvas.Helpers Rule = rule, WindowTitle = GetWindowTitle(hWnd) }); + } catch (Exception ex) { @@ -1243,12 +1256,6 @@ 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/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 5ff02806..c2576703 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -575,6 +575,11 @@ + + + + + diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index e9495e55..1669a49e 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -1040,6 +1040,28 @@ + + + + + + + + + + + + + + + + @@ -3666,9 +3688,6 @@ - diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index a18bbb96..7857e8bb 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -511,8 +511,8 @@ namespace Ink_Canvas } SystemEvents.DisplaySettingsChanged += SystemEventsOnDisplaySettingsChanged; - // 自动收纳到侧边栏(若通过 --board 进入白板模式则跳过收纳) - if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode) + // 自动收纳到侧边栏(若通过 --board 进入白板模式或 --show 参数则跳过收纳) + if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode && !App.StartWithShowMode) { FoldFloatingBar_MouseUp(new object(), null); } @@ -523,8 +523,6 @@ namespace Ink_Canvas else RadioCrashNoAction.IsChecked = true; - - // 如果当前不是黑板模式,则切换到黑板模式 if (currentMode == 0) { @@ -584,6 +582,21 @@ namespace Ink_Canvas SwitchToBoardMode(); }), DispatcherPriority.Loaded); } + + // 检查是否通过--show参数启动,如果是则确保退出收纳模式并恢复浮动栏 + if (App.StartWithShowMode) + { + LogHelper.WriteLogToFile("检测到--show参数,退出收纳模式并恢复浮动栏", LogHelper.LogType.Event); + // 延迟执行,确保UI已完全加载 + Dispatcher.BeginInvoke(new Action(async () => + { + // 如果当前处于收纳模式,则展开浮动栏 + if (isFloatingBarFolded) + { + await UnFoldFloatingBar(new object()); + } + }), DispatcherPriority.Loaded); + } } private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e) @@ -727,6 +740,13 @@ namespace Ink_Canvas _inkFadeManager = null; } + // 清理悬浮窗拦截管理器 + if (_floatingWindowInterceptorManager != null) + { + _floatingWindowInterceptorManager.Dispose(); + _floatingWindowInterceptorManager = null; + } + // 停止置顶维护定时器 StopTopmostMaintenance(); diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index 289e9039..5fe56780 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -29,7 +29,6 @@ namespace Ink_Canvas { public partial class MainWindow : Window { - // 添加当前模式的缓存,避免依赖可能过时的inkCanvas.EditingMode状态 private string _currentToolMode = "cursor"; #region "手勢"按鈕 @@ -1537,7 +1536,6 @@ namespace Ink_Canvas // 仅计算Windows任务栏高度,不考虑其他程序对工作区的影响 var toolbarHeight = ForegroundWindowInfo.GetTaskbarHeight(screen, dpiScaleY); - // 计算浮动栏位置,考虑快捷调色盘的显示状态 // 使用更可靠的方法获取浮动栏宽度 double baseWidth = ViewboxFloatingBar.ActualWidth; @@ -1577,7 +1575,7 @@ namespace Ink_Canvas else { // 双行显示模式,宽度较大 - floatingBarWidth = Math.Max(floatingBarWidth, 820 * ViewboxFloatingBarScaleTransform.ScaleX); + floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX); } } @@ -1657,7 +1655,7 @@ namespace Ink_Canvas public async void PureViewboxFloatingBarMarginAnimationInDesktopMode() { - // 新增:在白板模式下不执行浮动栏动画 + // 在白板模式下不执行浮动栏动画 if (currentMode == 1) { return; @@ -1682,8 +1680,6 @@ namespace Ink_Canvas // 仅计算Windows任务栏高度,不考虑其他程序对工作区的影响 var toolbarHeight = ForegroundWindowInfo.GetTaskbarHeight(screen, dpiScaleY); - // 计算浮动栏位置,考虑快捷调色盘的显示状态 - // 使用更可靠的方法获取浮动栏宽度 double baseWidth = ViewboxFloatingBar.ActualWidth; // 如果ActualWidth为0,尝试使用DesiredSize @@ -1722,13 +1718,13 @@ namespace Ink_Canvas else { // 双行显示模式,宽度较大 - floatingBarWidth = Math.Max(floatingBarWidth, 850 * ViewboxFloatingBarScaleTransform.ScaleX); + floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX); } } pos.X = (screenWidth - floatingBarWidth) / 2; - // 如果任务栏高度为0(隐藏状态),则使用固定边距 + // 如果任务栏高度为0,则使用固定边距 if (toolbarHeight == 0) { pos.Y = screenHeight - ViewboxFloatingBar.ActualHeight * ViewboxFloatingBarScaleTransform.ScaleY - @@ -1828,7 +1824,7 @@ namespace Ink_Canvas else { // 双行显示模式,宽度较大 - floatingBarWidth = Math.Max(floatingBarWidth, 820 * ViewboxFloatingBarScaleTransform.ScaleX); + floatingBarWidth = Math.Max(floatingBarWidth, 68 * ViewboxFloatingBarScaleTransform.ScaleX); } } diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 48b22800..dd5391e5 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -186,6 +186,20 @@ namespace Ink_Canvas SaveSettingsToFile(); } + private void ToggleSwitchEnableSplashScreen_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.EnableSplashScreen = ToggleSwitchEnableSplashScreen.IsOn; + SaveSettingsToFile(); + } + + private void ComboBoxSplashScreenStyle_SelectionChanged(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.SplashScreenStyle = ComboBoxSplashScreenStyle.SelectedIndex; + SaveSettingsToFile(); + } + private void ViewboxFloatingBarScaleTransformValueSlider_ValueChanged(object sender, RoutedEventArgs e) { if (!isLoaded) return; diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index f5d4f5cb..0653fd3a 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -332,6 +332,10 @@ namespace Ink_Canvas ToggleSwitchEnableQuickPanel.IsOn = Settings.Appearance.IsShowQuickPanel; + ToggleSwitchEnableSplashScreen.IsOn = Settings.Appearance.EnableSplashScreen; + + ComboBoxSplashScreenStyle.SelectedIndex = Settings.Appearance.SplashScreenStyle; + ToggleSwitchEnableTrayIcon.IsOn = Settings.Appearance.EnableTrayIcon; ICCTrayIconExampleImage.Visibility = Settings.Appearance.EnableTrayIcon ? Visibility.Visible : Visibility.Collapsed; diff --git a/Ink Canvas/MainWindow_cs/MW_Timer.cs b/Ink Canvas/MainWindow_cs/MW_Timer.cs index aaa6b3f4..309f239b 100644 --- a/Ink Canvas/MainWindow_cs/MW_Timer.cs +++ b/Ink Canvas/MainWindow_cs/MW_Timer.cs @@ -275,6 +275,8 @@ namespace Ink_Canvas { var processes = Process.GetProcessesByName("EasiNote"); if (processes.Length > 0) arg += " /IM EasiNote.exe"; + var seewoStartProcesses = Process.GetProcessesByName("SeewoStart"); + if (seewoStartProcesses.Length > 0) arg += " /IM SeewoStart.exe"; } if (Settings.Automation.IsAutoKillHiteAnnotation) @@ -471,10 +473,22 @@ namespace Ink_Canvas Trace.WriteLine(ForegroundWindowInfo.ProcessPath()); Trace.WriteLine(version); Trace.WriteLine(prodName); - if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote && (!(windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500) || - !Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno)) + if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote) { // EasiNote5 - if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null); + // 检查是否是桌面批注窗口 + bool isAnnotationWindow = windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500; + + // 如果启用了忽略桌面批注窗口功能,且当前是批注窗口 + if (Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno && isAnnotationWindow) + { + // 强制保持收纳状态 + if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null); + } + else if (!isAnnotationWindow) + { + // 非批注窗口时正常收纳 + if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null); + } } else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3) { // EasiNote3 diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs index bd5b753d..b34b60b4 100644 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs @@ -696,6 +696,11 @@ namespace Ink_Canvas touchPoint = e.GetTouchPoint(inkCanvas); EraserOverlay_PointerDown(sender); EraserOverlay_PointerMove(sender, touchPoint.Position); + if (Settings.Canvas.IsShowCursor) + { + inkCanvas.ForceCursor = false; + inkCanvas.UseCustomCursor = false; + } } } } @@ -840,6 +845,11 @@ namespace Ink_Canvas // 禁用橡皮擦覆盖层 DisableEraserOverlay(); + if (Settings.Canvas.IsShowCursor) + { + inkCanvas.ForceCursor = true; + inkCanvas.UseCustomCursor = true; + } LogHelper.WriteLogToFile("Palm eraser state reset completed"); } @@ -948,6 +958,12 @@ namespace Ink_Canvas ViewboxFloatingBar.IsHitTestVisible = true; BlackboardUIGridForInkReplay.IsHitTestVisible = true; + DisableEraserOverlay(); + if (Settings.Canvas.IsShowCursor) + { + inkCanvas.ForceCursor = true; + inkCanvas.UseCustomCursor = true; + } LogHelper.WriteLogToFile("Palm eraser force recovery completed"); } diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs index 1f1d5c72..16ecbd01 100644 --- a/Ink Canvas/Properties/AssemblyInfo.cs +++ b/Ink Canvas/Properties/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.4")] -[assembly: AssemblyFileVersion("1.7.11.4")] +[assembly: AssemblyVersion("1.7.12.0")] +[assembly: AssemblyFileVersion("1.7.12.0")] diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index 77f0103b..73cd49ed 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -27,6 +27,8 @@ namespace Ink_Canvas public RandSettings RandSettings { get; set; } = new RandSettings(); [JsonProperty("modeSettings")] public ModeSettings ModeSettings { get; set; } = new ModeSettings(); + [JsonProperty("camera")] + public CameraSettings Camera { get; set; } = new CameraSettings(); } public class Canvas @@ -199,6 +201,10 @@ namespace Ink_Canvas public int UnFoldButtonImageType { get; set; } [JsonProperty("isShowLRSwitchButton")] public bool IsShowLRSwitchButton { get; set; } + [JsonProperty("enableSplashScreen")] + public bool EnableSplashScreen { get; set; } = false; + [JsonProperty("splashScreenStyle")] + public int SplashScreenStyle { get; set; } = 1; // 0-随机, 1-跟随四季, 2-春季, 3-夏季, 4-秋季, 5-冬季, 6-马年限定 [JsonProperty("isShowQuickPanel")] public bool IsShowQuickPanel { get; set; } = true; [JsonProperty("chickenSoupSource")] @@ -678,4 +684,19 @@ namespace Ink_Canvas [JsonProperty("isPPTOnlyMode")] public bool IsPPTOnlyMode { get; set; } = false; // 是否为仅PPT模式,默认为false(正常模式) } + + public class CameraSettings + { + [JsonProperty("rotationAngle")] + public int RotationAngle { get; set; } = 0; + + [JsonProperty("resolutionWidth")] + public int ResolutionWidth { get; set; } = 1920; + + [JsonProperty("resolutionHeight")] + public int ResolutionHeight { get; set; } = 1080; + + [JsonProperty("selectedCameraIndex")] + public int SelectedCameraIndex { get; set; } = 0; + } } diff --git a/Ink Canvas/Resources/Startup-animation/ICC Autumn.png b/Ink Canvas/Resources/Startup-animation/ICC Autumn.png new file mode 100644 index 00000000..2fdaee0d Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Autumn.png differ diff --git a/Ink Canvas/Resources/Startup-animation/ICC Horse.png b/Ink Canvas/Resources/Startup-animation/ICC Horse.png new file mode 100644 index 00000000..0a4d14f8 Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Horse.png differ diff --git a/Ink Canvas/Resources/Startup-animation/ICC Spring.png b/Ink Canvas/Resources/Startup-animation/ICC Spring.png new file mode 100644 index 00000000..15be2f3e Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Spring.png differ diff --git a/Ink Canvas/Resources/Startup-animation/ICC Summer.png b/Ink Canvas/Resources/Startup-animation/ICC Summer.png new file mode 100644 index 00000000..b18708a3 Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Summer.png differ diff --git a/Ink Canvas/Resources/Startup-animation/ICC Winter.png b/Ink Canvas/Resources/Startup-animation/ICC Winter.png new file mode 100644 index 00000000..f11542f4 Binary files /dev/null and b/Ink Canvas/Resources/Startup-animation/ICC Winter.png differ diff --git a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml index b4832ff9..97c4804f 100644 --- a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml +++ b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml @@ -181,29 +181,65 @@ VerticalAlignment="Center" /> - - - + + + + + + +