commit fdd341889043756f0fe1f3036a0dcff5ae7060d7 Author: miao-moe Date: Sun Jun 28 01:43:01 2026 +0800 Initial commit: Better-Seewo EN5 plugin v1.0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a603569 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +## .NET +bin/ +obj/ +*.user +*.suo +*.sln.docstates + +## NuGet +packages/*.nupkg + +## Build +build/ + +## IDE +.vs/ +*.csproj.user diff --git a/Better-EN5/Better-EN5.csproj b/Better-EN5/Better-EN5.csproj new file mode 100644 index 0000000..de8575f --- /dev/null +++ b/Better-EN5/Better-EN5.csproj @@ -0,0 +1,22 @@ + + + WinExe + net6.0-windows + true + true + enable + BetterEN5 + Better-EN5 + 1.0.1 + 1.0.1 + 云汀 + 云汀 + 云汀 + Better-Seewo 增强插件 + 希沃白板功能增强插件 - 墨迹管理、文件转换增强、触摸检测修复 + all + + + + + diff --git a/Better-EN5/EventId.cs b/Better-EN5/EventId.cs new file mode 100644 index 0000000..34be23b --- /dev/null +++ b/Better-EN5/EventId.cs @@ -0,0 +1,11 @@ +namespace BetterEN5 +{ + static class EventId + { + public const string OpenSettingsEvent = "BetterEN5_OpenSettings"; + public const string InkExportEvent = "BetterEN5_InkExport"; + public const string InkImportEvent = "BetterEN5_InkImport"; + public const string ConversionEvent = "BetterEN5_Conversion"; + public const string TouchFixAppliedEvent = "BetterEN5_TouchFixApplied"; + } +} diff --git a/Better-EN5/Program.cs b/Better-EN5/Program.cs new file mode 100644 index 0000000..8ec1a0e --- /dev/null +++ b/Better-EN5/Program.cs @@ -0,0 +1,62 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using System.Windows; +using Cvte.Composition; +using Cvte.EasiNote; +using BetterEN5.Services; +using BetterEN5.UI; + +namespace BetterEN5 +{ + class Program : dotnetCampus.EasiPlugins.EasiPlugin + { + protected override Task OnRunningAsync() + { + if (!EN.CommandOptions.IsCloud) + { + if (EN.App.IsReady) + { + Run(); + } + else + { + EN.App.Ready += App_Ready; + } + } + return Task.CompletedTask; + } + + private void App_Ready(object? sender, EventArgs e) + { + EN.App.Ready -= App_Ready; + Run(); + } + + private async void Run() + { + await Task.Delay(TimeSpan.FromSeconds(3)); + + TouchFixService.ApplyFixAsync(); + + ExportUIItems(); + } + + private void ExportUIItems() + { + var manager = Container.Current.Get(); + manager.AppendWithLang(new ExportInkMenuItem(), + new UIItemAttribute(UIItemPurposes.BoardEditMenu), new[] + { + new UIItemLangInfo(new CultureInfo("zh-CHS"), "导出墨迹"), + new UIItemLangInfo(new CultureInfo("en"), "Export Ink"), + }); + manager.AppendWithLang(new BetterEN5SettingsMenuItem(), + new UIItemAttribute(UIItemPurposes.BoardEditMenu), new[] + { + new UIItemLangInfo(new CultureInfo("zh-CHS"), "Better-Seewo 设置"), + new UIItemLangInfo(new CultureInfo("en"), "Better-Seewo Settings"), + }); + } + } +} diff --git a/Better-EN5/Services/ConversionService.cs b/Better-EN5/Services/ConversionService.cs new file mode 100644 index 0000000..092d82e --- /dev/null +++ b/Better-EN5/Services/ConversionService.cs @@ -0,0 +1,99 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +namespace BetterEN5.Services +{ + public class ConversionService : IConversionService + { + public async Task ConvertPptxToEnbxAsync(string pptxPath) + { + if (!File.Exists(pptxPath)) + throw new FileNotFoundException("PPT 文件未找到", pptxPath); + + var outputPath = Path.ChangeExtension(pptxPath, ".enbx"); + + var converterProcess = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), + "Seewo", "EasiNote5", "EasiNote5_5.2.4.9855", "Main", + "EasiNote.OfficeDocumentConverter.exe"); + + if (File.Exists(converterProcess)) + { + var psi = new ProcessStartInfo + { + FileName = converterProcess, + Arguments = $"\"{pptxPath}\" \"{outputPath}\"", + UseShellExecute = false, + CreateNoWindow = true, + }; + using var proc = Process.Start(psi); + if (proc != null) + { + await proc.WaitForExitAsync(); + } + } + else + { + throw new FileNotFoundException("转换器未找到,请确认希沃白板安装完整", converterProcess); + } + + return outputPath; + } + + public async Task ConvertEnbxToPptxAsync(string enbxPath) + { + if (!File.Exists(enbxPath)) + throw new FileNotFoundException("ENBX 文件未找到", enbxPath); + + var outputPath = Path.ChangeExtension(enbxPath, ".pptx"); + + var exporterProcess = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), + "Seewo", "EasiNote5", "EasiNote5_5.2.4.9855", "Main", + "EasiNote.OfficeDocumentConverter.exe"); + + if (File.Exists(exporterProcess)) + { + var psi = new ProcessStartInfo + { + FileName = exporterProcess, + Arguments = $"\"{enbxPath}\" \"{outputPath}\" /export", + UseShellExecute = false, + CreateNoWindow = true, + }; + using var proc = Process.Start(psi); + if (proc != null) + { + await proc.WaitForExitAsync(); + } + } + else + { + throw new FileNotFoundException("转换器未找到,请确认希沃白板安装完整", exporterProcess); + } + + return outputPath; + } + + public async Task FixConversionErrorsAsync() + { + await Task.Run(() => + { + var en5Path = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Seewo", "EasiNote5"); + + if (Directory.Exists(en5Path)) + { + var tempDir = Path.Combine(en5Path, "Temp", "Conversion"); + if (Directory.Exists(tempDir)) + { + try { Directory.Delete(tempDir, true); } catch { } + } + } + }); + } + } +} diff --git a/Better-EN5/Services/IConversionService.cs b/Better-EN5/Services/IConversionService.cs new file mode 100644 index 0000000..206e154 --- /dev/null +++ b/Better-EN5/Services/IConversionService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace BetterEN5.Services +{ + public interface IConversionService + { + Task ConvertPptxToEnbxAsync(string pptxPath); + Task ConvertEnbxToPptxAsync(string enbxPath); + Task FixConversionErrorsAsync(); + } +} diff --git a/Better-EN5/Services/IInkService.cs b/Better-EN5/Services/IInkService.cs new file mode 100644 index 0000000..04f6974 --- /dev/null +++ b/Better-EN5/Services/IInkService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace BetterEN5.Services +{ + public interface IInkService + { + Task ExportInkAsync(string filePath); + Task ImportInkAsync(string filePath); + } +} diff --git a/Better-EN5/Services/ITouchFixService.cs b/Better-EN5/Services/ITouchFixService.cs new file mode 100644 index 0000000..9e66f5d --- /dev/null +++ b/Better-EN5/Services/ITouchFixService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace BetterEN5.Services +{ + public interface ITouchFixService + { + Task ApplyFixAsync(); + bool IsTouchWorkingCorrectly(); + Task ForceTouchEnableAsync(); + } +} diff --git a/Better-EN5/Services/InkService.cs b/Better-EN5/Services/InkService.cs new file mode 100644 index 0000000..4a1fd7d --- /dev/null +++ b/Better-EN5/Services/InkService.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace BetterEN5.Services +{ + public class InkService : IInkService + { + public async Task ExportInkAsync(string filePath) + { + await Task.Run(() => + { + dynamic? board = GetENProperty("CurrentBoardApi"); + if (board == null) return; + + dynamic? slides = board.Slides; + if (slides == null || slides.Count == 0) return; + + var inkData = new InkPackage(); + foreach (var slide in slides) + { + try + { + var remarker = slide.GetType().GetMethod("GetRemarkProvider")?.Invoke(slide, null); + if (remarker != null) + { + var remarks = remarker.GetType().GetMethod("GetAllRemarks")?.Invoke(remarker, null); + if (remarks != null) + { + inkData.Slides.Add(new SlideInkData + { + SlideId = slide.Id, + Remarks = remarks + }); + } + } + } + catch { } + } + + var dir = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + var json = System.Text.Json.JsonSerializer.Serialize(inkData); + File.WriteAllText(filePath, json); + }); + } + + public async Task ImportInkAsync(string filePath) + { + await Task.Run(() => + { + if (!File.Exists(filePath)) return; + + var json = File.ReadAllText(filePath); + var inkData = System.Text.Json.JsonSerializer.Deserialize(json); + if (inkData == null) return; + + dynamic? board = GetENProperty("CurrentBoardApi"); + if (board == null) return; + + foreach (var slideData in inkData.Slides) + { + try + { + var slide = board.GetType().GetMethod("FindSlideById")?.Invoke(board, new[] { slideData.SlideId }); + if (slide == null) continue; + + var remarker = slide.GetType().GetMethod("GetRemarkProvider")?.Invoke(slide, null); + if (remarker != null) + { + remarker.GetType().GetMethod("LoadRemarks")?.Invoke(remarker, new[] { slideData.Remarks }); + } + } + catch { } + } + }); + } + + private static dynamic? GetENProperty(string propertyName) + { + try + { + var enType = Type.GetType("Cvte.EasiNote.EN, Cvte.EasiUI", false); + if (enType == null) + { + var asm = typeof(Cvte.EasiNote.UIItem).Assembly; + enType = asm.GetType("Cvte.EasiNote.EN"); + } + return enType?.GetProperty(propertyName)?.GetValue(null); + } + catch + { + return null; + } + } + } + + public class InkPackage + { + public List Slides { get; set; } = new(); + } + + public class SlideInkData + { + public string SlideId { get; set; } = string.Empty; + public object Remarks { get; set; } = new(); + } +} diff --git a/Better-EN5/Services/TouchFixService.cs b/Better-EN5/Services/TouchFixService.cs new file mode 100644 index 0000000..bf17d9d --- /dev/null +++ b/Better-EN5/Services/TouchFixService.cs @@ -0,0 +1,185 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +namespace BetterEN5.Services +{ + public static class TouchFixService + { + private static bool _fixApplied = false; + + public static async Task ApplyFixAsync() + { + if (_fixApplied) return; + + try + { + await Task.Run(() => + { + ForceIWBMode(); + PatchTouchConfig(); + SuppressTouchErrors(); + + _fixApplied = true; + }); + } + catch (Exception ex) + { + Debug.WriteLine($"[Better-EN5] TouchFix failed: {ex.Message}"); + } + } + + public static bool IsTouchWorkingCorrectly() + { + try + { + return TryDetectIWB(); + } + catch + { + return false; + } + } + + public static async Task ForceTouchEnableAsync() + { + return await Task.Run(() => + { + try + { + ForceIWBMode(); + return true; + } + catch + { + return false; + } + }); + } + + private static bool TryDetectIWB() + { + try + { + using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Seewo\EasiNote5"); + if (key != null) + { + var val = key.GetValue("ForceIWB"); + if (val != null && val.ToString() == "1") + return true; + } + } + catch { } + + try + { + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var configFile = Path.Combine(localAppData, "Seewo", "EasiNote5", "Config", "IWBConfig.json"); + if (File.Exists(configFile)) + { + var json = File.ReadAllText(configFile); + if (json.Contains("\"ForceIWB\": true") || json.Contains("\"ForceIWB\":true")) + return true; + } + } + catch { } + + return false; + } + + private static void ForceIWBMode() + { + try + { + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var configDir = Path.Combine(localAppData, "Seewo", "EasiNote5", "Config"); + var configFile = Path.Combine(configDir, "IWBConfig.json"); + + if (!Directory.Exists(configDir)) + Directory.CreateDirectory(configDir); + + var config = new + { + ForceIWB = true, + TouchDriverType = "WindowsTouch", + SkipTouchCheck = true, + EnableMultiTouch = true, + LastFixTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + }; + + var json = System.Text.Json.JsonSerializer.Serialize(config, new System.Text.Json.JsonSerializerOptions + { + WriteIndented = true + }); + + File.WriteAllText(configFile, json); + + Microsoft.Win32.Registry.SetValue(@"HKEY_CURRENT_USER\SOFTWARE\Seewo\EasiNote5", "ForceIWB", 1, Microsoft.Win32.RegistryValueKind.DWord); + Microsoft.Win32.Registry.SetValue(@"HKEY_CURRENT_USER\SOFTWARE\Seewo\EasiNote5", "SkipTouchCheck", 1, Microsoft.Win32.RegistryValueKind.DWord); + Microsoft.Win32.Registry.SetValue(@"HKEY_CURRENT_USER\SOFTWARE\Seewo\EasiNote5", "EnableMultiTouch", 1, Microsoft.Win32.RegistryValueKind.DWord); + } + catch { } + } + + private static void PatchTouchConfig() + { + try + { + var configDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Seewo", "EasiNote5", "Config"); + + var cfgFile = Path.Combine(configDir, "TouchConfig.ini"); + if (!Directory.Exists(configDir)) + Directory.CreateDirectory(configDir); + + var lines = new[] + { + "[Touch]", + "Enable=1", + "Driver=Auto", + "ForceEnable=1", + "SuppressErrors=1", + "SkipDetection=1", + "FallbackToMouse=1", + "", + "[Calibration]", + "Enabled=0", + "", + "[MultiTouch]", + "MaxPoints=10", + "EnableGesture=1", + }; + + File.WriteAllLines(cfgFile, lines); + } + catch { } + } + + private static void SuppressTouchErrors() + { + try + { + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var logDir = Path.Combine(localAppData, "Seewo", "EasiNote5", "Logs", "Touch"); + + if (Directory.Exists(logDir)) + { + foreach (var logFile in Directory.GetFiles(logDir, "*.log")) + { + try { File.Delete(logFile); } catch { } + } + } + + var seewoRegPath = @"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Seewo\EasiNote5"; + try + { + Microsoft.Win32.Registry.SetValue(seewoRegPath, "SkipTouchCheck", "1"); + } + catch { } + } + catch { } + } + } +} diff --git a/Better-EN5/UI/BetterEN5SettingsMenuItem.cs b/Better-EN5/UI/BetterEN5SettingsMenuItem.cs new file mode 100644 index 0000000..9fd89bb --- /dev/null +++ b/Better-EN5/UI/BetterEN5SettingsMenuItem.cs @@ -0,0 +1,36 @@ +using System.Linq; +using System.Windows; +using Cvte.EasiNote; +using Cvte.Windows.Input; + +namespace BetterEN5.UI +{ + public class BetterEN5SettingsMenuItem : BoardEditMenuItem + { + public BetterEN5SettingsMenuItem() + { + SortHint = 999; + Command = new DelegateCommand(() => + { + Application.Current.Dispatcher.Invoke(() => + { + var existing = Application.Current.Windows.OfType().FirstOrDefault(); + if (existing != null) + { + existing.Activate(); + return; + } + + var enMainWindow = Application.Current.MainWindow; + var settingsWindow = new SettingsWindow + { + Owner = enMainWindow, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + settingsWindow.ShowDialog(); + }); + }); + Predicate = _ => true; + } + } +} diff --git a/Better-EN5/UI/ExportInkMenuItem.cs b/Better-EN5/UI/ExportInkMenuItem.cs new file mode 100644 index 0000000..419b891 --- /dev/null +++ b/Better-EN5/UI/ExportInkMenuItem.cs @@ -0,0 +1,32 @@ +using System.Linq; +using System.Windows; +using Microsoft.Win32; +using Cvte.EasiNote; +using Cvte.Windows.Input; +using BetterEN5.Services; + +namespace BetterEN5.UI +{ + public class ExportInkMenuItem : BoardEditMenuItem + { + public ExportInkMenuItem() + { + SortHint = 998; + Command = new DelegateCommand(() => + { + var dialog = new SaveFileDialog + { + Filter = "墨迹文件 (*.ink)|*.ink|JSON 文件 (*.json)|*.json", + DefaultExt = ".ink", + FileName = "课件墨迹.ink" + }; + if (dialog.ShowDialog() == true) + { + var inkService = new InkService(); + _ = inkService.ExportInkAsync(dialog.FileName); + } + }); + Predicate = _ => true; + } + } +} diff --git a/Better-EN5/UI/SettingsWindow.xaml b/Better-EN5/UI/SettingsWindow.xaml new file mode 100644 index 0000000..2676507 --- /dev/null +++ b/Better-EN5/UI/SettingsWindow.xaml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + 将当前课件中的所有墨迹(笔迹、标注)导出为 JSON 文件,方便备份和迁移。 + + +