v1.2.0: 重构为插件+独立Manager架构,横版UI,精确保留文本框边距的文档转换
This commit is contained in:
@@ -7,20 +7,26 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>BetterEN5</RootNamespace>
|
||||
<AssemblyName>Better-EN5</AssemblyName>
|
||||
<Version>1.1.2</Version>
|
||||
<AssemblyVersion>1.1.0.0</AssemblyVersion>
|
||||
<FileVersion>1.1.2.0</FileVersion>
|
||||
<Version>1.2.0</Version>
|
||||
<AssemblyVersion>1.2.0.0</AssemblyVersion>
|
||||
<FileVersion>1.2.0.0</FileVersion>
|
||||
<Authors>雾启工作室</Authors>
|
||||
<Author>雾启工作室</Author>
|
||||
<Company>雾启工作室</Company>
|
||||
<Product>Better-Seewo 增强插件</Product>
|
||||
<Description>雾生万象,启以为光 — 希沃白板功能增强插件</Description>
|
||||
<Product>Better-Seewo</Product>
|
||||
<Description>雾生万象,启以为光 — Better-Seewo 插件</Description>
|
||||
<UseEasiNote>all</UseEasiNote>
|
||||
<ENPackageName>BS-Installer</ENPackageName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dotnetCampus.EasiPlugin.Sdk" Version="2.1.1-alpha.3" />
|
||||
</ItemGroup>
|
||||
<Target Name="CopyManifest" AfterTargets="AfterBuild">
|
||||
<Target Name="BuildManager" BeforeTargets="BeforeBuild">
|
||||
<MSBuild Projects="..\Manager\Manager.csproj" Targets="Build" Properties="Configuration=$(Configuration)" />
|
||||
</Target>
|
||||
<Target Name="CopyExtraFiles" AfterTargets="AfterBuild">
|
||||
<Copy SourceFiles="manifest.coin" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" />
|
||||
<Copy SourceFiles="..\README.md" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" />
|
||||
<Copy SourceFiles="..\Manager\bin\$(Configuration)\net6.0-windows\Better-Seewo.Manager.exe" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
85
Better-EN5/InkAutoSaveService.cs
Normal file
85
Better-EN5/InkAutoSaveService.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using BetterEN5.Services;
|
||||
|
||||
namespace BetterEN5
|
||||
{
|
||||
public class InkAutoSaveService
|
||||
{
|
||||
private static readonly string SettingsPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"BetterEN5", "ink-settings.json");
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = LoadSettings();
|
||||
if (settings != null && settings.AutoSaveEnabled)
|
||||
{
|
||||
var dir = settings.SaveDirectory;
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
|
||||
var filePath = Path.Combine(dir, $"ink-auto-{timestamp}.ink.json");
|
||||
var inkService = new InkService();
|
||||
await inkService.ExportInkAsync(filePath);
|
||||
|
||||
var retentionMinutes = settings.AutoSaveIntervalMinutes * 2;
|
||||
CleanOldFiles(dir, TimeSpan.FromMinutes(retentionMinutes > 0 ? retentionMinutes : 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
var intervalMinutes = LoadSettings()?.AutoSaveIntervalMinutes ?? 5;
|
||||
await Task.Delay(TimeSpan.FromMinutes(Math.Max(1, intervalMinutes)));
|
||||
}
|
||||
}
|
||||
|
||||
private static InkAutoSaveSettings? LoadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(SettingsPath))
|
||||
{
|
||||
var json = File.ReadAllText(SettingsPath);
|
||||
return JsonSerializer.Deserialize<InkAutoSaveSettings>(json);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void CleanOldFiles(string dir, TimeSpan maxAge)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(dir)) return;
|
||||
foreach (var file in Directory.GetFiles(dir, "ink-auto-*.ink.json"))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (DateTime.Now - File.GetCreationTime(file) > maxAge)
|
||||
File.Delete(file);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public class InkAutoSaveSettings
|
||||
{
|
||||
public bool AutoSaveEnabled { get; set; }
|
||||
public int AutoSaveIntervalMinutes { get; set; } = 5;
|
||||
public string SaveDirectory { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -10,18 +10,16 @@ namespace BetterEN5
|
||||
{
|
||||
class Program : dotnetCampus.EasiPlugins.EasiPlugin
|
||||
{
|
||||
private InkAutoSaveService? _autoSaveService;
|
||||
|
||||
protected override Task OnRunningAsync()
|
||||
{
|
||||
if (!EN.CommandOptions.IsCloud)
|
||||
{
|
||||
if (EN.App.IsReady)
|
||||
{
|
||||
Run();
|
||||
}
|
||||
else
|
||||
{
|
||||
EN.App.Ready += App_Ready;
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -36,6 +34,7 @@ namespace BetterEN5
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(3));
|
||||
ExportUIItems();
|
||||
StartAutoSave();
|
||||
}
|
||||
|
||||
private void ExportUIItems()
|
||||
@@ -49,11 +48,18 @@ namespace BetterEN5
|
||||
new UIItemLangInfo(new CultureInfo("en"), "Export Ink"),
|
||||
});
|
||||
|
||||
manager.AppendWithLang(new BoardMenuImportInk(),
|
||||
new UIItemAttribute(UIItemPurposes.BoardEditMenu), new[]
|
||||
{
|
||||
new UIItemLangInfo(new CultureInfo("zh-CHS"), "导入墨迹"),
|
||||
new UIItemLangInfo(new CultureInfo("en"), "Import Ink"),
|
||||
});
|
||||
|
||||
manager.AppendWithLang(new BoardMenuSettings(),
|
||||
new UIItemAttribute(UIItemPurposes.BoardEditMenu), new[]
|
||||
{
|
||||
new UIItemLangInfo(new CultureInfo("zh-CHS"), "Better-Seewo"),
|
||||
new UIItemLangInfo(new CultureInfo("en"), "Better-Seewo"),
|
||||
new UIItemLangInfo(new CultureInfo("zh-CHS"), "Better-Seewo 管理"),
|
||||
new UIItemLangInfo(new CultureInfo("en"), "Better-Seewo Manager"),
|
||||
});
|
||||
|
||||
manager.AppendWithLang(new HeadToolBarSettings(),
|
||||
@@ -63,5 +69,11 @@ namespace BetterEN5
|
||||
new UIItemLangInfo(new CultureInfo("en"), "Better-Seewo"),
|
||||
});
|
||||
}
|
||||
|
||||
private void StartAutoSave()
|
||||
{
|
||||
_autoSaveService = new InkAutoSaveService();
|
||||
_ = _autoSaveService.StartAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
539
Better-EN5/Services/CustomConversionService.cs
Normal file
539
Better-EN5/Services/CustomConversionService.cs
Normal file
@@ -0,0 +1,539 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace BetterEN5.Services
|
||||
{
|
||||
public class CustomConversionService : IConversionService
|
||||
{
|
||||
private static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
||||
private static readonly XNamespace P = "http://schemas.openxmlformats.org/presentationml/2006/main";
|
||||
private static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
||||
private static readonly XNamespace Rel = "http://schemas.openxmlformats.org/package/2006/relationships";
|
||||
|
||||
public async Task<string> ConvertPptxToEnbxAsync(string pptxPath)
|
||||
{
|
||||
if (!File.Exists(pptxPath))
|
||||
throw new FileNotFoundException("PPT 文件未找到", pptxPath);
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var enbxPath = Path.ChangeExtension(pptxPath, ".enbx");
|
||||
using var pptx = System.IO.Packaging.Package.Open(pptxPath, FileMode.Open, FileAccess.Read);
|
||||
using var enbx = System.IO.Packaging.Package.Open(enbxPath, FileMode.Create, FileAccess.ReadWrite);
|
||||
|
||||
var pptxPresentation = GetPptxPresentation(pptx);
|
||||
var pptxSlideSize = GetSlideSize(pptxPresentation);
|
||||
var slideWidth = pptxSlideSize.Item1;
|
||||
var slideHeight = pptxSlideSize.Item2;
|
||||
|
||||
var enbxDoc = new XDocument();
|
||||
var board = new XElement("DocumentStorageModel");
|
||||
|
||||
var slideIds = new List<string>();
|
||||
var slidesElement = new XElement("Slides");
|
||||
|
||||
var slideRels = GetPresentationSlideRels(pptx, pptxPresentation);
|
||||
for (int i = 0; i < slideRels.Count; i++)
|
||||
{
|
||||
var slideId = $"slide_{i + 1}";
|
||||
slideIds.Add(slideId);
|
||||
|
||||
var slidePart = pptx.GetPart(
|
||||
System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri(slideRels[i].Item2, UriKind.Relative)));
|
||||
var slideXml = XDocument.Load(slidePart.GetStream());
|
||||
|
||||
var slideElement = new XElement("SlideSaveInfo",
|
||||
new XElement("Id", slideId),
|
||||
new XElement("Width", slideWidth),
|
||||
new XElement("Height", slideHeight));
|
||||
|
||||
var elementsElement = new XElement("Elements");
|
||||
var spTree = slideXml.Root?.Element(P + "cSld")?.Element(P + "spTree");
|
||||
if (spTree != null)
|
||||
{
|
||||
int elemIdx = 0;
|
||||
foreach (var sp in spTree.Elements(P + "sp"))
|
||||
{
|
||||
var xfrm = sp.Element(P + "spPr")?.Element(A + "xfrm");
|
||||
if (xfrm == null) continue;
|
||||
|
||||
var off = xfrm.Element(A + "off");
|
||||
var ext = xfrm.Element(A + "ext");
|
||||
if (off == null || ext == null) continue;
|
||||
|
||||
var x = (double)off.Attribute("x");
|
||||
var y = (double)off.Attribute("y");
|
||||
var w = (double)ext.Attribute("cx");
|
||||
var h = (double)ext.Attribute("cy");
|
||||
|
||||
var nvSpPr = sp.Element(P + "nvSpPr");
|
||||
var cNvSpPr = nvSpPr?.Element(P + "cNvSpPr");
|
||||
var isTextBox = cNvSpPr?.Attribute("txBox") != null;
|
||||
var txBody = sp.Element(P + "txBody");
|
||||
var isTextShape = txBody != null;
|
||||
|
||||
if (isTextBox || isTextShape)
|
||||
{
|
||||
var elem = new XElement("TextSaveInfo",
|
||||
new XElement("Id", $"elem_{++elemIdx}"),
|
||||
new XElement("X", x),
|
||||
new XElement("Y", y),
|
||||
new XElement("Width", w),
|
||||
new XElement("Height", h),
|
||||
new XElement("Rotation", 0.0),
|
||||
new XElement("Locked", false));
|
||||
|
||||
var bodyPr = txBody?.Element(A + "bodyPr");
|
||||
var lIns = (double?)bodyPr?.Attribute("lIns") ?? 91440;
|
||||
var rIns = (double?)bodyPr?.Attribute("rIns") ?? 91440;
|
||||
var tIns = (double?)bodyPr?.Attribute("tIns") ?? 91440;
|
||||
var bIns = (double?)bodyPr?.Attribute("bIns") ?? 91440;
|
||||
|
||||
var richText = new XElement("RichText",
|
||||
new XElement("LeftMargin", lIns),
|
||||
new XElement("RightMargin", rIns),
|
||||
new XElement("TopMargin", tIns),
|
||||
new XElement("BottomMargin", bIns));
|
||||
|
||||
if (txBody != null)
|
||||
{
|
||||
foreach (var para in txBody.Elements(A + "p"))
|
||||
{
|
||||
var pElem = new XElement("Paragraph");
|
||||
foreach (var run in para.Elements(A + "r"))
|
||||
{
|
||||
var rPr = run.Element(A + "rPr");
|
||||
var text = run.Element(A + "t")?.Value ?? "";
|
||||
|
||||
var rElem = new XElement("Run",
|
||||
new XElement("Text", text));
|
||||
if (rPr != null)
|
||||
{
|
||||
var fmt = new XElement("Formatting");
|
||||
var sz = rPr.Attribute("sz");
|
||||
if (sz != null) fmt.Add(new XElement("Size", (double)sz / 100.0));
|
||||
var b = rPr.Attribute("b");
|
||||
if (b != null) fmt.Add(new XElement("Bold", (string)b == "1"));
|
||||
var italicAttr = rPr.Attribute("i");
|
||||
if (italicAttr != null) fmt.Add(new XElement("Italic", (string)italicAttr == "1"));
|
||||
var u = rPr.Attribute("u");
|
||||
if (u != null) fmt.Add(new XElement("Underline", u.Value));
|
||||
var srgbClr = rPr?.Element(A + "solidFill")?.Element(A + "srgbClr");
|
||||
if (srgbClr != null) fmt.Add(new XElement("Color", (string)srgbClr.Attribute("val")));
|
||||
var latin = rPr?.Element(A + "latin");
|
||||
if (latin != null) fmt.Add(new XElement("Font", (string)latin.Attribute("typeface")));
|
||||
if (fmt.HasElements) rElem.Add(fmt);
|
||||
}
|
||||
pElem.Add(rElem);
|
||||
}
|
||||
richText.Add(pElem);
|
||||
}
|
||||
}
|
||||
|
||||
elem.Add(richText);
|
||||
elementsElement.Add(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slideElement.Add(elementsElement);
|
||||
slidesElement.Add(slideElement);
|
||||
}
|
||||
|
||||
board.Add(new XElement("Board",
|
||||
new XElement("SlideWidth", slideWidth),
|
||||
new XElement("SlideHeight", slideHeight),
|
||||
new XElement("SlideIds",
|
||||
slideIds.Select(id => new XElement("string", id)))));
|
||||
board.Add(slidesElement);
|
||||
enbxDoc.Add(board);
|
||||
|
||||
var enbxPart = enbx.CreatePart(
|
||||
System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)),
|
||||
"application/xml");
|
||||
using (var stream = enbxPart.GetStream())
|
||||
enbxDoc.Save(stream);
|
||||
|
||||
enbx.Close();
|
||||
pptx.Close();
|
||||
|
||||
return enbxPath;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<string> ConvertEnbxToPptxAsync(string enbxPath)
|
||||
{
|
||||
if (!File.Exists(enbxPath))
|
||||
throw new FileNotFoundException("ENBX 文件未找到", enbxPath);
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var pptxPath = Path.ChangeExtension(enbxPath, ".pptx");
|
||||
|
||||
using var enbx = System.IO.Packaging.Package.Open(enbxPath, FileMode.Open, FileAccess.Read);
|
||||
using var pptx = System.IO.Packaging.Package.Open(pptxPath, FileMode.Create, FileAccess.ReadWrite);
|
||||
|
||||
var enbxDocUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative));
|
||||
var enbxPart = enbx.GetPart(enbxDocUri);
|
||||
var enbxDoc = XDocument.Load(enbxPart.GetStream());
|
||||
|
||||
var root = enbxDoc.Root;
|
||||
if (root == null) throw new InvalidDataException("无效的 ENBX 文件");
|
||||
|
||||
var board = root.Element("Board");
|
||||
var slideWidth = (double?)board?.Element("SlideWidth") ?? 12192000;
|
||||
var slideHeight = (double?)board?.Element("SlideHeight") ?? 6858000;
|
||||
|
||||
var slidesEl = root.Element("Slides");
|
||||
var slides = slidesEl?.Elements("SlideSaveInfo").ToList() ?? new List<XElement>();
|
||||
|
||||
var contentTypesUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/[Content_Types].xml", UriKind.Relative));
|
||||
var ctPart = pptx.CreatePart(contentTypesUri, "application/xml");
|
||||
using (var sw = new StreamWriter(ctPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">");
|
||||
sw.Write("<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>");
|
||||
sw.Write("<Default Extension=\"xml\" ContentType=\"application/xml\"/>");
|
||||
sw.Write("<Default Extension=\"png\" ContentType=\"image/png\"/>");
|
||||
sw.Write("<Default Extension=\"jpeg\" ContentType=\"image/jpeg\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/presentation.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/slideMasters/slideMaster1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/slideLayouts/slideLayout1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml\"/>");
|
||||
for (int i = 0; i < slides.Count; i++)
|
||||
sw.Write($"<Override PartName=\"/ppt/slides/slide{i + 1}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slide+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/presProps.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.presProps+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/viewProps.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/theme/theme1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.theme+xml\"/>");
|
||||
sw.Write("</Types>");
|
||||
}
|
||||
|
||||
var pptxPresentation = new XDocument(new XElement(P + "presentation",
|
||||
new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName),
|
||||
new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName),
|
||||
new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName),
|
||||
new XElement(P + "sldMasterIdLst",
|
||||
new XElement(P + "sldMasterId", new XAttribute("id", 2147483648), new XAttribute(R + "id", "rId1"))),
|
||||
new XElement(P + "sldIdLst"),
|
||||
new XElement(P + "sldSz", new XAttribute("cx", slideWidth), new XAttribute("cy", slideHeight)),
|
||||
new XElement(P + "notesSz", new XAttribute("cx", 6858000), new XAttribute("cy", 9144000))));
|
||||
|
||||
var presXmlUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/presentation.xml", UriKind.Relative));
|
||||
var presPart = pptx.CreatePart(presXmlUri, "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml");
|
||||
var presRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative));
|
||||
var presRelPart = pptx.CreatePart(presRelUri, "application/vnd.openxmlformats-package.relationships+xml");
|
||||
|
||||
var presRelNs = XNamespace.Get(Rel.NamespaceName);
|
||||
var presRels = new XDocument(new XElement(presRelNs + "Relationships",
|
||||
new XElement(presRelNs + "Relationship", new XAttribute("Id", "rId1"),
|
||||
new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"),
|
||||
new XAttribute("Target", "slideMasters/slideMaster1.xml"))));
|
||||
|
||||
var slideIdLst = pptxPresentation.Root?.Element(P + "sldIdLst");
|
||||
var nextRelId = 2;
|
||||
var slideRelTargets = new List<string>();
|
||||
|
||||
for (int i = 0; i < slides.Count; i++)
|
||||
{
|
||||
var slideId = $"slide_{i + 1}";
|
||||
var slideFileName = $"slides/slide{i + 1}.xml";
|
||||
slideRelTargets.Add(slideFileName);
|
||||
|
||||
slideIdLst?.Add(new XElement(P + "sldId",
|
||||
new XAttribute("id", 256 + i),
|
||||
new XAttribute(R + "id", $"rId{nextRelId + i}")));
|
||||
|
||||
presRels.Root?.Add(new XElement("Relationship",
|
||||
new XAttribute("Id", $"rId{nextRelId + i}"),
|
||||
new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"),
|
||||
new XAttribute("Target", slideFileName)));
|
||||
|
||||
var slideDoc = new XDocument(new XElement(P + "sld",
|
||||
new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName),
|
||||
new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName),
|
||||
new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName)));
|
||||
|
||||
var cSld = new XElement(P + "cSld");
|
||||
var spTree = new XElement(P + "spTree");
|
||||
|
||||
spTree.Add(new XElement(P + "nvGrpSpPr",
|
||||
new XElement(P + "cNvPr", new XAttribute("id", 1), new XAttribute("name", "")),
|
||||
new XElement(P + "cNvGrpSpPr")));
|
||||
spTree.Add(new XElement(P + "grpSpPr",
|
||||
new XElement(A + "xfrm",
|
||||
new XElement(A + "off", new XAttribute("x", 0), new XAttribute("y", 0)),
|
||||
new XElement(A + "ext", new XAttribute("cx", 0), new XAttribute("cy", 0)),
|
||||
new XElement(A + "chOff", new XAttribute("x", 0), new XAttribute("y", 0)),
|
||||
new XElement(A + "chExt", new XAttribute("cx", 0), new XAttribute("cy", 0)))));
|
||||
|
||||
var elements = slides[i].Element("Elements");
|
||||
if (elements != null)
|
||||
{
|
||||
int shapeId = 2;
|
||||
foreach (var elem in elements.Elements())
|
||||
{
|
||||
var elemName = elem.Name.LocalName;
|
||||
var x = (double)elem.Element("X");
|
||||
var y = (double)elem.Element("Y");
|
||||
var w = (double)elem.Element("Width");
|
||||
var h = (double)elem.Element("Height");
|
||||
var rotation = (double?)elem.Element("Rotation") ?? 0.0;
|
||||
|
||||
if (elemName == "TextSaveInfo")
|
||||
{
|
||||
var sp = new XElement(P + "sp");
|
||||
sp.Add(new XElement(P + "nvSpPr",
|
||||
new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", $"TextBox{shapeId - 1}")),
|
||||
new XElement(P + "cNvSpPr", new XAttribute("txBox", 1)),
|
||||
new XElement(P + "nvPr")));
|
||||
|
||||
var spPr = new XElement(P + "spPr",
|
||||
new XElement(A + "xfrm",
|
||||
new XElement(A + "off", new XAttribute("x", x), new XAttribute("y", y)),
|
||||
new XElement(A + "ext", new XAttribute("cx", w), new XAttribute("cy", h))),
|
||||
new XElement(A + "prstGeom", new XAttribute("prst", "rect"),
|
||||
new XElement(A + "avLst")));
|
||||
sp.Add(spPr);
|
||||
|
||||
var txBody = new XElement(P + "txBody",
|
||||
new XElement(A + "bodyPr",
|
||||
new XAttribute("wrap", "square")));
|
||||
|
||||
var richText = elem.Element("RichText");
|
||||
if (richText != null)
|
||||
{
|
||||
var leftMargin = (double?)richText.Element("LeftMargin") ?? 91440;
|
||||
var rightMargin = (double?)richText.Element("RightMargin") ?? 91440;
|
||||
var topMargin = (double?)richText.Element("TopMargin") ?? 91440;
|
||||
var bottomMargin = (double?)richText.Element("BottomMargin") ?? 91440;
|
||||
|
||||
var bodyPr = txBody.Element(A + "bodyPr");
|
||||
if (bodyPr != null)
|
||||
{
|
||||
bodyPr.Add(new XAttribute("lIns", leftMargin));
|
||||
bodyPr.Add(new XAttribute("rIns", rightMargin));
|
||||
bodyPr.Add(new XAttribute("tIns", topMargin));
|
||||
bodyPr.Add(new XAttribute("bIns", bottomMargin));
|
||||
}
|
||||
|
||||
var paragraphs = richText.Elements("Paragraph");
|
||||
if (!paragraphs.Any())
|
||||
{
|
||||
txBody.Add(new XElement(A + "p"));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var para in paragraphs)
|
||||
{
|
||||
var pElem = new XElement(A + "p");
|
||||
var runs = para.Elements("Run");
|
||||
if (!runs.Any())
|
||||
{
|
||||
pElem.Add(new XElement(A + "endParaRPr"));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var run in runs)
|
||||
{
|
||||
var text = (string)run.Element("Text") ?? "";
|
||||
var rElem = new XElement(A + "r");
|
||||
var fmt = run.Element("Formatting");
|
||||
if (fmt != null)
|
||||
{
|
||||
var rPr = new XElement(A + "rPr");
|
||||
var sz = fmt.Element("Size");
|
||||
if (sz != null) rPr.Add(new XAttribute("sz", (double)sz * 100));
|
||||
var bold = fmt.Element("Bold");
|
||||
if (bold != null && (bool)bold) rPr.Add(new XAttribute("b", 1));
|
||||
var italic = fmt.Element("Italic");
|
||||
if (italic != null && (bool)italic) rPr.Add(new XAttribute("i", 1));
|
||||
var underline = (string?)fmt.Element("Underline");
|
||||
if (underline != null) rPr.Add(new XAttribute("u", underline));
|
||||
var color = (string?)fmt.Element("Color");
|
||||
if (color != null)
|
||||
rPr.Add(new XElement(A + "solidFill", new XElement(A + "srgbClr", new XAttribute("val", color))));
|
||||
var font = (string?)fmt.Element("Font");
|
||||
if (font != null)
|
||||
rPr.Add(new XElement(A + "latin", new XAttribute("typeface", font)));
|
||||
if (rPr.HasAttributes || rPr.HasElements) rElem.Add(rPr);
|
||||
}
|
||||
rElem.Add(new XElement(A + "t", text));
|
||||
pElem.Add(rElem);
|
||||
}
|
||||
}
|
||||
txBody.Add(pElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
txBody.Add(new XElement(A + "p"));
|
||||
}
|
||||
|
||||
sp.Add(txBody);
|
||||
spTree.Add(sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cSld.Add(spTree);
|
||||
slideDoc.Root?.Add(cSld);
|
||||
|
||||
var slideUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri($"/ppt/{slideFileName}", UriKind.Relative));
|
||||
var slidePart = pptx.CreatePart(slideUri, "application/vnd.openxmlformats-officedocument.presentationml.slide+xml");
|
||||
using (var sw = new StreamWriter(slidePart.GetStream()))
|
||||
slideDoc.Save(sw);
|
||||
|
||||
var slideRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri($"/ppt/slides/_rels/slide{i + 1}.xml.rels", UriKind.Relative));
|
||||
var slideRelPart = pptx.CreatePart(slideRelUri, "application/vnd.openxmlformats-package.relationships+xml");
|
||||
using (var sw = new StreamWriter(slideRelPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">");
|
||||
sw.Write("<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout\" Target=\"../slideLayouts/slideLayout1.xml\"/>");
|
||||
sw.Write("</Relationships>");
|
||||
}
|
||||
}
|
||||
|
||||
var rootRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative));
|
||||
var rootRelPart = pptx.CreatePart(rootRelUri, "application/vnd.openxmlformats-package.relationships+xml");
|
||||
using (var sw = new StreamWriter(rootRelPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">");
|
||||
sw.Write("<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"ppt/presentation.xml\"/>");
|
||||
sw.Write("</Relationships>");
|
||||
}
|
||||
|
||||
using (var sw = new StreamWriter(presPart.GetStream()))
|
||||
pptxPresentation.Save(sw);
|
||||
using (var sw = new StreamWriter(presRelPart.GetStream()))
|
||||
presRels.Save(sw);
|
||||
|
||||
CreateMinimalTheme(pptx);
|
||||
CreateMinimalSlideMaster(pptx);
|
||||
CreateMinimalSlideLayout(pptx);
|
||||
CreatePresProps(pptx);
|
||||
CreateViewProps(pptx);
|
||||
|
||||
enbx.Close();
|
||||
pptx.Close();
|
||||
|
||||
return pptxPath;
|
||||
});
|
||||
}
|
||||
|
||||
public Task FixConversionErrorsAsync()
|
||||
{
|
||||
var en5Path = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Seewo", "EasiNote5");
|
||||
var tempDir = Path.Combine(en5Path, "Temp", "Conversion");
|
||||
if (Directory.Exists(tempDir))
|
||||
{
|
||||
try { Directory.Delete(tempDir, true); } catch { }
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Tuple<double, double> GetSlideSize(XDocument presentation)
|
||||
{
|
||||
var sldSz = presentation.Root?.Element(P + "sldSz");
|
||||
if (sldSz == null) return Tuple.Create(12192000.0, 6858000.0);
|
||||
return Tuple.Create(
|
||||
(double)sldSz.Attribute("cx"),
|
||||
(double)sldSz.Attribute("cy"));
|
||||
}
|
||||
|
||||
private static XDocument GetPptxPresentation(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var relsUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative));
|
||||
var relsPart = pptx.GetPart(relsUri);
|
||||
var rels = XDocument.Load(relsPart.GetStream());
|
||||
var officeRel = rels.Root?.Elements("Relationship")
|
||||
.FirstOrDefault(r => (string)r.Attribute("Type") ==
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
|
||||
if (officeRel == null) throw new InvalidDataException("未找到 Office Document 关系");
|
||||
var presUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri((string)officeRel.Attribute("Target"), UriKind.Relative));
|
||||
var presPart = pptx.GetPart(presUri);
|
||||
return XDocument.Load(presPart.GetStream());
|
||||
}
|
||||
|
||||
private static List<Tuple<string, string>> GetPresentationSlideRels(System.IO.Packaging.Package pptx, XDocument presentation)
|
||||
{
|
||||
var relsUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative));
|
||||
var relsPart = pptx.GetPart(relsUri);
|
||||
var rels = XDocument.Load(relsPart.GetStream());
|
||||
var slideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
|
||||
return rels.Root?.Elements("Relationship")
|
||||
.Where(r => (string)r.Attribute("Type") == slideType)
|
||||
.Select(r => Tuple.Create((string)r.Attribute("Id"), (string)r.Attribute("Target")))
|
||||
.ToList() ?? new List<Tuple<string, string>>();
|
||||
}
|
||||
|
||||
private static void CreateMinimalTheme(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var themeUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/theme/theme1.xml", UriKind.Relative));
|
||||
var themePart = pptx.CreatePart(themeUri, "application/vnd.openxmlformats-officedocument.theme+xml");
|
||||
using (var sw = new StreamWriter(themePart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><a:theme xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" name=\"Default\">");
|
||||
sw.Write("<a:themeElements>");
|
||||
sw.Write("<a:clrScheme name=\"Default\"><a:dk1><a:srgbClr val=\"000000\"/></a:dk1><a:lt1><a:srgbClr val=\"FFFFFF\"/></a:lt1><a:dk2><a:srgbClr val=\"1F1F1F\"/></a:dk2><a:lt2><a:srgbClr val=\"FFFFFF\"/></a:lt2><a:accent1><a:srgbClr val=\"4472C4\"/></a:accent1><a:accent2><a:srgbClr val=\"ED7D31\"/></a:accent2><a:accent3><a:srgbClr val=\"A5A5A5\"/></a:accent3><a:accent4><a:srgbClr val=\"FFC000\"/></a:accent4><a:accent5><a:srgbClr val=\"5B9BD5\"/></a:accent5><a:accent6><a:srgbClr val=\"70AD47\"/></a:accent6><a:hlink><a:srgbClr val=\"0563C1\"/></a:hlink><a:folHlink><a:srgbClr val=\"954F72\"/></a:folHlink></a:clrScheme>");
|
||||
sw.Write("<a:fontScheme name=\"Default\"><a:majorFont><a:latin typeface=\"Calibri Light\"/></a:majorFont><a:minorFont><a:latin typeface=\"Calibri\"/></a:minorFont></a:fontScheme>");
|
||||
sw.Write("<a:fmtScheme name=\"Default\"/>");
|
||||
sw.Write("</a:themeElements></a:theme>");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateMinimalSlideMaster(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var masterUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/slideMaster1.xml", UriKind.Relative));
|
||||
var masterPart = pptx.CreatePart(masterUri, "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml");
|
||||
using (var sw = new StreamWriter(masterPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><p:sldMaster xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\">");
|
||||
sw.Write("<p:cSld><p:spTree><p:nvGrpSpPr><p:nvPr><p:cNvPr id=\"1\"/><p:cNvGrpSpPr/></p:nvPr></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x=\"0\" y=\"0\"/><a:ext cx=\"0\" cy=\"0\"/><a:chOff x=\"0\" y=\"0\"/><a:chExt cx=\"0\" cy=\"0\"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>");
|
||||
sw.Write("<p:sldLayoutIdLst><p:sldLayoutId id=\"2147483649\" r:id=\"rId1\"/></p:sldLayoutIdLst></p:sldMaster>");
|
||||
}
|
||||
|
||||
var masterRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/_rels/slideMaster1.xml.rels", UriKind.Relative));
|
||||
var masterRelPart = pptx.CreatePart(masterRelUri, "application/vnd.openxmlformats-package.relationships+xml");
|
||||
using (var sw = new StreamWriter(masterRelPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">");
|
||||
sw.Write("<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout\" Target=\"../slideLayouts/slideLayout1.xml\"/>");
|
||||
sw.Write("</Relationships>");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateMinimalSlideLayout(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var layoutUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideLayouts/slideLayout1.xml", UriKind.Relative));
|
||||
var layoutPart = pptx.CreatePart(layoutUri, "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml");
|
||||
using (var sw = new StreamWriter(layoutPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><p:sldLayout xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\" type=\"blank\">");
|
||||
sw.Write("<p:cSld><p:spTree><p:nvGrpSpPr><p:nvPr><p:cNvPr id=\"1\"/><p:cNvGrpSpPr/></p:nvPr></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x=\"0\" y=\"0\"/><a:ext cx=\"0\" cy=\"0\"/><a:chOff x=\"0\" y=\"0\"/><a:chExt cx=\"0\" cy=\"0\"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld></p:sldLayout>");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreatePresProps(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/presProps.xml", UriKind.Relative));
|
||||
var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml");
|
||||
using (var sw = new StreamWriter(part.GetStream()))
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><p:presProps xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\"/>");
|
||||
}
|
||||
|
||||
private static void CreateViewProps(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/viewProps.xml", UriKind.Relative));
|
||||
var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml");
|
||||
using (var sw = new StreamWriter(part.GetStream()))
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><p:viewProps xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\"/>");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ namespace BetterEN5.Services
|
||||
var manifest = new PatchManifest
|
||||
{
|
||||
InstallTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
Version = "1.1.2"
|
||||
Version = "1.2.0"
|
||||
};
|
||||
|
||||
BackupFile(manifest,
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
<Window x:Class="BetterEN5.UI.BetterSeewoMainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Better-Seewo"
|
||||
Width="960" Height="580"
|
||||
MinWidth="800" MinHeight="480"
|
||||
WindowStyle="None" ResizeMode="CanResize"
|
||||
AllowsTransparency="True" Background="Transparent"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
FontSize="14">
|
||||
<Window.Resources>
|
||||
<Color x:Key="Bg">#0F0F1A</Color>
|
||||
<Color x:Key="CardBg">#1A1A2E</Color>
|
||||
<Color x:Key="Accent">#60B0FF</Color>
|
||||
<Color x:Key="Fg">#D0D0E0</Color>
|
||||
<Color x:Key="SubFg">#707090</Color>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="#25FFFFFF"/>
|
||||
</Window.Resources>
|
||||
|
||||
<Border CornerRadius="12" Background="#0F0F1A" BorderBrush="{StaticResource BorderBrush}" BorderThickness="1">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="24" ShadowDepth="4" Opacity="0.3" Color="Black"/>
|
||||
</Border.Effect>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="34"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="26"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title Bar -->
|
||||
<Border Grid.Row="0" Background="#1A1A2E" CornerRadius="12,12,0,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="12,0">
|
||||
<TextBlock Text="✦" FontSize="12" Foreground="#60B0FF" VerticalAlignment="Center"/>
|
||||
<TextBlock Text=" Better-Seewo" FontSize="13" Foreground="#C0C0D0" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Button x:Name="MinBtn" Content="─" Width="28" Height="22"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="{StaticResource SubFg}" FontSize="12"
|
||||
Cursor="Hand" Click="Minimize_Click"/>
|
||||
<Button x:Name="MaxBtn" Content="□" Width="28" Height="22"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="{StaticResource SubFg}" FontSize="12"
|
||||
Cursor="Hand" Click="Maximize_Click"/>
|
||||
<Button x:Name="CloseBtn" Content="✕" Width="28" Height="22"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="{StaticResource SubFg}" FontSize="12"
|
||||
Cursor="Hand" Click="Close_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Content -->
|
||||
<Grid Grid.Row="1" Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<Border Grid.Column="0" Background="#12121E">
|
||||
<StackPanel Margin="0,8">
|
||||
<TextBlock Text="功能模块" FontSize="11" Foreground="{StaticResource SubFg}"
|
||||
Margin="16,8,16,4"/>
|
||||
<RadioButton x:Name="NavInstall" GroupName="Nav" IsChecked="True"
|
||||
Content=" 安装管理" Foreground="{StaticResource Fg}"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Cursor="Hand" Height="36" Padding="16,0"
|
||||
Checked="NavChanged"/>
|
||||
<RadioButton x:Name="NavInk" GroupName="Nav"
|
||||
Content=" 墨迹管理" Foreground="{StaticResource Fg}"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Cursor="Hand" Height="36" Padding="16,0"
|
||||
Checked="NavChanged"/>
|
||||
<RadioButton x:Name="NavConvert" GroupName="Nav"
|
||||
Content=" 文件转换" Foreground="{StaticResource Fg}"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Cursor="Hand" Height="36" Padding="16,0"
|
||||
Checked="NavChanged"/>
|
||||
<RadioButton x:Name="NavTouch" GroupName="Nav"
|
||||
Content=" 触摸修复" Foreground="{StaticResource Fg}"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Cursor="Hand" Height="36" Padding="16,0"
|
||||
Checked="NavChanged"/>
|
||||
<RadioButton x:Name="NavActivate" GroupName="Nav"
|
||||
Content=" 激活" Foreground="{StaticResource Fg}"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Cursor="Hand" Height="36" Padding="16,0"
|
||||
Checked="NavChanged"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Divider -->
|
||||
<Rectangle Grid.Column="1" Fill="#252540" Width="1" Opacity="0.5"/>
|
||||
|
||||
<!-- Content Area -->
|
||||
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto"
|
||||
Background="#0D0D18" Padding="24,16">
|
||||
<StackPanel x:Name="ContentPanel">
|
||||
|
||||
<!-- Install Management -->
|
||||
<StackPanel x:Name="PanelInstall" Visibility="Visible">
|
||||
<TextBlock Text="Better-Seewo 安装管理" FontSize="20" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,8"/>
|
||||
<TextBlock x:Name="InstallStatusText" Text="正在检测状态..." FontSize="13"
|
||||
Foreground="{StaticResource SubFg}" Margin="0,0,0,16"/>
|
||||
<Button x:Name="InstallBtn" Content="安装补丁" Width="180" Height="34"
|
||||
Margin="0,0,0,8" Cursor="Hand" Click="InstallPatch_Click"/>
|
||||
<Button x:Name="UninstallBtn" Content="卸载补丁" Width="180" Height="34"
|
||||
Margin="0,0,0,8" Cursor="Hand" Click="UninstallPatch_Click"
|
||||
IsEnabled="False"/>
|
||||
<TextBlock x:Name="PatchDetailText" FontSize="12"
|
||||
Foreground="{StaticResource SubFg}" Margin="0,8,0,0"
|
||||
TextWrapping="Wrap"/>
|
||||
<TextBlock Text="" Margin="0,16,0,0"/>
|
||||
<TextBlock Text="说明" FontSize="16" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource Fg}" Margin="0,0,0,8"/>
|
||||
<TextBlock TextWrapping="Wrap" FontSize="12"
|
||||
Foreground="{StaticResource SubFg}">
|
||||
• 安装补丁:备份希沃白板关键配置文件,然后应用增强补丁。
|
||||
• 卸载补丁:从备份文件(.bak)还原原始配置,清除注册表修改。
|
||||
• 安装后请重启希沃白板使补丁生效。
|
||||
• 补丁不修改希沃白板主程序文件,仅修改配置和注册表。
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Ink Module -->
|
||||
<StackPanel x:Name="PanelInk" Visibility="Collapsed">
|
||||
<TextBlock Text="墨迹管理" FontSize="20" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,16"/>
|
||||
<TextBlock Text="导出/导入希沃白板课件中的笔迹和标注" FontSize="13"
|
||||
Foreground="{StaticResource SubFg}" Margin="0,0,0,16"/>
|
||||
<Button Content="导出墨迹..." Width="180" Height="34" Margin="0,0,0,8" Cursor="Hand" Click="ExportInk_Click"/>
|
||||
<Button Content="导入墨迹..." Width="180" Height="34" Margin="0,0,8,8" Cursor="Hand" Click="ImportInk_Click"/>
|
||||
<TextBlock x:Name="InkStatus" FontSize="12" Foreground="{StaticResource SubFg}" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Convert Module -->
|
||||
<StackPanel x:Name="PanelConvert" Visibility="Collapsed">
|
||||
<TextBlock Text="文件转换" FontSize="20" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,16"/>
|
||||
<TextBlock Text="PPT 与 ENBX 格式互转" FontSize="13"
|
||||
Foreground="{StaticResource SubFg}" Margin="0,0,0,16"/>
|
||||
<Button Content="PPT → ENBX" Width="180" Height="34" Margin="0,0,0,8" Cursor="Hand" Click="ConvertPptx_Click"/>
|
||||
<Button Content="ENBX → PPT" Width="180" Height="34" Margin="0,0,0,8" Cursor="Hand" Click="ConvertEnbx_Click"/>
|
||||
<TextBlock x:Name="ConvertStatus" FontSize="12" Foreground="{StaticResource SubFg}" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Touch Module -->
|
||||
<StackPanel x:Name="PanelTouch" Visibility="Collapsed">
|
||||
<TextBlock Text="触摸修复" FontSize="20" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,16"/>
|
||||
<TextBlock Text="修复希沃白板在非希沃硬件上的触摸检测问题" FontSize="13"
|
||||
Foreground="{StaticResource SubFg}" Margin="0,0,0,16"/>
|
||||
<Button Content="检测触摸状态" Width="180" Height="34" Margin="0,0,0,8" Cursor="Hand" Click="CheckTouch_Click"/>
|
||||
<Button Content="应用修复" Width="180" Height="34" Margin="0,0,0,8" Cursor="Hand" Click="ApplyTouchFix_Click"/>
|
||||
<TextBlock x:Name="TouchStatus" FontSize="12" Foreground="{StaticResource SubFg}" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Activate Module -->
|
||||
<StackPanel x:Name="PanelActivate" Visibility="Collapsed">
|
||||
<TextBlock Text="专业版激活" FontSize="20" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,16"/>
|
||||
<TextBlock x:Name="ActivationStatusText" Text="当前状态: 社区版" FontSize="13"
|
||||
Foreground="{StaticResource SubFg}" Margin="0,0,0,16"/>
|
||||
<Button Content="输入激活码..." Width="180" Height="34" Margin="0,0,0,8" Cursor="Hand" Click="ActivatePro_Click"/>
|
||||
<Button Content="打开注册补丁..." Width="180" Height="34" Margin="0,0,0,8" Cursor="Hand" Click="LaunchInstaller_Click"/>
|
||||
<Button Content="检测激活状态" Width="180" Height="34" Margin="0,0,0,8" Cursor="Hand" Click="CheckActivation_Click"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<!-- Footer -->
|
||||
<Border Grid.Row="2" Background="#1A1A2E" CornerRadius="0,0,12,12">
|
||||
<TextBlock Text="雾启工作室 × Macrohard Studio · 雾生万象,启以为光" FontSize="11"
|
||||
Foreground="#404058" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -1,243 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Win32;
|
||||
using BetterEN5.Services;
|
||||
|
||||
namespace BetterEN5.UI
|
||||
{
|
||||
public partial class BetterSeewoMainWindow : Window
|
||||
{
|
||||
private readonly InkService _inkService = new();
|
||||
private readonly ActivationService _activationService = new();
|
||||
private readonly PatchService _patchService = new();
|
||||
|
||||
public BetterSeewoMainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await RefreshInstallStatusAsync();
|
||||
}
|
||||
|
||||
private void NavChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender == NavInstall) ShowPanel(PanelInstall);
|
||||
else if (sender == NavInk) ShowPanel(PanelInk);
|
||||
else if (sender == NavConvert) ShowPanel(PanelConvert);
|
||||
else if (sender == NavTouch) ShowPanel(PanelTouch);
|
||||
else if (sender == NavActivate) ShowPanel(PanelActivate);
|
||||
}
|
||||
|
||||
private void ShowPanel(StackPanel panel)
|
||||
{
|
||||
foreach (var p in new[] { PanelInstall, PanelInk, PanelConvert, PanelTouch, PanelActivate })
|
||||
p.Visibility = p == panel ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private async Task RefreshInstallStatusAsync()
|
||||
{
|
||||
var installed = await Task.Run(() => _patchService.IsInstalled());
|
||||
InstallBtn.IsEnabled = !installed;
|
||||
UninstallBtn.IsEnabled = installed;
|
||||
InstallStatusText.Text = installed
|
||||
? "✅ 补丁已安装"
|
||||
: "⚠️ 补丁未安装";
|
||||
if (installed)
|
||||
{
|
||||
var m = await Task.Run(() => _patchService.GetManifest());
|
||||
PatchDetailText.Text = $"版本: {m.Version} | 安装时间: {m.InstallTime} | 备份文件: {m.Backups.Count} 个 | 注册表项: {m.RegistryKeys.Count} 个";
|
||||
}
|
||||
else
|
||||
{
|
||||
PatchDetailText.Text = "尚未安装补丁。点击「安装补丁」将备份关键配置并应用增强补丁。";
|
||||
}
|
||||
}
|
||||
|
||||
private async void InstallPatch_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var progress = new Progress<string>(msg =>
|
||||
{
|
||||
Dispatcher.Invoke(() => PatchDetailText.Text = msg);
|
||||
});
|
||||
InstallBtn.IsEnabled = false;
|
||||
UninstallBtn.IsEnabled = false;
|
||||
InstallStatusText.Text = "⏳ 正在安装...";
|
||||
try
|
||||
{
|
||||
await _patchService.InstallAsync(progress);
|
||||
InstallStatusText.Text = "✅ 补丁已安装";
|
||||
UninstallBtn.IsEnabled = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
InstallStatusText.Text = "❌ 安装失败";
|
||||
PatchDetailText.Text = ex.Message;
|
||||
InstallBtn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void UninstallPatch_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show(this,
|
||||
"确定要卸载 Better-Seewo 补丁吗?\n将还原备份文件并清除注册表修改。",
|
||||
"卸载确认", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (result != MessageBoxResult.Yes) return;
|
||||
|
||||
var progress = new Progress<string>(msg =>
|
||||
{
|
||||
Dispatcher.Invoke(() => PatchDetailText.Text = msg);
|
||||
});
|
||||
InstallBtn.IsEnabled = false;
|
||||
UninstallBtn.IsEnabled = false;
|
||||
InstallStatusText.Text = "⏳ 正在卸载...";
|
||||
try
|
||||
{
|
||||
await _patchService.UninstallAsync(progress);
|
||||
InstallStatusText.Text = "✅ 补丁已卸载";
|
||||
InstallBtn.IsEnabled = true;
|
||||
PatchDetailText.Text = "卸载完成。建议重启希沃白板。";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
InstallStatusText.Text = "❌ 卸载失败";
|
||||
PatchDetailText.Text = ex.Message;
|
||||
UninstallBtn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Minimize_Click(object s, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
||||
private void Maximize_Click(object s, RoutedEventArgs e) => WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||
private void Close_Click(object s, RoutedEventArgs e) => Close();
|
||||
|
||||
private async void ExportInk_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new SaveFileDialog { Title = "导出墨迹", Filter = "墨迹文件|*.ink.json|JSON|*.json", DefaultExt = ".ink.json" };
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
await ProgressDialog.Show(this, "导出墨迹", async p =>
|
||||
{
|
||||
p.UpdateStatus("正在导出墨迹...", "读取课件笔迹数据");
|
||||
await _inkService.ExportInkAsync(dialog.FileName);
|
||||
p.UpdateStatus("导出完成", $"已保存至 {Path.GetFileName(dialog.FileName)}");
|
||||
});
|
||||
InkStatus.Text = $"已导出: {Path.GetFileName(dialog.FileName)}";
|
||||
}
|
||||
}
|
||||
|
||||
private async void ImportInk_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog { Title = "导入墨迹", Filter = "墨迹文件|*.ink.json|JSON|*.json" };
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
await ProgressDialog.Show(this, "导入墨迹", async p =>
|
||||
{
|
||||
p.UpdateStatus("正在导入墨迹...", "写入课件笔迹");
|
||||
await _inkService.ImportInkAsync(dialog.FileName);
|
||||
});
|
||||
InkStatus.Text = "墨迹已导入";
|
||||
}
|
||||
}
|
||||
|
||||
private async void ConvertPptx_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog { Title = "选择 PPT", Filter = "PowerPoint|*.pptx" };
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
await ProgressDialog.Show(this, "转换文件", async p =>
|
||||
{
|
||||
p.UpdateStatus("正在转换 PPT → ENBX...", "调用转换器");
|
||||
var result = await new ConversionService().ConvertPptxToEnbxAsync(dialog.FileName);
|
||||
p.UpdateStatus("转换完成", Path.GetFileName(result));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void ConvertEnbx_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog { Title = "选择 ENBX", Filter = "希沃课件|*.enbx" };
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
await ProgressDialog.Show(this, "转换文件", async p =>
|
||||
{
|
||||
p.UpdateStatus("正在转换 ENBX → PPT...", "调用转换器");
|
||||
var result = await new ConversionService().ConvertEnbxToPptxAsync(dialog.FileName);
|
||||
p.UpdateStatus("导出完成", Path.GetFileName(result));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void CheckTouch_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var ok = await Task.Run(() => TouchFixService.IsTouchWorkingCorrectly());
|
||||
TouchStatus.Text = ok ? "✅ IWB 模式已启用" : "⚠️ 未启用 IWB 模式";
|
||||
}
|
||||
|
||||
private async void ApplyTouchFix_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
await ProgressDialog.Show(this, "触摸修复", async p =>
|
||||
{
|
||||
p.UpdateStatus("正在写入注册表配置...", "ForceIWB");
|
||||
await Task.Run(() => TouchFixService.ApplyFixAsync());
|
||||
p.UpdateStatus("正在配置 IWB 模式...", "IWBConfig.json");
|
||||
await Task.Delay(300);
|
||||
p.UpdateStatus("完成", "请重启希沃白板生效");
|
||||
});
|
||||
TouchStatus.Text = "修复已应用,请重启";
|
||||
}
|
||||
|
||||
private async void ActivatePro_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new ActivateDialog();
|
||||
dlg.Owner = this;
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
await ProgressDialog.Show(this, "激活", async p =>
|
||||
{
|
||||
p.UpdateStatus("正在验证激活码...", "");
|
||||
var ok = await _activationService.ActivateProfessionalAsync(dlg.LicenseKey);
|
||||
if (ok)
|
||||
{
|
||||
p.UpdateStatus("激活成功", "专业版已启用");
|
||||
ActivationStatusText.Text = "✅ 专业版已激活";
|
||||
}
|
||||
else
|
||||
{
|
||||
p.UpdateStatus("激活失败", "激活码无效");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void LaunchInstaller_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var ok = await _activationService.LaunchBen5InstallerAsync();
|
||||
if (!ok)
|
||||
MessageBox.Show(this, "未找到注册补丁安装程序", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
private async void CheckActivation_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var info = await Task.Run(() => _activationService.GetCurrentStatus());
|
||||
ActivationStatusText.Text = info.IsProfessional
|
||||
? $"✅ 专业版 · {info.LastActivationDate}"
|
||||
: "🔓 社区版";
|
||||
}
|
||||
|
||||
public static void ShowWindow()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var w = Application.Current.Windows.OfType<BetterSeewoMainWindow>().FirstOrDefault();
|
||||
if (w != null) { w.Activate(); return; }
|
||||
new BetterSeewoMainWindow().Show();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Better-EN5/UI/BoardMenuImportInk.cs
Normal file
30
Better-EN5/UI/BoardMenuImportInk.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Microsoft.Win32;
|
||||
using Cvte.EasiNote;
|
||||
using Cvte.Windows.Input;
|
||||
using BetterEN5.Services;
|
||||
|
||||
namespace BetterEN5.UI
|
||||
{
|
||||
public class BoardMenuImportInk : BoardEditMenuItem
|
||||
{
|
||||
public BoardMenuImportInk()
|
||||
{
|
||||
SortHint = 997;
|
||||
Command = new DelegateCommand(() =>
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "导入墨迹",
|
||||
Filter = "墨迹文件 (*.ink.json)|*.ink.json|JSON 文件 (*.json)|*.json",
|
||||
DefaultExt = ".ink.json"
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
var inkService = new InkService();
|
||||
_ = inkService.ImportInkAsync(dialog.FileName);
|
||||
}
|
||||
});
|
||||
Predicate = _ => true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using Cvte.EasiNote;
|
||||
using Cvte.Windows.Input;
|
||||
|
||||
@@ -12,9 +10,24 @@ namespace BetterEN5.UI
|
||||
SortHint = 999;
|
||||
Command = new DelegateCommand(() =>
|
||||
{
|
||||
BetterSeewoMainWindow.ShowWindow();
|
||||
LaunchManager();
|
||||
});
|
||||
Predicate = _ => true;
|
||||
}
|
||||
|
||||
private static void LaunchManager()
|
||||
{
|
||||
var baseDir = System.AppDomain.CurrentDomain.BaseDirectory;
|
||||
var mgrPath = System.IO.Path.Combine(baseDir, "Better-Seewo.Manager.exe");
|
||||
if (System.IO.File.Exists(mgrPath))
|
||||
{
|
||||
var psi = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = mgrPath,
|
||||
UseShellExecute = true
|
||||
};
|
||||
System.Diagnostics.Process.Start(psi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,24 @@ namespace BetterEN5.UI
|
||||
SortHint = 999;
|
||||
Command = new DelegateCommand(() =>
|
||||
{
|
||||
BetterSeewoMainWindow.ShowWindow();
|
||||
LaunchManager();
|
||||
});
|
||||
Predicate = _ => true;
|
||||
}
|
||||
|
||||
private static void LaunchManager()
|
||||
{
|
||||
var baseDir = System.AppDomain.CurrentDomain.BaseDirectory;
|
||||
var mgrPath = System.IO.Path.Combine(baseDir, "Better-Seewo.Manager.exe");
|
||||
if (System.IO.File.Exists(mgrPath))
|
||||
{
|
||||
var psi = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = mgrPath,
|
||||
UseShellExecute = true
|
||||
};
|
||||
System.Diagnostics.Process.Start(psi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<Window x:Class="BetterEN5.UI.ProgressDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="操作进度" Height="160" Width="420"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
WindowStyle="None" ResizeMode="NoResize"
|
||||
AllowsTransparency="True" Background="Transparent">
|
||||
<Border CornerRadius="10" Background="#1E1E2E" BorderBrush="#30FFFFFF" BorderThickness="1">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="16" ShadowDepth="3" Opacity="0.3" Color="Black"/>
|
||||
</Border.Effect>
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" x:Name="StatusText" Text="正在操作..." FontSize="14"
|
||||
Foreground="#C0C0D0" Margin="0,0,0,12"/>
|
||||
<ProgressBar Grid.Row="1" x:Name="ProgressBar" Height="6"
|
||||
IsIndeterminate="True" Foreground="#60B0FF"
|
||||
Background="#2A2A3E" BorderThickness="0"/>
|
||||
<TextBlock Grid.Row="2" x:Name="DetailText" Text="" FontSize="11"
|
||||
Foreground="#707090" Margin="0,8,0,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace BetterEN5.UI
|
||||
{
|
||||
public partial class ProgressDialog : Window
|
||||
{
|
||||
public ProgressDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
var desktop = System.Windows.SystemParameters.WorkArea;
|
||||
Left = (desktop.Width - Width) / 2;
|
||||
Top = (desktop.Height - Height) / 2;
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateStatus(string text, string detail = "")
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
StatusText.Text = text;
|
||||
if (!string.IsNullOrEmpty(detail))
|
||||
DetailText.Text = detail;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task RunWithProgress(Func<ProgressDialog, Task> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
await action(this);
|
||||
UpdateStatus("完成", "");
|
||||
await Task.Delay(500);
|
||||
DialogResult = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus($"失败: {ex.Message}", "");
|
||||
await Task.Delay(1500);
|
||||
DialogResult = false;
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
public static async Task<bool> Show(Window owner, string title, Func<ProgressDialog, Task> action)
|
||||
{
|
||||
var dialog = new ProgressDialog();
|
||||
dialog.Owner = owner;
|
||||
dialog.Title = title;
|
||||
dialog.Show();
|
||||
await dialog.RunWithProgress(action);
|
||||
return dialog.DialogResult ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
<Window x:Class="BetterEN5.UI.SettingsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Better-Seewo 设置"
|
||||
Height="520" Width="680"
|
||||
ResizeMode="CanResize"
|
||||
WindowStyle="SingleBorderWindow"
|
||||
FontSize="14">
|
||||
<Window.Resources>
|
||||
<Style x:Key="TabHeaderStyle" TargetType="TabItem">
|
||||
<Setter Property="FontSize" Value="15"/>
|
||||
<Setter Property="Padding" Value="16,8"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
<Style x:Key="ActionButton" TargetType="Button">
|
||||
<Setter Property="Padding" Value="16,8"/>
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
</Style>
|
||||
<Style x:Key="GroupBoxStyle" TargetType="GroupBox">
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
<Setter Property="Padding" Value="12"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="12">
|
||||
<TabControl>
|
||||
<TabItem Header="墨迹管理" Style="{StaticResource TabHeaderStyle}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="8">
|
||||
<GroupBox Header="墨迹导出" Style="{StaticResource GroupBoxStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,12">
|
||||
将当前课件中的所有墨迹(笔迹、标注)导出为 JSON 文件,方便备份和迁移。
|
||||
</TextBlock>
|
||||
<UniformGrid Columns="2" HorizontalAlignment="Left">
|
||||
<Button Content="导出墨迹..." Style="{StaticResource ActionButton}"
|
||||
Click="ExportInk_Click"/>
|
||||
<Button Content="导入墨迹..." Style="{StaticResource ActionButton}"
|
||||
Click="ImportInk_Click"/>
|
||||
</UniformGrid>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="墨迹设置" Style="{StaticResource GroupBoxStyle}">
|
||||
<StackPanel>
|
||||
<CheckBox x:Name="AutoSaveInkCheckBox" Margin="4" Content="关闭课件时自动备份墨迹"/>
|
||||
<CheckBox x:Name="IncludeShapeInkCheckBox" Margin="4" Content="导出包含形状上的笔迹"/>
|
||||
<Button Content="保存设置" Style="{StaticResource ActionButton}"
|
||||
HorizontalAlignment="Left" Click="SaveInkSettings_Click"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="状态" Style="{StaticResource GroupBoxStyle}">
|
||||
<TextBlock x:Name="InkStatusText" Text="就绪" Foreground="Gray"/>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="文件转换" Style="{StaticResource TabHeaderStyle}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="8">
|
||||
<GroupBox Header="PPT 转 ENBX" Style="{StaticResource GroupBoxStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,12">
|
||||
将 PowerPoint 文件(.pptx)转换为希沃白板课件格式(.enbx),
|
||||
保留更多原始格式和排版。
|
||||
</TextBlock>
|
||||
<Button Content="选择并转换 PPT..." Style="{StaticResource ActionButton}"
|
||||
HorizontalAlignment="Left" Click="ConvertPptx_Click"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="ENBX 转 PPT" Style="{StaticResource GroupBoxStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,12">
|
||||
将希沃白板课件(.enbx)导出为 PowerPoint 格式(.pptx)。
|
||||
</TextBlock>
|
||||
<Button Content="选择并转换 ENBX..." Style="{StaticResource ActionButton}"
|
||||
HorizontalAlignment="Left" Click="ConvertEnbx_Click"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="转换修复" Style="{StaticResource GroupBoxStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,12">
|
||||
清除转换缓存和临时文件,修复可能的转换失败问题。
|
||||
</TextBlock>
|
||||
<UniformGrid Columns="2" HorizontalAlignment="Left">
|
||||
<Button Content="清理转换缓存" Style="{StaticResource ActionButton}"
|
||||
Click="ClearConversionCache_Click"/>
|
||||
<Button Content="强制重新注册转换器" Style="{StaticResource ActionButton}"
|
||||
Click="ReRegisterConverter_Click"/>
|
||||
</UniformGrid>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="状态" Style="{StaticResource GroupBoxStyle}">
|
||||
<TextBlock x:Name="ConversionStatusText" Text="就绪" Foreground="Gray"/>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="触摸修复" Style="{StaticResource TabHeaderStyle}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="8">
|
||||
<GroupBox Header="触摸检测状态" Style="{StaticResource GroupBoxStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="TouchStatusText" TextWrapping="Wrap" Margin="0,0,0,12">
|
||||
正在检测触摸状态...
|
||||
</TextBlock>
|
||||
<UniformGrid Columns="2" HorizontalAlignment="Left">
|
||||
<Button Content="检测触摸状态" Style="{StaticResource ActionButton}"
|
||||
Click="CheckTouch_Click"/>
|
||||
<Button Content="应用修复" Style="{StaticResource ActionButton}"
|
||||
Click="ApplyTouchFix_Click"/>
|
||||
</UniformGrid>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="修复选项" Style="{StaticResource GroupBoxStyle}">
|
||||
<StackPanel>
|
||||
<CheckBox x:Name="ForceIWBModeCheckBox" Margin="4" Content="强制启用 IWB 模式(白板模式)" IsChecked="True"/>
|
||||
<CheckBox x:Name="SkipTouchCheckCheckBox" Margin="4" Content="跳过启动时的触摸检测" IsChecked="True"/>
|
||||
<CheckBox x:Name="SuppressTouchErrorsCheckBox" Margin="4" Content="抑制触摸相关错误提示" IsChecked="True"/>
|
||||
<CheckBox x:Name="EnableFallbackModeCheckBox" Margin="4" Content="启用鼠标回退模式(触摸失效时自动切换)" IsChecked="True"/>
|
||||
<Button Content="保存触摸设置" Style="{StaticResource ActionButton}"
|
||||
HorizontalAlignment="Left" Click="SaveTouchSettings_Click"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="说明" Style="{StaticResource GroupBoxStyle}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
• 部分学校电脑的触摸硬件本身没有故障,但希沃白板的触摸检测逻辑存在兼容性问题。
|
||||
• 本功能通过配置注册表和配置文件,强制启用触摸支持,跳过有问题的检测步骤。
|
||||
• 如果修复后触摸仍不工作,请尝试重启希沃白板。
|
||||
• 如遇到更严重的触摸问题,可使用"还原"功能恢复默认设置。
|
||||
</TextBlock>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="关于" Style="{StaticResource TabHeaderStyle}">
|
||||
<StackPanel Margin="24" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Better-Seewo" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,8"/>
|
||||
<TextBlock Text="希沃白板功能增强插件" FontSize="16"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,4"/>
|
||||
<TextBlock x:Name="VersionText" Text="版本 1.0.0"
|
||||
HorizontalAlignment="Center" Foreground="Gray" Margin="0,0,0,24"/>
|
||||
<Separator Margin="0,0,0,16"/>
|
||||
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center">
|
||||
Better-EN5 模块提供以下增强功能:
|
||||
• 墨迹(笔迹/标注)的独立导出和导入
|
||||
• 增强的 PPT 与 ENBX 文件转换
|
||||
• 触摸检测逻辑修复与错误抑制
|
||||
项目地址:https://github.com/anomalyco/opencode
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,283 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Microsoft.Win32;
|
||||
using Cvte.EasiNote;
|
||||
using BetterEN5.Services;
|
||||
|
||||
namespace BetterEN5.UI
|
||||
{
|
||||
public partial class SettingsWindow : Window
|
||||
{
|
||||
private readonly InkService _inkService = new();
|
||||
private readonly ConversionService _conversionService = new();
|
||||
|
||||
public SettingsWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LoadSettings();
|
||||
await RefreshTouchStatusAsync();
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var settingsFile = Path.Combine(localAppData, "Seewo", "EasiNote5", "BetterEN5", "settings.json");
|
||||
if (File.Exists(settingsFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(settingsFile);
|
||||
var settings = System.Text.Json.JsonSerializer.Deserialize<SettingsData>(json);
|
||||
if (settings != null)
|
||||
{
|
||||
AutoSaveInkCheckBox.IsChecked = settings.AutoSaveInk;
|
||||
IncludeShapeInkCheckBox.IsChecked = settings.IncludeShapeInk;
|
||||
ForceIWBModeCheckBox.IsChecked = settings.ForceIWB;
|
||||
SkipTouchCheckCheckBox.IsChecked = settings.SkipTouchCheck;
|
||||
SuppressTouchErrorsCheckBox.IsChecked = settings.SuppressTouchErrors;
|
||||
EnableFallbackModeCheckBox.IsChecked = settings.EnableFallbackMode;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var dir = Path.Combine(localAppData, "Seewo", "EasiNote5", "BetterEN5");
|
||||
var settingsFile = Path.Combine(dir, "settings.json");
|
||||
|
||||
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
||||
|
||||
var settings = new SettingsData
|
||||
{
|
||||
AutoSaveInk = AutoSaveInkCheckBox.IsChecked ?? false,
|
||||
IncludeShapeInk = IncludeShapeInkCheckBox.IsChecked ?? false,
|
||||
ForceIWB = ForceIWBModeCheckBox.IsChecked ?? true,
|
||||
SkipTouchCheck = SkipTouchCheckCheckBox.IsChecked ?? true,
|
||||
SuppressTouchErrors = SuppressTouchErrorsCheckBox.IsChecked ?? true,
|
||||
EnableFallbackMode = EnableFallbackModeCheckBox.IsChecked ?? true,
|
||||
};
|
||||
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(settings, new System.Text.Json.JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
File.WriteAllText(settingsFile, json);
|
||||
}
|
||||
|
||||
private async void ExportInk_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new SaveFileDialog
|
||||
{
|
||||
Title = "导出墨迹",
|
||||
Filter = "墨迹文件|*.ink.json|JSON 文件|*.json|所有文件|*.*",
|
||||
DefaultExt = ".ink.json"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _inkService.ExportInkAsync(dialog.FileName);
|
||||
InkStatusText.Text = $"墨迹已导出至: {dialog.FileName}";
|
||||
InkStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
InkStatusText.Text = $"导出失败: {ex.Message}";
|
||||
InkStatusText.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void ImportInk_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "导入墨迹",
|
||||
Filter = "墨迹文件|*.ink.json|JSON 文件|*.json|所有文件|*.*",
|
||||
DefaultExt = ".ink.json"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _inkService.ImportInkAsync(dialog.FileName);
|
||||
InkStatusText.Text = "墨迹已导入";
|
||||
InkStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
InkStatusText.Text = $"导入失败: {ex.Message}";
|
||||
InkStatusText.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveInkSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
InkStatusText.Text = "墨迹设置已保存";
|
||||
InkStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
|
||||
private async void ConvertPptx_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "选择 PowerPoint 文件",
|
||||
Filter = "PowerPoint 文件|*.pptx|所有文件|*.*"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _conversionService.ConvertPptxToEnbxAsync(dialog.FileName);
|
||||
ConversionStatusText.Text = $"转换完成: {result}";
|
||||
ConversionStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConversionStatusText.Text = $"转换失败: {ex.Message}";
|
||||
ConversionStatusText.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void ConvertEnbx_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "选择希沃白板课件",
|
||||
Filter = "希沃课件|*.enbx|所有文件|*.*"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _conversionService.ConvertEnbxToPptxAsync(dialog.FileName);
|
||||
ConversionStatusText.Text = $"导出完成: {result}";
|
||||
ConversionStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConversionStatusText.Text = $"导出失败: {ex.Message}";
|
||||
ConversionStatusText.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void ClearConversionCache_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _conversionService.FixConversionErrorsAsync();
|
||||
ConversionStatusText.Text = "转换缓存已清理";
|
||||
ConversionStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConversionStatusText.Text = $"清理失败: {ex.Message}";
|
||||
ConversionStatusText.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ReRegisterConverter_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var converterPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
|
||||
"Seewo", "EasiNote5", "EasiNote5_5.2.4.9855", "Main",
|
||||
"EasiNote.OfficeDocumentConverter.exe");
|
||||
|
||||
if (File.Exists(converterPath))
|
||||
{
|
||||
Process.Start(converterPath, "/register");
|
||||
await Task.Delay(1000);
|
||||
ConversionStatusText.Text = "转换器已重新注册";
|
||||
}
|
||||
else
|
||||
{
|
||||
ConversionStatusText.Text = "未找到转换器";
|
||||
}
|
||||
ConversionStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConversionStatusText.Text = $"注册失败: {ex.Message}";
|
||||
ConversionStatusText.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
}
|
||||
|
||||
private async void CheckTouch_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await RefreshTouchStatusAsync();
|
||||
}
|
||||
|
||||
private async void ApplyTouchFix_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await TouchFixService.ApplyFixAsync();
|
||||
TouchStatusText.Text = "触摸修复已应用,请重启希沃白板生效";
|
||||
TouchStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
SaveSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TouchStatusText.Text = $"修复失败: {ex.Message}";
|
||||
TouchStatusText.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveTouchSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
TouchStatusText.Text = "触摸设置已保存";
|
||||
TouchStatusText.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
|
||||
private async Task RefreshTouchStatusAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var isIwb = await Task.Run(() => TouchFixService.IsTouchWorkingCorrectly());
|
||||
TouchStatusText.Text = isIwb
|
||||
? "触摸状态: 正常 (IWB 模式已启用)"
|
||||
: "触摸状态: 非 IWB 模式 (可能存在问题)";
|
||||
TouchStatusText.Foreground = isIwb
|
||||
? System.Windows.Media.Brushes.Green
|
||||
: System.Windows.Media.Brushes.Orange;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TouchStatusText.Text = $"无法检测触摸状态: {ex.Message}";
|
||||
TouchStatusText.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
}
|
||||
|
||||
private class SettingsData
|
||||
{
|
||||
public bool AutoSaveInk { get; set; }
|
||||
public bool IncludeShapeInk { get; set; }
|
||||
public bool ForceIWB { get; set; } = true;
|
||||
public bool SkipTouchCheck { get; set; } = true;
|
||||
public bool SuppressTouchErrors { get; set; } = true;
|
||||
public bool EnableFallbackMode { get; set; } = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Better-EN5", "Better-EN5\Better-EN5.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,296 +0,0 @@
|
||||
# Better-Seewo 完整开发指南
|
||||
|
||||
**项目类型**: 希沃白板 5 插件 SDK(基于 dotnetCampus.EasiPlugin.Sdk v2.1.1-alpha.3 构建)
|
||||
**目标框架**: net6.0-windows (.NET 6.0, WPF + WinForms)
|
||||
**开发环境**: 希沃白板 5 (版本 5.2.2.653 ~ 5.3.0.0)
|
||||
|
||||
## 前言
|
||||
|
||||
希沃白板 5 插件 SDK 文档严重缺失,导致开发者几乎无法找到正确的菜单注册点。本项目通过对 `EasiNote.Api.dll` 的完全逆向工程分析,填补了所有开发空白。
|
||||
|
||||
## 核心发现
|
||||
|
||||
经过对 `EasiNote.Api.dll` (版本 5.2.4.9855) 的静态分析,我们发现了 **`Cvte.EasiNote.UIItemPurposes`** 类型,其中包含 **13 个可用的菜单常量**。这是本项目开发的所有菜单入口的基础。
|
||||
|
||||
### 所有真实可用的常量
|
||||
|
||||
| 常量名 | 实际字符串值 | 菜单层级 | 适用场景 |
|
||||
|---|---|---|---|
|
||||
| `ToolBar` | `EduBoard.V5.ToolBarItem` | 顶部通用工具栏 | 白板顶部的工具按钮区域(绘图工具) |
|
||||
| `FunctionBar` | `EduBoard.V5.FunctionBarItem` | 顶部功能栏 | 白板顶部显示的绘图工具栏(画笔、橡皮擦等) |
|
||||
| `BoardEditMenu` | `EduBoard.V5.BoardEditMenuItem` | 备课模式·右键菜单 | 课件上方的"板书"区域右键菜单 (备课场景) |
|
||||
| `BoardDisplayMenu` | `EduBoard.V5.BoardDisplayMenuItem` | 授课模式·右键菜单 | 课件上方的"板书"区域右键菜单 (授课场景) |
|
||||
| `ElementEditMenu` | `EduBoard.V5.ElementEditMenuItem` | 备课模式·元素右键菜单 | 备课时对单个板书元素的右键操作 |
|
||||
| `ElementDisplayMenu` | `EduBoard.V5.ElementDisplayMenuItem` | 授课模式·元素右键菜单 | 授课时对单个板书元素的右键操作 |
|
||||
| `HeadToolBar` | `EduBoard.V5.HeadToolBarItem` | 顶级工具栏 | 白板左上角的"Better-Seewo"入口按钮 |
|
||||
| `BrowserTitleBar` | `EduBoard.V5.ExtendedTitleBar.Browser` | 浏览器·标题栏 | 备课时浏览器区域顶部的标题栏区域 |
|
||||
| `CloudTitleBar` | `EduBoard.V5.ExtendedTitleBar.Cloud` | 云盘·标题栏 | 备课时云盘区域顶部的标题栏区域 |
|
||||
| `AllTitleBar` | `EduBoard.V5.ExtendedTitleBar.All` | 所有·标题栏 | 备课时所有标题栏区域的集合 |
|
||||
| `TabUniqueApplicationMenu` | `EduBoard.V5.ApplicationMenu.Shell` | 当前标签页·应用菜单 | 当前标签页左下角的汉堡菜单 (ApplicationMenu 类型) |
|
||||
| `TabGlobalApplicationMenu` | `EduBoard.V5.ApplicationMenu.All` | 全局·应用菜单 | 所有标签页左下角的汉堡菜单 (ApplicationMenu 类型) |
|
||||
| `MultiBoardProxyToolBar` | `EduBoard.V5.MultiBoardProxyToolBar.Shell` | 多板代理·工具栏 | 多板代理的工具栏区域 |
|
||||
|
||||
**⚠️ 关键提示**:希沃 SDK **不包含** `ApplicationMenu` 或 `ExtendedTitleBar` 类型常量!实际使用对应的具体常量替代:
|
||||
- `ApplicationMenu` → `TabUniqueApplicationMenu` / `TabGlobalApplicationMenu`
|
||||
- `ExtendedTitleBar` → `BrowserTitleBar` / `CloudTitleBar` / `AllTitleBar`
|
||||
|
||||
## 菜单层级设计
|
||||
|
||||
每个菜单项都对应于 `IUIItem` 的一个具体子类,这些子类定义了特定的 UI 层级。
|
||||
|
||||
```csharp
|
||||
// 菜单常量对应关系 (全部位于 Cvte.EasiNote 命名空间)
|
||||
UIItemPurposes.BoardEditMenu // → BoardEditMenuItem
|
||||
UIItemPurposes.HeadToolBar // → HeadToolBarItem
|
||||
UIItemPurposes.TabGlobalApplicationMenu // → ApplicationMenuItem
|
||||
UIItemPurposes.AllTitleBar // → ExtendedTitleBarItem
|
||||
```
|
||||
|
||||
## 本项目核心组件
|
||||
|
||||
### 1. BetterSeewoMainWindow
|
||||
**文件**:`Better-EN5/UI/BetterSeewoMainWindow.xaml.cs`
|
||||
**作用**:插件的主界面,采用 16:9 横版布局,包含五个功能模块
|
||||
|
||||
### 2. 五大核心服务
|
||||
|
||||
#### InkService (课件墨迹管理)
|
||||
- **功能**:导出/导入课件墨迹数据
|
||||
- **技术**:反射调用希沃白板 Remark API
|
||||
- **优势**:无需硬编译依赖,兼容所有版本的白板
|
||||
- **应用场景**:课件备份、墨迹恢复、跨设备同步
|
||||
|
||||
#### ConversionService (文件转换)
|
||||
- **功能**:PPT ↔ ENBX 格式互转
|
||||
- **技术**:独立进程 (`EasiNote.OfficeDocumentConverter.exe`)
|
||||
- **优势**:避免平台兼容性问题,保持文件原始质量
|
||||
|
||||
#### TouchFixService (触摸修复)
|
||||
- **功能**:为非触摸大屏设备启用 IWB 模式
|
||||
- **技术**:注册表 + 配置文件补丁 (`IWBConfig.json`, `TouchConfig.ini`)
|
||||
- **应用场景**:大屏模式、触摸屏校准、学校多点触控
|
||||
|
||||
#### ActivationService (专业版激活)
|
||||
- **功能**:专业版激活码验证 + 注册
|
||||
- **技术**:注册表写入 + 一键安装补丁程序
|
||||
|
||||
### 3. UI模块
|
||||
|
||||
#### BoardMenuExportInk
|
||||
**文件**:`Better-EN5/UI/BoardMenuExportInk.cs`
|
||||
**用途**:备课模式·右键菜单,导出当前课件墨迹为 `.json` 文件
|
||||
|
||||
#### BoardMenuSettings
|
||||
**文件**:`Better-EN5/UI/BoardMenuSettings.cs`
|
||||
**用途**:备课模式·右键菜单,打开 Better-Seewo 设置界面
|
||||
|
||||
#### HeadToolBarSettings
|
||||
**文件**:`Better-EN5/UI/HeadToolBarSettings.cs`
|
||||
**用途**:通用顶级工具栏,快速打开 Better-Seewo 主界面
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 1. 环境搭建
|
||||
```bash
|
||||
# 1. 安装 .NET 6.0 SDK
|
||||
# 2. 下载并安装希沃白板 5 (5.2.2.653 ~ 5.3.0.0)
|
||||
```
|
||||
|
||||
### 2. 构建项目
|
||||
```bash
|
||||
dotnet build Better-EN5/Better-EN5.csproj -c Release
|
||||
```
|
||||
|
||||
### 3. 安装插件
|
||||
#### 方式一:安装程序
|
||||
```powershell
|
||||
# 以管理员权限运行,自动安装到希沃白板指定目录
|
||||
.
|
||||
\\install.ps1
|
||||
```
|
||||
|
||||
#### 方式二:手动部署
|
||||
```powershell
|
||||
# 将以下目录复制到:
|
||||
# %APPDATA%\Seewo\EasiNote5\Extensions\Better-EN5\
|
||||
Better-EN5\bin\Release\net6.0-windows\
|
||||
Better-EN5\manifest.coin
|
||||
```
|
||||
|
||||
### 4. 使用说明
|
||||
1. **重启希沃白板 5**
|
||||
2. **找到入口**:
|
||||
- **备课模式**:右键菜单 → "Better-Seewo" 或顶部工具栏按钮
|
||||
- **授课模式**:左下角汉堡菜单 → "Better-Seewo" (待实现)
|
||||
3. **体验功能**:打开主界面,体验五大核心服务
|
||||
|
||||
## 完整开发指南
|
||||
|
||||
### 1. 创建新菜单项
|
||||
|
||||
**步骤**:
|
||||
1. **选择目标常量**,确定基类
|
||||
2. **创建子类**,继承对应基类
|
||||
3. **实现功能成员**:`Command`、`Predicate`、`SortHint`
|
||||
4. **自动注册**:无需手动处理,`AppendWithLang` 自动完成
|
||||
|
||||
**示例** (备课模式·右键菜单):
|
||||
```csharp
|
||||
// 1. 选择 BoardEditMenu 常量(备课模式·右键菜单)
|
||||
UIItemPurposes.BoardEditMenu // → BoardEditMenuItem
|
||||
|
||||
// 2. 创建子类
|
||||
public class ExportInkMenuItem : BoardEditMenuItem
|
||||
{
|
||||
public ExportInkMenuItem()
|
||||
{
|
||||
SortHint = 100; // 菜单项排序权重
|
||||
Command = new DelegateCommand(ExportInk);
|
||||
Predicate = _ => true; // 总是显示
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 自动注册 (通过 AppendWithLang)
|
||||
manager.AppendWithLang(new ExportInkMenuItem(),
|
||||
new UIItemAttribute(UIItemPurposes.BoardEditMenu), // <-- 指向 BoardEditMenu 常量
|
||||
new[]
|
||||
{
|
||||
new UIItemLangInfo(CultureInfo.Chinese, "导出墨迹"),
|
||||
new UIItemLangInfo(CultureInfo.English, "Export Ink")
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 多语言支持
|
||||
|
||||
系统中内置完整的多语言支持框架。只需在注册时提供对应语言的文本即可。
|
||||
|
||||
```csharp
|
||||
new[]
|
||||
{
|
||||
new UIItemLangInfo(new CultureInfo("zh-CHS"), "功能名称"),
|
||||
new UIItemLangInfo(new CultureInfo("en"), "Feature Name"),
|
||||
new UIItemLangInfo(new CultureInfo("ja"), "機能名"),
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 所有常量对照表与应用场景
|
||||
|
||||
| 菜单常量 | 所处层级 | 适用场景 | 示例 |
|
||||
|---|---|---|---|
|
||||
| `BoardEditMenu` | 备课模式·右键菜单 | 备课时课件上方的右键菜单 | 导出墨迹、设置、打印等 |
|
||||
| `BoardDisplayMenu` | 授课模式·右键菜单 | 授课时课件上方的右键菜单 | 授课控制、评阅等 |
|
||||
| `HeadToolBar` | 通用顶级工具栏 | 白板左上角的按钮 | Better-Seewo 入口 |
|
||||
| `TabUniqueApplicationMenu` | 当前标签页·汉堡菜单 | 当前标签页的左下角菜单 | 单页专用功能 |
|
||||
| `TabGlobalApplicationMenu` | 全局·汉堡菜单 | 所有标签页的左下角菜单 | 所有页面通用功能 |
|
||||
| `AllTitleBar` | 备课·所有标题栏 | 备课时所有标题栏区域 | 标题栏菜单 |
|
||||
| `BrowserTitleBar` | 备课·浏览器标题栏 | 备课时浏览器区域标题栏 | 浏览器相关操作 |
|
||||
| `CloudTitleBar` | 备课·云盘标题栏 | 备课时云盘区域标题栏 | 云盘相关操作 |
|
||||
|
||||
## 技术要点
|
||||
|
||||
### 1. 菜单注册机制
|
||||
|
||||
```csharp
|
||||
// 底层方法 (不推荐直接使用)
|
||||
manager.Append(item, new UIItemAttribute(UIItemPurposes.BoardEditMenu));
|
||||
|
||||
// 方便易用的封装 (推荐使用)
|
||||
manager.AppendWithLang(item, new UIItemAttribute(UIItemPurposes.BoardEditMenu), langInfos);
|
||||
```
|
||||
|
||||
### 2. 大屏支持
|
||||
|
||||
为非触摸大屏设备提供专属支持,通过注册表和配置文件补丁实现。
|
||||
|
||||
### 3. 激活机制
|
||||
|
||||
专业版激活码验证系统,支持在线验证和离线注册。
|
||||
|
||||
### 4. 文件转换
|
||||
|
||||
使用独立进程实现 PPT ↔ ENBX 互转,避免平台兼容性问题。
|
||||
|
||||
### 5. 激活流程
|
||||
|
||||
一键启动 BEN5 注册补丁安装程序,实现专业版激活。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: `ApplicationMenu` 和 `ExtendedTitleBar` 是否存在?
|
||||
**A**: 不存在。使用对应的具体常量替代:
|
||||
- `ApplicationMenu` → `TabUniqueApplicationMenu` / `TabGlobalApplicationMenu`
|
||||
- `ExtendedTitleBar` → `BrowserTitleBar` / `CloudTitleBar` / `AllTitleBar`
|
||||
|
||||
### Q: 菜单项如何排序?
|
||||
**A**: 使用 `SortHint` 属性,数值越小排序越前。
|
||||
|
||||
### Q: 如何控制菜单项显示条件?
|
||||
**A**: 使用 `Predicate` 属性,接收 `object` 参数,返回 `bool` 值。
|
||||
|
||||
### Q: 多语言如何使用?
|
||||
**A**: 系统自动生成 Key `Lang.{前缀}.{菜单项类名}`,直接在 UI 中使用即可。
|
||||
|
||||
## 构建与发布
|
||||
|
||||
### 构建命令
|
||||
```bash
|
||||
# 调试版本
|
||||
dotnet build Better-EN5/Better-EN5.csproj -c Debug
|
||||
|
||||
# 发布版本
|
||||
dotnet build Better-EN5/Better-EN5.csproj -c Release
|
||||
```
|
||||
|
||||
### 发布文件
|
||||
```
|
||||
Better-EN5/bin/Release/net6.0-windows/
|
||||
├── Better-EN5.dll # 主插件
|
||||
├── BetterSeewoMainWindow.exe.config # 配置文件
|
||||
├── ... (所有依赖 DLL)
|
||||
└── BetterSeewoMainWindow.xaml # 主界面资源
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
## 构建方法
|
||||
|
||||
### 前置条件
|
||||
1. 已安装 **希沃白板 5**(版本 5.2.2.653 ~ 5.3.0.0)
|
||||
2. 已安装 **.NET SDK 6.0**(推荐 `C:\Program Files\dotnet`)
|
||||
3. 确保 `dotnet` 在 `PATH` 中,或使用完整路径
|
||||
|
||||
### 构建 Release
|
||||
```powershell
|
||||
# 方式一:确保 dotnet 在 PATH
|
||||
set PATH=C:\Program Files\dotnet;%PATH%
|
||||
dotnet build -c Release Better-EN5
|
||||
|
||||
# 方式二:使用完整路径
|
||||
& "C:\Program Files\dotnet\dotnet.exe" build -c Release Better-EN5
|
||||
```
|
||||
|
||||
### 安装到希沃白板
|
||||
```powershell
|
||||
# 管理员身份运行
|
||||
.\scripts\install.ps1
|
||||
```
|
||||
|
||||
### 构建输出
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `Better-EN5\bin\Release\net6.0-windows\Better-EN5.dll` | 插件 DLL |
|
||||
| `Better-EN5\bin\Release\net6.0-windows\Better-EN5.exe` | 宿主程序 |
|
||||
| `bin\Release\Better-Seewo 增强插件.X.X.X.exe` | 安装包 |
|
||||
| `bin\Release\Better-Seewo 增强插件.X.X.X.zip` | 插件包 (.enp) |
|
||||
|
||||
## 许可协议
|
||||
|
||||
本项目仅供 **学习交流** 使用。
|
||||
|
||||
## 致谢
|
||||
|
||||
感谢 dotnetCampus.EasiPlugin.Sdk 框架的支持。
|
||||
|
||||
---
|
||||
|
||||
**开发指南已根据对 `EasiNote.Api.dll` 的完整逆向工程成果生成**。所有常量和继承关系均已验证。
|
||||
@@ -1,10 +0,0 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageOutputPath>$(MSBuildThisFileDirectory)bin\$(Configuration)</PackageOutputPath>
|
||||
<Company>Better-Seewo</Company>
|
||||
<Authors>Better-Seewo</Authors>
|
||||
<RepositoryUrl>https://github.com/anomalyco/opencode</RepositoryUrl>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
35
Manager/ActivateDialog.xaml
Normal file
35
Manager/ActivateDialog.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<Window x:Class="BetterSeewo.Manager.ActivateDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="专业版激活" Height="260" Width="440"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
WindowStyle="SingleBorderWindow"
|
||||
ResizeMode="NoResize"
|
||||
FontSize="14">
|
||||
<Window.Resources>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Padding" Value="20,8"/>
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Text="输入 Better-Seewo 专业版激活码" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,16"/>
|
||||
<TextBox Grid.Row="1" x:Name="KeyTextBox" FontSize="18" Padding="8"
|
||||
HorizontalAlignment="Stretch" TextChanged="OnKeyTextChanged"/>
|
||||
<TextBlock Grid.Row="2" x:Name="HintText" Text="激活码通常由 16 位以上字母和数字组成"
|
||||
Foreground="Gray" FontSize="12" Margin="0,6,0,0"/>
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0">
|
||||
<Button Content="取消" IsCancel="True" Width="80"/>
|
||||
<Button x:Name="ActivateButton" Content="激活" IsDefault="True" Width="80"
|
||||
IsEnabled="False" Click="Activate_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
27
Manager/ActivateDialog.xaml.cs
Normal file
27
Manager/ActivateDialog.xaml.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace BetterSeewo.Manager
|
||||
{
|
||||
public partial class ActivateDialog : Window
|
||||
{
|
||||
public string LicenseKey { get; private set; } = "";
|
||||
|
||||
public ActivateDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnKeyTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
ActivateButton.IsEnabled = KeyTextBox.Text.Trim().Length >= 4;
|
||||
}
|
||||
|
||||
private void Activate_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LicenseKey = KeyTextBox.Text.Trim();
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
Manager/App.xaml
Normal file
15
Manager/App.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<Application x:Class="BetterSeewo.Manager.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<Color x:Key="Bg">#0F0F1A</Color>
|
||||
<Color x:Key="CardBg">#1A1A2E</Color>
|
||||
<Color x:Key="Accent">#60B0FF</Color>
|
||||
<Color x:Key="Fg">#D0D0E0</Color>
|
||||
<Color x:Key="SubFg">#707090</Color>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="#25FFFFFF"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
8
Manager/App.xaml.cs
Normal file
8
Manager/App.xaml.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace BetterSeewo.Manager
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
230
Manager/MainWindow.xaml
Normal file
230
Manager/MainWindow.xaml
Normal file
@@ -0,0 +1,230 @@
|
||||
<Window x:Class="BetterSeewo.Manager.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Better-Seewo 管理工具"
|
||||
Width="860" Height="560"
|
||||
MinWidth="720" MinHeight="420"
|
||||
WindowStyle="None" ResizeMode="CanResize"
|
||||
AllowsTransparency="True" Background="Transparent"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
FontSize="13">
|
||||
<Border CornerRadius="10" Background="#0F0F1A" BorderBrush="#25FFFFFF" BorderThickness="1">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="20" ShadowDepth="3" Opacity="0.3" Color="Black"/>
|
||||
</Border.Effect>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="36"/>
|
||||
<RowDefinition Height="40"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="24"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" Background="#1A1A2E" CornerRadius="10,10,0,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="12,0">
|
||||
<TextBlock Text="✦" FontSize="11" Foreground="#60B0FF" VerticalAlignment="Center"/>
|
||||
<TextBlock Text=" Better-Seewo Manager" FontSize="12" Foreground="#C0C0D0" VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Button x:Name="MinBtn" Content="─" Width="26" Height="20"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="#707090" FontSize="11"
|
||||
Cursor="Hand" Click="Minimize_Click"/>
|
||||
<Button x:Name="MaxBtn" Content="□" Width="26" Height="20"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="#707090" FontSize="11"
|
||||
Cursor="Hand" Click="Maximize_Click"/>
|
||||
<Button x:Name="CloseBtn" Content="✕" Width="26" Height="20"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="#707090" FontSize="11"
|
||||
Cursor="Hand" Click="Close_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Background="#12121E" Padding="8,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<RadioButton x:Name="TabInstall" GroupName="Nav" IsChecked="True"
|
||||
Content=" 安装管理 " Foreground="#D0D0E0"
|
||||
Background="#1A1A2E" BorderThickness="0"
|
||||
Cursor="Hand" Height="28" Padding="14,0"
|
||||
Checked="TabChanged" FontSize="12"/>
|
||||
<RadioButton x:Name="TabInk" GroupName="Nav"
|
||||
Content=" 墨迹管理 " Foreground="#D0D0E0"
|
||||
Background="#1A1A2E" BorderThickness="0"
|
||||
Cursor="Hand" Height="28" Padding="14,0"
|
||||
Checked="TabChanged" FontSize="12"/>
|
||||
<RadioButton x:Name="TabConvert" GroupName="Nav"
|
||||
Content=" 文件转换 " Foreground="#D0D0E0"
|
||||
Background="#1A1A2E" BorderThickness="0"
|
||||
Cursor="Hand" Height="28" Padding="14,0"
|
||||
Checked="TabChanged" FontSize="12"/>
|
||||
<RadioButton x:Name="TabTouch" GroupName="Nav"
|
||||
Content=" 触摸修复 " Foreground="#D0D0E0"
|
||||
Background="#1A1A2E" BorderThickness="0"
|
||||
Cursor="Hand" Height="28" Padding="14,0"
|
||||
Checked="TabChanged" FontSize="12"/>
|
||||
<RadioButton x:Name="TabActivate" GroupName="Nav"
|
||||
Content=" 激活 " Foreground="#D0D0E0"
|
||||
Background="#1A1A2E" BorderThickness="0"
|
||||
Cursor="Hand" Height="28" Padding="14,0"
|
||||
Checked="TabChanged" FontSize="12"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Rectangle Grid.Row="2" Fill="#252540" Height="1" Opacity="0.5"/>
|
||||
|
||||
<ScrollViewer Grid.Row="3" Background="#0D0D18" Padding="20,14"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<!-- Install -->
|
||||
<StackPanel x:Name="PageInstall" Visibility="Visible">
|
||||
<TextBlock Text="安装管理" FontSize="18" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,6"/>
|
||||
<TextBlock x:Name="InstallStatusText" Text="正在检测状态..." FontSize="12"
|
||||
Foreground="#707090" Margin="0,0,0,14"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" x:Name="InstallBtn" Content="安装补丁" Width="160" Height="32"
|
||||
Cursor="Hand" Click="InstallPatch_Click"/>
|
||||
<Button Grid.Column="2" x:Name="UninstallBtn" Content="卸载补丁" Width="160" Height="32"
|
||||
Cursor="Hand" Click="UninstallPatch_Click" IsEnabled="False"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="PatchDetailText" FontSize="11" Foreground="#707090"
|
||||
TextWrapping="Wrap" Margin="0,10,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Ink -->
|
||||
<StackPanel x:Name="PageInk" Visibility="Collapsed">
|
||||
<TextBlock Text="墨迹管理" FontSize="18" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,6"/>
|
||||
<TextBlock Text="保存/打开白板中的墨笔笔迹,支持自动保存" FontSize="12"
|
||||
Foreground="#707090" Margin="0,0,0,14"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Content="导出墨迹..." Width="120" Height="32" Cursor="Hand" Click="ExportInk_Click"/>
|
||||
<Button Grid.Column="2" Content="导入墨迹..." Width="120" Height="32" Cursor="Hand" Click="ImportInk_Click"/>
|
||||
<Button Grid.Column="4" Content="设置..." Width="120" Height="32" Cursor="Hand" Click="InkSettings_Click"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="InkStatus" FontSize="11" Foreground="#707090" Margin="0,8,0,0"/>
|
||||
<!-- Auto-save settings -->
|
||||
<Border Background="#1A1A2E" CornerRadius="6" Padding="12" Margin="0,12,0,0"
|
||||
BorderBrush="#25FFFFFF" BorderThickness="1">
|
||||
<StackPanel>
|
||||
<TextBlock Text="自动保存设置" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="#D0D0E0" Margin="0,0,0,8"/>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="启用" Foreground="#707090" VerticalAlignment="Center"/>
|
||||
<CheckBox Grid.Row="0" Grid.Column="1" x:Name="AutoSaveEnabled" Foreground="#D0D0E0"
|
||||
Checked="AutoSave_Changed" Unchecked="AutoSave_Changed"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="保存间隔" Foreground="#707090" VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
|
||||
<TextBox x:Name="AutoSaveInterval" Text="5" Width="50" Height="22"
|
||||
Foreground="#D0D0E0" Background="#0D0D18"
|
||||
BorderBrush="#25FFFFFF" BorderThickness="1"
|
||||
TextChanged="AutoSaveInterval_Changed"/>
|
||||
<TextBlock Text=" 分钟" Foreground="#707090" Margin="4,0,0,0" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="保存目录" Foreground="#707090" VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<TextBlock x:Name="AutoSavePath" Text="%USERPROFILE%\Documents\Better-Seewo\Ink"
|
||||
Foreground="#707090" VerticalAlignment="Center"/>
|
||||
<Button Content="选择..." Width="60" Height="22" Margin="8,0,0,0"
|
||||
Cursor="Hand" FontSize="11" Click="SelectInkDir_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Convert -->
|
||||
<StackPanel x:Name="PageConvert" Visibility="Collapsed">
|
||||
<TextBlock Text="文件转换" FontSize="18" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,6"/>
|
||||
<TextBlock Text="PPT ↔ ENBX 格式互转" FontSize="12"
|
||||
Foreground="#707090" Margin="0,0,0,14"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Content="PPT → ENBX" Width="140" Height="32" Cursor="Hand" Click="ConvertPptx_Click"/>
|
||||
<Button Grid.Column="2" Content="ENBX → PPT" Width="140" Height="32" Cursor="Hand" Click="ConvertEnbx_Click"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="ConvertStatus" FontSize="11" Foreground="#707090" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Touch -->
|
||||
<StackPanel x:Name="PageTouch" Visibility="Collapsed">
|
||||
<TextBlock Text="触摸修复" FontSize="18" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,6"/>
|
||||
<TextBlock Text="修复希沃白板在非希沃硬件上的触摸检测问题" FontSize="12"
|
||||
Foreground="#707090" Margin="0,0,0,14"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Content="检测触摸状态" Width="140" Height="32" Cursor="Hand" Click="CheckTouch_Click"/>
|
||||
<Button Grid.Column="2" Content="应用修复" Width="140" Height="32" Cursor="Hand" Click="ApplyTouchFix_Click"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="TouchStatus" FontSize="11" Foreground="#707090" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Activate -->
|
||||
<StackPanel x:Name="PageActivate" Visibility="Collapsed">
|
||||
<TextBlock Text="专业版激活" FontSize="18" FontWeight="SemiBold"
|
||||
Foreground="#60B0FF" Margin="0,0,0,6"/>
|
||||
<TextBlock x:Name="ActivationStatusText" Text="当前状态: 社区版" FontSize="12"
|
||||
Foreground="#707090" Margin="0,0,0,14"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Content="输入激活码..." Width="130" Height="32" Cursor="Hand" Click="ActivatePro_Click"/>
|
||||
<Button Grid.Column="2" Content="打开注册补丁..." Width="130" Height="32" Cursor="Hand" Click="LaunchInstaller_Click"/>
|
||||
<Button Grid.Column="4" Content="检测激活状态" Width="130" Height="32" Cursor="Hand" Click="CheckActivation_Click"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Border Grid.Row="4" Background="#1A1A2E" CornerRadius="0,0,10,10">
|
||||
<TextBlock Text="雾启工作室 × Macrohard Studio · 雾生万象,启以为光"
|
||||
FontSize="10" Foreground="#404058"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
307
Manager/MainWindow.xaml.cs
Normal file
307
Manager/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using BetterSeewo.Manager.Services;
|
||||
|
||||
namespace BetterSeewo.Manager
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly PatchService _patchService = new();
|
||||
private readonly InkService _inkService = new();
|
||||
private readonly CustomConversionService _conversionService = new();
|
||||
private readonly ActivationService _activationService = new();
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await RefreshInstallStatusAsync();
|
||||
LoadInkSettings();
|
||||
}
|
||||
|
||||
private void TabChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender == TabInstall) ShowPage(PageInstall);
|
||||
else if (sender == TabInk) ShowPage(PageInk);
|
||||
else if (sender == TabConvert) ShowPage(PageConvert);
|
||||
else if (sender == TabTouch) ShowPage(PageTouch);
|
||||
else if (sender == TabActivate) ShowPage(PageActivate);
|
||||
}
|
||||
|
||||
private void ShowPage(StackPanel page)
|
||||
{
|
||||
foreach (var p in new[] { PageInstall, PageInk, PageConvert, PageTouch, PageActivate })
|
||||
p.Visibility = p == page ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void Minimize_Click(object s, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
||||
private void Maximize_Click(object s, RoutedEventArgs e) =>
|
||||
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||
private void Close_Click(object s, RoutedEventArgs e) => Close();
|
||||
|
||||
// Install Management
|
||||
private async Task RefreshInstallStatusAsync()
|
||||
{
|
||||
var installed = await Task.Run(() => _patchService.IsInstalled());
|
||||
InstallBtn.IsEnabled = !installed;
|
||||
UninstallBtn.IsEnabled = installed;
|
||||
InstallStatusText.Text = installed ? "补丁已安装" : "补丁未安装";
|
||||
if (installed)
|
||||
{
|
||||
var m = await Task.Run(() => _patchService.GetManifest());
|
||||
PatchDetailText.Text = $"版本: {m.Version} | 安装时间: {m.InstallTime} | 备份: {m.Backups.Count} 个 | 注册表: {m.RegistryKeys.Count} 项";
|
||||
}
|
||||
else
|
||||
PatchDetailText.Text = "尚未安装补丁。点击「安装补丁」将备份关键配置并应用增强。";
|
||||
}
|
||||
|
||||
private async void InstallPatch_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var progress = new Progress<string>(msg => Dispatcher.Invoke(() => PatchDetailText.Text = msg));
|
||||
InstallBtn.IsEnabled = false;
|
||||
UninstallBtn.IsEnabled = false;
|
||||
InstallStatusText.Text = "正在安装...";
|
||||
try
|
||||
{
|
||||
await _patchService.InstallAsync(progress);
|
||||
InstallStatusText.Text = "补丁已安装";
|
||||
UninstallBtn.IsEnabled = true;
|
||||
await CreateShortcutAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
InstallStatusText.Text = "安装失败";
|
||||
PatchDetailText.Text = ex.Message;
|
||||
InstallBtn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void UninstallPatch_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show(this, "确定要卸载补丁吗?\n将还原备份文件并清除注册表修改。",
|
||||
"卸载确认", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (result != MessageBoxResult.Yes) return;
|
||||
|
||||
var progress = new Progress<string>(msg => Dispatcher.Invoke(() => PatchDetailText.Text = msg));
|
||||
InstallBtn.IsEnabled = false;
|
||||
UninstallBtn.IsEnabled = false;
|
||||
InstallStatusText.Text = "正在卸载...";
|
||||
try
|
||||
{
|
||||
await _patchService.UninstallAsync(progress);
|
||||
InstallStatusText.Text = "补丁已卸载";
|
||||
InstallBtn.IsEnabled = true;
|
||||
PatchDetailText.Text = "卸载完成。建议重启希沃白板。";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
InstallStatusText.Text = "卸载失败";
|
||||
PatchDetailText.Text = ex.Message;
|
||||
UninstallBtn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateShortcutAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var ps1 = Path.GetTempFileName() + ".ps1";
|
||||
var exePath = Environment.ProcessPath;
|
||||
var desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
||||
var lnkPath = Path.Combine(desktop, "Better-Seewo Manager.lnk");
|
||||
File.WriteAllText(ps1,
|
||||
"$ws = New-Object -ComObject WScript.Shell\n" +
|
||||
"$s = $ws.CreateShortcut(\"" + lnkPath.Replace("'", "''") + "\")\n" +
|
||||
"$s.TargetPath = \"" + exePath.Replace("'", "''") + "\"\n" +
|
||||
"$s.Description = \"Better-Seewo 插件管理工具\"\n" +
|
||||
"$s.Save()");
|
||||
var psi = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell",
|
||||
Arguments = $"-ExecutionPolicy Bypass -File \"{ps1}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
using var proc = System.Diagnostics.Process.Start(psi);
|
||||
proc?.WaitForExit(5000);
|
||||
try { File.Delete(ps1); } catch { }
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// Ink Management
|
||||
private void LoadInkSettings()
|
||||
{
|
||||
var settings = InkSettings.Load();
|
||||
AutoSaveEnabled.IsChecked = settings.AutoSaveEnabled;
|
||||
AutoSaveInterval.Text = settings.AutoSaveIntervalMinutes.ToString();
|
||||
AutoSavePath.Text = settings.SaveDirectory;
|
||||
}
|
||||
|
||||
private async void ExportInk_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Title = "导出墨迹",
|
||||
Filter = "墨迹文件|*.ink.json|JSON|*.json",
|
||||
DefaultExt = ".ink.json"
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
await Task.Run(() => _inkService.ExportInkAsync(dialog.FileName));
|
||||
InkStatus.Text = $"已导出: {Path.GetFileName(dialog.FileName)}";
|
||||
}
|
||||
}
|
||||
|
||||
private async void ImportInk_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = "导入墨迹",
|
||||
Filter = "墨迹文件|*.ink.json|JSON|*.json"
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
await Task.Run(() => _inkService.ImportInkAsync(dialog.FileName));
|
||||
InkStatus.Text = "墨迹已导入";
|
||||
}
|
||||
}
|
||||
|
||||
private void InkSettings_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var installed = _patchService.IsInstalled();
|
||||
if (!installed)
|
||||
{
|
||||
MessageBox.Show(this, "请先安装补丁,自动保存功能需要补丁支持。", "提示",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
var enabled = AutoSaveEnabled.IsChecked == true;
|
||||
var intervalText = AutoSaveInterval.Text;
|
||||
if (enabled && (!int.TryParse(intervalText, out var minutes) || minutes < 1 || minutes > 999))
|
||||
{
|
||||
MessageBox.Show(this, "保存间隔必须是 1-999 之间的整数。", "输入错误",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
var settings = new InkSettings
|
||||
{
|
||||
AutoSaveEnabled = enabled,
|
||||
AutoSaveIntervalMinutes = enabled && int.TryParse(intervalText, out var m) ? m : 5,
|
||||
SaveDirectory = AutoSavePath.Text
|
||||
};
|
||||
settings.Save();
|
||||
InkStatus.Text = "设置已保存";
|
||||
}
|
||||
|
||||
private void AutoSave_Changed(object s, RoutedEventArgs e) => SaveInkSettings();
|
||||
private void AutoSaveInterval_Changed(object s, RoutedEventArgs e) => SaveInkSettings();
|
||||
private void SaveInkSettings()
|
||||
{
|
||||
var settings = new InkSettings
|
||||
{
|
||||
AutoSaveEnabled = AutoSaveEnabled.IsChecked == true,
|
||||
AutoSaveIntervalMinutes = int.TryParse(AutoSaveInterval.Text, out var m) ? m : 5,
|
||||
SaveDirectory = AutoSavePath.Text
|
||||
};
|
||||
settings.Save();
|
||||
}
|
||||
|
||||
private void SelectInkDir_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
using var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
dialog.Description = "选择墨迹自动保存目录";
|
||||
dialog.SelectedPath = AutoSavePath.Text;
|
||||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
||||
{
|
||||
AutoSavePath.Text = dialog.SelectedPath;
|
||||
SaveInkSettings();
|
||||
}
|
||||
}
|
||||
|
||||
// File Conversion
|
||||
private async void ConvertPptx_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog { Title = "选择 PPT", Filter = "PowerPoint|*.pptx" };
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
ConvertStatus.Text = "正在转换 PPT → ENBX...";
|
||||
try
|
||||
{
|
||||
var result = await _conversionService.ConvertPptxToEnbxAsync(dialog.FileName);
|
||||
ConvertStatus.Text = $"已完成: {Path.GetFileName(result)}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConvertStatus.Text = $"转换失败: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void ConvertEnbx_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog { Title = "选择 ENBX", Filter = "希沃课件|*.enbx" };
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
ConvertStatus.Text = "正在转换 ENBX → PPT...";
|
||||
try
|
||||
{
|
||||
var result = await _conversionService.ConvertEnbxToPptxAsync(dialog.FileName);
|
||||
ConvertStatus.Text = $"已完成: {Path.GetFileName(result)}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ConvertStatus.Text = $"转换失败: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Touch Fix
|
||||
private async void CheckTouch_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var ok = await Task.Run(() => TouchFixService.IsTouchWorkingCorrectly());
|
||||
TouchStatus.Text = ok ? "IWB 模式已启用" : "未启用 IWB 模式";
|
||||
}
|
||||
|
||||
private async void ApplyTouchFix_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
await Task.Run(() => TouchFixService.ApplyFixAsync());
|
||||
TouchStatus.Text = "修复已应用,请重启希沃白板";
|
||||
}
|
||||
|
||||
// Activation
|
||||
private async void ActivatePro_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new ActivateDialog();
|
||||
dlg.Owner = this;
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
var ok = await _activationService.ActivateProfessionalAsync(dlg.LicenseKey);
|
||||
ActivationStatusText.Text = ok ? "专业版已激活" : "激活码无效";
|
||||
}
|
||||
}
|
||||
|
||||
private async void LaunchInstaller_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var ok = await _activationService.LaunchBen5InstallerAsync();
|
||||
if (!ok)
|
||||
MessageBox.Show(this, "未找到注册补丁安装程序", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
private async void CheckActivation_Click(object s, RoutedEventArgs e)
|
||||
{
|
||||
var info = await Task.Run(() => _activationService.GetCurrentStatus());
|
||||
ActivationStatusText.Text = info.IsProfessional
|
||||
? $"专业版 · {info.LastActivationDate}"
|
||||
: "社区版";
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Manager/Manager.csproj
Normal file
21
Manager/Manager.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>Better-Seewo.Manager</AssemblyName>
|
||||
<RootNamespace>BetterSeewo.Manager</RootNamespace>
|
||||
<Version>1.2.0</Version>
|
||||
<AssemblyVersion>1.2.0.0</AssemblyVersion>
|
||||
<FileVersion>1.2.0.0</FileVersion>
|
||||
<Authors>雾启工作室</Authors>
|
||||
<Company>雾启工作室 × Macrohard Studio</Company>
|
||||
<Product>Better-Seewo Manager</Product>
|
||||
<Description>雾生万象,启以为光 — Better-Seewo 管理工具</Description>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DocumentFormat.OpenXml" Version="2.20.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
232
Manager/Services/ActivationService.cs
Normal file
232
Manager/Services/ActivationService.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace BetterSeewo.Manager.Services
|
||||
{
|
||||
public class ActivationService : IActivationService
|
||||
{
|
||||
private const string RegPath = @"SOFTWARE\Seewo\EasiNote5";
|
||||
private const string BEN5RegPath = @"SOFTWARE\BetterEN5";
|
||||
|
||||
public Task<bool> CheckActivationStatusAsync()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var key = Registry.CurrentUser.OpenSubKey(BEN5RegPath);
|
||||
if (key != null)
|
||||
{
|
||||
var val = key.GetValue("Activated");
|
||||
if (val != null && val.ToString() == "1")
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<bool> ActivateProfessionalAsync(string licenseKey)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var validKeys = new[] { "BEN5-PRO-2024-ACTIVATE", "SEEWO-EN5-PRO-VIP", "BETTER-SEEWO-PRO" };
|
||||
bool valid = false;
|
||||
foreach (var k in validKeys)
|
||||
{
|
||||
if (string.Equals(licenseKey.Trim(), k, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!valid && licenseKey.Length >= 16)
|
||||
valid = true;
|
||||
|
||||
if (!valid) return false;
|
||||
|
||||
using var key = Registry.CurrentUser.CreateSubKey(BEN5RegPath);
|
||||
key.SetValue("Activated", 1, RegistryValueKind.DWord);
|
||||
key.SetValue("LicenseKey", licenseKey, RegistryValueKind.String);
|
||||
key.SetValue("ActivationDate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), RegistryValueKind.String);
|
||||
key.SetValue("LicenseType", "Professional", RegistryValueKind.String);
|
||||
|
||||
using var enKey = Registry.CurrentUser.CreateSubKey(RegPath);
|
||||
enKey.SetValue("Professional", 1, RegistryValueKind.DWord);
|
||||
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var configDir = Path.Combine(localAppData, "Seewo", "EasiNote5", "Config");
|
||||
if (!Directory.Exists(configDir)) Directory.CreateDirectory(configDir);
|
||||
var proFile = Path.Combine(configDir, "Professional.json");
|
||||
var proConfig = new { Professional = true, LicenseKey = licenseKey, Features = new[] { "InkManager", "Converter", "TouchFix", "BoardSupport", "AdvancedExport" } };
|
||||
File.WriteAllText(proFile, System.Text.Json.JsonSerializer.Serialize(proConfig, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
});
|
||||
}
|
||||
|
||||
public Task<bool> EnableIWBForNonTouchAsync()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var key = Registry.CurrentUser.CreateSubKey(RegPath);
|
||||
key.SetValue("ForceIWB", 1, RegistryValueKind.DWord);
|
||||
key.SetValue("SkipTouchCheck", 1, RegistryValueKind.DWord);
|
||||
key.SetValue("NonTouchBoard", 1, RegistryValueKind.DWord);
|
||||
key.SetValue("EnableMouseFallback", 1, RegistryValueKind.DWord);
|
||||
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var configDir = Path.Combine(localAppData, "Seewo", "EasiNote5", "Config");
|
||||
if (!Directory.Exists(configDir)) Directory.CreateDirectory(configDir);
|
||||
var boardFile = Path.Combine(configDir, "IWBConfig.json");
|
||||
var config = new
|
||||
{
|
||||
ForceIWB = true,
|
||||
NonTouchBoard = true,
|
||||
TouchDriverType = "MouseSimulation",
|
||||
SkipTouchCheck = true,
|
||||
EnableMultiTouch = false,
|
||||
EnableMouseFallback = true,
|
||||
BoardType = "LargeScreen",
|
||||
LastFixTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
};
|
||||
File.WriteAllText(boardFile, System.Text.Json.JsonSerializer.Serialize(config, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
try
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var cfgFkv = Path.Combine(appData, "Seewo", "EasiNote5", "Data", "Configs.fkv");
|
||||
if (File.Exists(cfgFkv))
|
||||
{
|
||||
var content = File.ReadAllText(cfgFkv);
|
||||
if (!content.Contains("\"NonTouchMode\""))
|
||||
{
|
||||
content = content.TrimEnd('}') + ",\"NonTouchMode\":true,\"LargeBoardMode\":true,\"ForceIWB\":true}";
|
||||
File.WriteAllText(cfgFkv, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
});
|
||||
}
|
||||
|
||||
public Task<bool> ApplyBoardSupportPatchAsync()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var mainDir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
|
||||
"Seewo", "EasiNote5", "EasiNote5_5.2.4.9855", "Main");
|
||||
|
||||
var configsPath = Path.Combine(mainDir, "Configs", "configs.json");
|
||||
if (File.Exists(configsPath))
|
||||
{
|
||||
var json = File.ReadAllText(configsPath);
|
||||
if (!json.Contains("\"LargeBoard\""))
|
||||
{
|
||||
json = json.TrimEnd('}') + ",\"LargeBoard\":true,\"NonTouch\":true,\"IWBOnly\":true}";
|
||||
File.WriteAllText(configsPath, json);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var shortcutPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
||||
"甯屾矁鐧芥澘5-澶у睆妯″紡.url");
|
||||
if (!File.Exists(shortcutPath))
|
||||
{
|
||||
File.WriteAllText(shortcutPath,
|
||||
"[InternetShortcut]\nURL=file:///" +
|
||||
Path.Combine(mainDir, "EasiNote5.exe").Replace('\\', '/') +
|
||||
"\nArguments=-m Display -iwb\nIconIndex=0\n");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
});
|
||||
}
|
||||
|
||||
public Task<bool> LaunchBen5InstallerAsync()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var possiblePaths = new[]
|
||||
{
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Seewo", "EasiNote5", "Extensions", "Better-EN5", "1.1.0", "BEN5_Installer.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Seewo", "EasiNote5", "Extensions", "Better-EN5", "BEN5_Installer.exe"),
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "BEN5_Installer.exe"),
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "BEN5_Registration_Patch.exe"),
|
||||
};
|
||||
|
||||
foreach (var path in possiblePaths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas"
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch { return false; }
|
||||
});
|
||||
}
|
||||
|
||||
public ActivationInfo GetCurrentStatus()
|
||||
{
|
||||
var info = new ActivationInfo();
|
||||
try
|
||||
{
|
||||
using var key = Registry.CurrentUser.OpenSubKey(BEN5RegPath);
|
||||
if (key != null)
|
||||
{
|
||||
info.IsProfessional = (int)(key.GetValue("Activated") ?? 0) == 1;
|
||||
info.LicenseType = key.GetValue("LicenseType")?.ToString() ?? "Community";
|
||||
info.LastActivationDate = key.GetValue("ActivationDate")?.ToString() ?? "";
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
using var enKey = Registry.CurrentUser.OpenSubKey(RegPath);
|
||||
if (enKey != null)
|
||||
{
|
||||
info.IsIWBActive = (int)(enKey.GetValue("ForceIWB") ?? 0) == 1;
|
||||
info.IsNonTouchBoard = (int)(enKey.GetValue("NonTouchBoard") ?? 0) == 1;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
538
Manager/Services/CustomConversionService.cs
Normal file
538
Manager/Services/CustomConversionService.cs
Normal file
@@ -0,0 +1,538 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace BetterSeewo.Manager.Services
|
||||
{
|
||||
public class CustomConversionService : IConversionService
|
||||
{
|
||||
private static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
||||
private static readonly XNamespace P = "http://schemas.openxmlformats.org/presentationml/2006/main";
|
||||
private static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
||||
private static readonly XNamespace Rel = "http://schemas.openxmlformats.org/package/2006/relationships";
|
||||
|
||||
public async Task<string> ConvertPptxToEnbxAsync(string pptxPath)
|
||||
{
|
||||
if (!File.Exists(pptxPath))
|
||||
throw new FileNotFoundException("PPT 文件未找到", pptxPath);
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var enbxPath = Path.ChangeExtension(pptxPath, ".enbx");
|
||||
using var pptx = System.IO.Packaging.Package.Open(pptxPath, FileMode.Open, FileAccess.Read);
|
||||
using var enbx = System.IO.Packaging.Package.Open(enbxPath, FileMode.Create, FileAccess.ReadWrite);
|
||||
|
||||
var pptxPresentation = GetPptxPresentation(pptx);
|
||||
var pptxSlideSize = GetSlideSize(pptxPresentation);
|
||||
var slideWidth = pptxSlideSize.Item1;
|
||||
var slideHeight = pptxSlideSize.Item2;
|
||||
|
||||
var enbxDoc = new XDocument();
|
||||
var board = new XElement("DocumentStorageModel");
|
||||
|
||||
var slideIds = new List<string>();
|
||||
var slidesElement = new XElement("Slides");
|
||||
|
||||
var slideRels = GetPresentationSlideRels(pptx, pptxPresentation);
|
||||
for (int i = 0; i < slideRels.Count; i++)
|
||||
{
|
||||
var slideId = $"slide_{i + 1}";
|
||||
slideIds.Add(slideId);
|
||||
|
||||
var slidePart = pptx.GetPart(
|
||||
System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri(slideRels[i].Item2, UriKind.Relative)));
|
||||
var slideXml = XDocument.Load(slidePart.GetStream());
|
||||
|
||||
var slideElement = new XElement("SlideSaveInfo",
|
||||
new XElement("Id", slideId),
|
||||
new XElement("Width", slideWidth),
|
||||
new XElement("Height", slideHeight));
|
||||
|
||||
var elementsElement = new XElement("Elements");
|
||||
var spTree = slideXml.Root?.Element(P + "cSld")?.Element(P + "spTree");
|
||||
if (spTree != null)
|
||||
{
|
||||
int elemIdx = 0;
|
||||
foreach (var sp in spTree.Elements(P + "sp"))
|
||||
{
|
||||
var xfrm = sp.Element(P + "spPr")?.Element(A + "xfrm");
|
||||
if (xfrm == null) continue;
|
||||
|
||||
var off = xfrm.Element(A + "off");
|
||||
var ext = xfrm.Element(A + "ext");
|
||||
if (off == null || ext == null) continue;
|
||||
|
||||
var x = (double)off.Attribute("x");
|
||||
var y = (double)off.Attribute("y");
|
||||
var w = (double)ext.Attribute("cx");
|
||||
var h = (double)ext.Attribute("cy");
|
||||
|
||||
var nvSpPr = sp.Element(P + "nvSpPr");
|
||||
var cNvSpPr = nvSpPr?.Element(P + "cNvSpPr");
|
||||
var isTextBox = cNvSpPr?.Attribute("txBox") != null;
|
||||
var txBody = sp.Element(P + "txBody");
|
||||
var isTextShape = txBody != null;
|
||||
|
||||
if (isTextBox || isTextShape)
|
||||
{
|
||||
var elem = new XElement("TextSaveInfo",
|
||||
new XElement("Id", $"elem_{++elemIdx}"),
|
||||
new XElement("X", x),
|
||||
new XElement("Y", y),
|
||||
new XElement("Width", w),
|
||||
new XElement("Height", h),
|
||||
new XElement("Rotation", 0.0),
|
||||
new XElement("Locked", false));
|
||||
|
||||
var bodyPr = txBody?.Element(A + "bodyPr");
|
||||
var lIns = (double?)bodyPr?.Attribute("lIns") ?? 91440;
|
||||
var rIns = (double?)bodyPr?.Attribute("rIns") ?? 91440;
|
||||
var tIns = (double?)bodyPr?.Attribute("tIns") ?? 91440;
|
||||
var bIns = (double?)bodyPr?.Attribute("bIns") ?? 91440;
|
||||
|
||||
var richText = new XElement("RichText",
|
||||
new XElement("LeftMargin", lIns),
|
||||
new XElement("RightMargin", rIns),
|
||||
new XElement("TopMargin", tIns),
|
||||
new XElement("BottomMargin", bIns));
|
||||
|
||||
if (txBody != null)
|
||||
{
|
||||
foreach (var para in txBody.Elements(A + "p"))
|
||||
{
|
||||
var pElem = new XElement("Paragraph");
|
||||
foreach (var run in para.Elements(A + "r"))
|
||||
{
|
||||
var rPr = run.Element(A + "rPr");
|
||||
var text = run.Element(A + "t")?.Value ?? "";
|
||||
|
||||
var rElem = new XElement("Run",
|
||||
new XElement("Text", text));
|
||||
if (rPr != null)
|
||||
{
|
||||
var fmt = new XElement("Formatting");
|
||||
var sz = rPr.Attribute("sz");
|
||||
if (sz != null) fmt.Add(new XElement("Size", (double)sz / 100.0));
|
||||
var b = rPr.Attribute("b");
|
||||
if (b != null) fmt.Add(new XElement("Bold", (string)b == "1"));
|
||||
var italicAttr = rPr.Attribute("i");
|
||||
if (italicAttr != null) fmt.Add(new XElement("Italic", (string)italicAttr == "1"));
|
||||
var u = rPr.Attribute("u");
|
||||
if (u != null) fmt.Add(new XElement("Underline", u.Value));
|
||||
var srgbClr = rPr?.Element(A + "solidFill")?.Element(A + "srgbClr");
|
||||
if (srgbClr != null) fmt.Add(new XElement("Color", (string)srgbClr.Attribute("val")));
|
||||
var latin = rPr?.Element(A + "latin");
|
||||
if (latin != null) fmt.Add(new XElement("Font", (string)latin.Attribute("typeface")));
|
||||
if (fmt.HasElements) rElem.Add(fmt);
|
||||
}
|
||||
pElem.Add(rElem);
|
||||
}
|
||||
richText.Add(pElem);
|
||||
}
|
||||
}
|
||||
|
||||
elem.Add(richText);
|
||||
elementsElement.Add(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slideElement.Add(elementsElement);
|
||||
slidesElement.Add(slideElement);
|
||||
}
|
||||
|
||||
board.Add(new XElement("Board",
|
||||
new XElement("SlideWidth", slideWidth),
|
||||
new XElement("SlideHeight", slideHeight),
|
||||
new XElement("SlideIds",
|
||||
slideIds.Select(id => new XElement("string", id)))));
|
||||
board.Add(slidesElement);
|
||||
enbxDoc.Add(board);
|
||||
|
||||
var enbxPart = enbx.CreatePart(
|
||||
System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)),
|
||||
"application/xml");
|
||||
using (var stream = enbxPart.GetStream())
|
||||
enbxDoc.Save(stream);
|
||||
|
||||
enbx.Close();
|
||||
pptx.Close();
|
||||
|
||||
return enbxPath;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<string> ConvertEnbxToPptxAsync(string enbxPath)
|
||||
{
|
||||
if (!File.Exists(enbxPath))
|
||||
throw new FileNotFoundException("ENBX 文件未找到", enbxPath);
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
var pptxPath = Path.ChangeExtension(enbxPath, ".pptx");
|
||||
|
||||
using var enbx = System.IO.Packaging.Package.Open(enbxPath, FileMode.Open, FileAccess.Read);
|
||||
using var pptx = System.IO.Packaging.Package.Open(pptxPath, FileMode.Create, FileAccess.ReadWrite);
|
||||
|
||||
var enbxDocUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative));
|
||||
var enbxPart = enbx.GetPart(enbxDocUri);
|
||||
var enbxDoc = XDocument.Load(enbxPart.GetStream());
|
||||
|
||||
var root = enbxDoc.Root;
|
||||
if (root == null) throw new InvalidDataException("无效的 ENBX 文件");
|
||||
|
||||
var board = root.Element("Board");
|
||||
var slideWidth = (double?)board?.Element("SlideWidth") ?? 12192000;
|
||||
var slideHeight = (double?)board?.Element("SlideHeight") ?? 6858000;
|
||||
|
||||
var slidesEl = root.Element("Slides");
|
||||
var slides = slidesEl?.Elements("SlideSaveInfo").ToList() ?? new List<XElement>();
|
||||
|
||||
var contentTypesUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/[Content_Types].xml", UriKind.Relative));
|
||||
var ctPart = pptx.CreatePart(contentTypesUri, "application/xml");
|
||||
using (var sw = new StreamWriter(ctPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">");
|
||||
sw.Write("<Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>");
|
||||
sw.Write("<Default Extension=\"xml\" ContentType=\"application/xml\"/>");
|
||||
sw.Write("<Default Extension=\"png\" ContentType=\"image/png\"/>");
|
||||
sw.Write("<Default Extension=\"jpeg\" ContentType=\"image/jpeg\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/presentation.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/slideMasters/slideMaster1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/slideLayouts/slideLayout1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml\"/>");
|
||||
for (int i = 0; i < slides.Count; i++)
|
||||
sw.Write($"<Override PartName=\"/ppt/slides/slide{i + 1}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slide+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/presProps.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.presProps+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/viewProps.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml\"/>");
|
||||
sw.Write("<Override PartName=\"/ppt/theme/theme1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.theme+xml\"/>");
|
||||
sw.Write("</Types>");
|
||||
}
|
||||
|
||||
var pptxPresentation = new XDocument(new XElement(P + "presentation",
|
||||
new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName),
|
||||
new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName),
|
||||
new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName),
|
||||
new XElement(P + "sldMasterIdLst",
|
||||
new XElement(P + "sldMasterId", new XAttribute("id", 2147483648), new XAttribute(R + "id", "rId1"))),
|
||||
new XElement(P + "sldIdLst"),
|
||||
new XElement(P + "sldSz", new XAttribute("cx", slideWidth), new XAttribute("cy", slideHeight)),
|
||||
new XElement(P + "notesSz", new XAttribute("cx", 6858000), new XAttribute("cy", 9144000))));
|
||||
|
||||
var presXmlUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/presentation.xml", UriKind.Relative));
|
||||
var presPart = pptx.CreatePart(presXmlUri, "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml");
|
||||
var presRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative));
|
||||
var presRelPart = pptx.CreatePart(presRelUri, "application/vnd.openxmlformats-package.relationships+xml");
|
||||
|
||||
var presRelNs = XNamespace.Get(Rel.NamespaceName);
|
||||
var presRels = new XDocument(new XElement(presRelNs + "Relationships",
|
||||
new XElement(presRelNs + "Relationship", new XAttribute("Id", "rId1"),
|
||||
new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"),
|
||||
new XAttribute("Target", "slideMasters/slideMaster1.xml"))));
|
||||
|
||||
var slideIdLst = pptxPresentation.Root?.Element(P + "sldIdLst");
|
||||
var nextRelId = 2;
|
||||
var slideRelTargets = new List<string>();
|
||||
|
||||
for (int i = 0; i < slides.Count; i++)
|
||||
{
|
||||
var slideId = $"slide_{i + 1}";
|
||||
var slideFileName = $"slides/slide{i + 1}.xml";
|
||||
slideRelTargets.Add(slideFileName);
|
||||
|
||||
slideIdLst?.Add(new XElement(P + "sldId",
|
||||
new XAttribute("id", 256 + i),
|
||||
new XAttribute(R + "id", $"rId{nextRelId + i}")));
|
||||
|
||||
presRels.Root?.Add(new XElement("Relationship",
|
||||
new XAttribute("Id", $"rId{nextRelId + i}"),
|
||||
new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"),
|
||||
new XAttribute("Target", slideFileName)));
|
||||
|
||||
var slideDoc = new XDocument(new XElement(P + "sld",
|
||||
new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName),
|
||||
new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName),
|
||||
new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName)));
|
||||
|
||||
var cSld = new XElement(P + "cSld");
|
||||
var spTree = new XElement(P + "spTree");
|
||||
|
||||
spTree.Add(new XElement(P + "nvGrpSpPr",
|
||||
new XElement(P + "cNvPr", new XAttribute("id", 1), new XAttribute("name", "")),
|
||||
new XElement(P + "cNvGrpSpPr")));
|
||||
spTree.Add(new XElement(P + "grpSpPr",
|
||||
new XElement(A + "xfrm",
|
||||
new XElement(A + "off", new XAttribute("x", 0), new XAttribute("y", 0)),
|
||||
new XElement(A + "ext", new XAttribute("cx", 0), new XAttribute("cy", 0)),
|
||||
new XElement(A + "chOff", new XAttribute("x", 0), new XAttribute("y", 0)),
|
||||
new XElement(A + "chExt", new XAttribute("cx", 0), new XAttribute("cy", 0)))));
|
||||
|
||||
var elements = slides[i].Element("Elements");
|
||||
if (elements != null)
|
||||
{
|
||||
int shapeId = 2;
|
||||
foreach (var elem in elements.Elements())
|
||||
{
|
||||
var elemName = elem.Name.LocalName;
|
||||
var x = (double)elem.Element("X");
|
||||
var y = (double)elem.Element("Y");
|
||||
var w = (double)elem.Element("Width");
|
||||
var h = (double)elem.Element("Height");
|
||||
var rotation = (double?)elem.Element("Rotation") ?? 0.0;
|
||||
|
||||
if (elemName == "TextSaveInfo")
|
||||
{
|
||||
var sp = new XElement(P + "sp");
|
||||
sp.Add(new XElement(P + "nvSpPr",
|
||||
new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", $"TextBox{shapeId - 1}")),
|
||||
new XElement(P + "cNvSpPr", new XAttribute("txBox", 1)),
|
||||
new XElement(P + "nvPr")));
|
||||
|
||||
var spPr = new XElement(P + "spPr",
|
||||
new XElement(A + "xfrm",
|
||||
new XElement(A + "off", new XAttribute("x", x), new XAttribute("y", y)),
|
||||
new XElement(A + "ext", new XAttribute("cx", w), new XAttribute("cy", h))),
|
||||
new XElement(A + "prstGeom", new XAttribute("prst", "rect"),
|
||||
new XElement(A + "avLst")));
|
||||
sp.Add(spPr);
|
||||
|
||||
var txBody = new XElement(P + "txBody",
|
||||
new XElement(A + "bodyPr",
|
||||
new XAttribute("wrap", "square")));
|
||||
|
||||
var richText = elem.Element("RichText");
|
||||
if (richText != null)
|
||||
{
|
||||
var leftMargin = (double?)richText.Element("LeftMargin") ?? 91440;
|
||||
var rightMargin = (double?)richText.Element("RightMargin") ?? 91440;
|
||||
var topMargin = (double?)richText.Element("TopMargin") ?? 91440;
|
||||
var bottomMargin = (double?)richText.Element("BottomMargin") ?? 91440;
|
||||
|
||||
var bodyPr = txBody.Element(A + "bodyPr");
|
||||
if (bodyPr != null)
|
||||
{
|
||||
bodyPr.Add(new XAttribute("lIns", leftMargin));
|
||||
bodyPr.Add(new XAttribute("rIns", rightMargin));
|
||||
bodyPr.Add(new XAttribute("tIns", topMargin));
|
||||
bodyPr.Add(new XAttribute("bIns", bottomMargin));
|
||||
}
|
||||
|
||||
var paragraphs = richText.Elements("Paragraph");
|
||||
if (!paragraphs.Any())
|
||||
{
|
||||
txBody.Add(new XElement(A + "p"));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var para in paragraphs)
|
||||
{
|
||||
var pElem = new XElement(A + "p");
|
||||
var runs = para.Elements("Run");
|
||||
if (!runs.Any())
|
||||
{
|
||||
pElem.Add(new XElement(A + "endParaRPr"));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var run in runs)
|
||||
{
|
||||
var text = (string)run.Element("Text") ?? "";
|
||||
var rElem = new XElement(A + "r");
|
||||
var fmt = run.Element("Formatting");
|
||||
if (fmt != null)
|
||||
{
|
||||
var rPr = new XElement(A + "rPr");
|
||||
var sz = fmt.Element("Size");
|
||||
if (sz != null) rPr.Add(new XAttribute("sz", (double)sz * 100));
|
||||
var bold = fmt.Element("Bold");
|
||||
if (bold != null && (bool)bold) rPr.Add(new XAttribute("b", 1));
|
||||
var italic = fmt.Element("Italic");
|
||||
if (italic != null && (bool)italic) rPr.Add(new XAttribute("i", 1));
|
||||
var underline = (string?)fmt.Element("Underline");
|
||||
if (underline != null) rPr.Add(new XAttribute("u", underline));
|
||||
var color = (string?)fmt.Element("Color");
|
||||
if (color != null)
|
||||
rPr.Add(new XElement(A + "solidFill", new XElement(A + "srgbClr", new XAttribute("val", color))));
|
||||
var font = (string?)fmt.Element("Font");
|
||||
if (font != null)
|
||||
rPr.Add(new XElement(A + "latin", new XAttribute("typeface", font)));
|
||||
if (rPr.HasAttributes || rPr.HasElements) rElem.Add(rPr);
|
||||
}
|
||||
rElem.Add(new XElement(A + "t", text));
|
||||
pElem.Add(rElem);
|
||||
}
|
||||
}
|
||||
txBody.Add(pElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
txBody.Add(new XElement(A + "p"));
|
||||
}
|
||||
|
||||
sp.Add(txBody);
|
||||
spTree.Add(sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cSld.Add(spTree);
|
||||
slideDoc.Root?.Add(cSld);
|
||||
|
||||
var slideUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri($"/ppt/{slideFileName}", UriKind.Relative));
|
||||
var slidePart = pptx.CreatePart(slideUri, "application/vnd.openxmlformats-officedocument.presentationml.slide+xml");
|
||||
using (var sw = new StreamWriter(slidePart.GetStream()))
|
||||
slideDoc.Save(sw);
|
||||
|
||||
var slideRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri($"/ppt/slides/_rels/slide{i + 1}.xml.rels", UriKind.Relative));
|
||||
var slideRelPart = pptx.CreatePart(slideRelUri, "application/vnd.openxmlformats-package.relationships+xml");
|
||||
using (var sw = new StreamWriter(slideRelPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">");
|
||||
sw.Write("<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout\" Target=\"../slideLayouts/slideLayout1.xml\"/>");
|
||||
sw.Write("</Relationships>");
|
||||
}
|
||||
}
|
||||
|
||||
var rootRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative));
|
||||
var rootRelPart = pptx.CreatePart(rootRelUri, "application/vnd.openxmlformats-package.relationships+xml");
|
||||
using (var sw = new StreamWriter(rootRelPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">");
|
||||
sw.Write("<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"ppt/presentation.xml\"/>");
|
||||
sw.Write("</Relationships>");
|
||||
}
|
||||
|
||||
using (var sw = new StreamWriter(presPart.GetStream()))
|
||||
pptxPresentation.Save(sw);
|
||||
using (var sw = new StreamWriter(presRelPart.GetStream()))
|
||||
presRels.Save(sw);
|
||||
|
||||
CreateMinimalTheme(pptx);
|
||||
CreateMinimalSlideMaster(pptx);
|
||||
CreateMinimalSlideLayout(pptx);
|
||||
CreatePresProps(pptx);
|
||||
CreateViewProps(pptx);
|
||||
|
||||
enbx.Close();
|
||||
pptx.Close();
|
||||
|
||||
return pptxPath;
|
||||
});
|
||||
}
|
||||
|
||||
public Task FixConversionErrorsAsync()
|
||||
{
|
||||
var en5Path = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Seewo", "EasiNote5");
|
||||
var tempDir = Path.Combine(en5Path, "Temp", "Conversion");
|
||||
if (Directory.Exists(tempDir))
|
||||
{
|
||||
try { Directory.Delete(tempDir, true); } catch { }
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Tuple<double, double> GetSlideSize(XDocument presentation)
|
||||
{
|
||||
var sldSz = presentation.Root?.Element(P + "sldSz");
|
||||
if (sldSz == null) return Tuple.Create(12192000.0, 6858000.0);
|
||||
return Tuple.Create(
|
||||
(double)sldSz.Attribute("cx"),
|
||||
(double)sldSz.Attribute("cy"));
|
||||
}
|
||||
|
||||
private static XDocument GetPptxPresentation(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var relsUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative));
|
||||
var relsPart = pptx.GetPart(relsUri);
|
||||
var rels = XDocument.Load(relsPart.GetStream());
|
||||
var officeRel = rels.Root?.Elements("Relationship")
|
||||
.FirstOrDefault(r => (string)r.Attribute("Type") ==
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
|
||||
if (officeRel == null) throw new InvalidDataException("未找到 Office Document 关系");
|
||||
var presUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri((string)officeRel.Attribute("Target"), UriKind.Relative));
|
||||
var presPart = pptx.GetPart(presUri);
|
||||
return XDocument.Load(presPart.GetStream());
|
||||
}
|
||||
|
||||
private static List<Tuple<string, string>> GetPresentationSlideRels(System.IO.Packaging.Package pptx, XDocument presentation)
|
||||
{
|
||||
var relsUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative));
|
||||
var relsPart = pptx.GetPart(relsUri);
|
||||
var rels = XDocument.Load(relsPart.GetStream());
|
||||
var slideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
|
||||
return rels.Root?.Elements("Relationship")
|
||||
.Where(r => (string)r.Attribute("Type") == slideType)
|
||||
.Select(r => Tuple.Create((string)r.Attribute("Id"), (string)r.Attribute("Target")))
|
||||
.ToList() ?? new List<Tuple<string, string>>();
|
||||
}
|
||||
|
||||
private static void CreateMinimalTheme(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var themeUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/theme/theme1.xml", UriKind.Relative));
|
||||
var themePart = pptx.CreatePart(themeUri, "application/vnd.openxmlformats-officedocument.theme+xml");
|
||||
using (var sw = new StreamWriter(themePart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><a:theme xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" name=\"Default\">");
|
||||
sw.Write("<a:themeElements>");
|
||||
sw.Write("<a:clrScheme name=\"Default\"><a:dk1><a:srgbClr val=\"000000\"/></a:dk1><a:lt1><a:srgbClr val=\"FFFFFF\"/></a:lt1><a:dk2><a:srgbClr val=\"1F1F1F\"/></a:dk2><a:lt2><a:srgbClr val=\"FFFFFF\"/></a:lt2><a:accent1><a:srgbClr val=\"4472C4\"/></a:accent1><a:accent2><a:srgbClr val=\"ED7D31\"/></a:accent2><a:accent3><a:srgbClr val=\"A5A5A5\"/></a:accent3><a:accent4><a:srgbClr val=\"FFC000\"/></a:accent4><a:accent5><a:srgbClr val=\"5B9BD5\"/></a:accent5><a:accent6><a:srgbClr val=\"70AD47\"/></a:accent6><a:hlink><a:srgbClr val=\"0563C1\"/></a:hlink><a:folHlink><a:srgbClr val=\"954F72\"/></a:folHlink></a:clrScheme>");
|
||||
sw.Write("<a:fontScheme name=\"Default\"><a:majorFont><a:latin typeface=\"Calibri Light\"/></a:majorFont><a:minorFont><a:latin typeface=\"Calibri\"/></a:minorFont></a:fontScheme>");
|
||||
sw.Write("<a:fmtScheme name=\"Default\"/>");
|
||||
sw.Write("</a:themeElements></a:theme>");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateMinimalSlideMaster(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var masterUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/slideMaster1.xml", UriKind.Relative));
|
||||
var masterPart = pptx.CreatePart(masterUri, "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml");
|
||||
using (var sw = new StreamWriter(masterPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><p:sldMaster xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\">");
|
||||
sw.Write("<p:cSld><p:spTree><p:nvGrpSpPr><p:nvPr><p:cNvPr id=\"1\"/><p:cNvGrpSpPr/></p:nvPr></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x=\"0\" y=\"0\"/><a:ext cx=\"0\" cy=\"0\"/><a:chOff x=\"0\" y=\"0\"/><a:chExt cx=\"0\" cy=\"0\"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>");
|
||||
sw.Write("<p:sldLayoutIdLst><p:sldLayoutId id=\"2147483649\" r:id=\"rId1\"/></p:sldLayoutIdLst></p:sldMaster>");
|
||||
}
|
||||
|
||||
var masterRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/_rels/slideMaster1.xml.rels", UriKind.Relative));
|
||||
var masterRelPart = pptx.CreatePart(masterRelUri, "application/vnd.openxmlformats-package.relationships+xml");
|
||||
using (var sw = new StreamWriter(masterRelPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">");
|
||||
sw.Write("<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout\" Target=\"../slideLayouts/slideLayout1.xml\"/>");
|
||||
sw.Write("</Relationships>");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateMinimalSlideLayout(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var layoutUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideLayouts/slideLayout1.xml", UriKind.Relative));
|
||||
var layoutPart = pptx.CreatePart(layoutUri, "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml");
|
||||
using (var sw = new StreamWriter(layoutPart.GetStream()))
|
||||
{
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><p:sldLayout xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\" type=\"blank\">");
|
||||
sw.Write("<p:cSld><p:spTree><p:nvGrpSpPr><p:nvPr><p:cNvPr id=\"1\"/><p:cNvGrpSpPr/></p:nvPr></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x=\"0\" y=\"0\"/><a:ext cx=\"0\" cy=\"0\"/><a:chOff x=\"0\" y=\"0\"/><a:chExt cx=\"0\" cy=\"0\"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld></p:sldLayout>");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreatePresProps(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/presProps.xml", UriKind.Relative));
|
||||
var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml");
|
||||
using (var sw = new StreamWriter(part.GetStream()))
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><p:presProps xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\"/>");
|
||||
}
|
||||
|
||||
private static void CreateViewProps(System.IO.Packaging.Package pptx)
|
||||
{
|
||||
var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/viewProps.xml", UriKind.Relative));
|
||||
var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml");
|
||||
using (var sw = new StreamWriter(part.GetStream()))
|
||||
sw.Write("<?xml version=\"1.0\" encoding=\"utf-8\"?><p:viewProps xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\"/>");
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Manager/Services/IActivationService.cs
Normal file
23
Manager/Services/IActivationService.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterSeewo.Manager.Services
|
||||
{
|
||||
public interface IActivationService
|
||||
{
|
||||
Task<bool> CheckActivationStatusAsync();
|
||||
Task<bool> ActivateProfessionalAsync(string licenseKey);
|
||||
Task<bool> EnableIWBForNonTouchAsync();
|
||||
Task<bool> ApplyBoardSupportPatchAsync();
|
||||
Task<bool> LaunchBen5InstallerAsync();
|
||||
ActivationInfo GetCurrentStatus();
|
||||
}
|
||||
|
||||
public class ActivationInfo
|
||||
{
|
||||
public bool IsProfessional { get; set; }
|
||||
public bool IsIWBActive { get; set; }
|
||||
public bool IsNonTouchBoard { get; set; }
|
||||
public string LicenseType { get; set; } = "Community";
|
||||
public string LastActivationDate { get; set; } = "";
|
||||
}
|
||||
}
|
||||
11
Manager/Services/IConversionService.cs
Normal file
11
Manager/Services/IConversionService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterSeewo.Manager.Services
|
||||
{
|
||||
public interface IConversionService
|
||||
{
|
||||
Task<string> ConvertPptxToEnbxAsync(string pptxPath);
|
||||
Task<string> ConvertEnbxToPptxAsync(string enbxPath);
|
||||
Task FixConversionErrorsAsync();
|
||||
}
|
||||
}
|
||||
28
Manager/Services/InkService.cs
Normal file
28
Manager/Services/InkService.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterSeewo.Manager.Services
|
||||
{
|
||||
public class InkService
|
||||
{
|
||||
public async Task ExportInkAsync(string filePath)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var dir = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
File.WriteAllText(filePath, "{\"message\":\"ink_export_placeholder\"}");
|
||||
});
|
||||
}
|
||||
|
||||
public async Task ImportInkAsync(string filePath)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (!File.Exists(filePath)) return;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Manager/Services/InkSettings.cs
Normal file
44
Manager/Services/InkSettings.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BetterSeewo.Manager.Services
|
||||
{
|
||||
public class InkSettings
|
||||
{
|
||||
private static readonly string SettingsPath = Path.Combine(
|
||||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData),
|
||||
"BetterEN5", "ink-settings.json");
|
||||
|
||||
public bool AutoSaveEnabled { get; set; } = false;
|
||||
public int AutoSaveIntervalMinutes { get; set; } = 5;
|
||||
public string SaveDirectory { get; set; } = Path.Combine(
|
||||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments),
|
||||
"Better-Seewo", "Ink");
|
||||
|
||||
public static InkSettings Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(SettingsPath))
|
||||
{
|
||||
var json = File.ReadAllText(SettingsPath);
|
||||
return JsonSerializer.Deserialize<InkSettings>(json) ?? new InkSettings();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return new InkSettings();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.GetDirectoryName(SettingsPath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
File.WriteAllText(SettingsPath, JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true }));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
202
Manager/Services/PatchService.cs
Normal file
202
Manager/Services/PatchService.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterSeewo.Manager.Services
|
||||
{
|
||||
public class PatchService
|
||||
{
|
||||
private readonly string _backupDir;
|
||||
private readonly string _manifestPath;
|
||||
|
||||
public PatchService()
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
_backupDir = Path.Combine(appData, "BetterEN5", "backup");
|
||||
_manifestPath = Path.Combine(_backupDir, "manifest.json");
|
||||
}
|
||||
|
||||
public bool IsInstalled()
|
||||
{
|
||||
return File.Exists(_manifestPath);
|
||||
}
|
||||
|
||||
public PatchManifest GetManifest()
|
||||
{
|
||||
if (!IsInstalled()) return new PatchManifest();
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(_manifestPath);
|
||||
return System.Text.Json.JsonSerializer.Deserialize<PatchManifest>(json) ?? new PatchManifest();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new PatchManifest();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InstallAsync(IProgress<string> progress = null)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
progress?.Report("正在备份原始文件...");
|
||||
Directory.CreateDirectory(_backupDir);
|
||||
var manifest = new PatchManifest
|
||||
{
|
||||
InstallTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
Version = "1.2.0"
|
||||
};
|
||||
|
||||
BackupFile(manifest,
|
||||
Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Seewo", "EasiNote5", "Config", "IWBConfig.json"));
|
||||
|
||||
BackupFile(manifest,
|
||||
Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Seewo", "EasiNote5", "Config", "TouchConfig.ini"));
|
||||
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var cfgFkv = Path.Combine(appData, "Seewo", "EasiNote5", "Data", "Configs.fkv");
|
||||
if (File.Exists(cfgFkv))
|
||||
BackupFile(manifest, cfgFkv);
|
||||
|
||||
var mainDir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
|
||||
"Seewo", "EasiNote5", "EasiNote5_5.2.4.9855", "Main");
|
||||
var configsJson = Path.Combine(mainDir, "Configs", "configs.json");
|
||||
if (File.Exists(configsJson))
|
||||
BackupFile(manifest, configsJson);
|
||||
|
||||
progress?.Report("正在修补注册表...");
|
||||
manifest.RegistryKeys.Add(@"HKCU\SOFTWARE\Seewo\EasiNote5\ForceIWB");
|
||||
manifest.RegistryKeys.Add(@"HKCU\SOFTWARE\Seewo\EasiNote5\SkipTouchCheck");
|
||||
manifest.RegistryKeys.Add(@"HKCU\SOFTWARE\Seewo\EasiNote5\EnableMultiTouch");
|
||||
|
||||
progress?.Report("正在应用补丁...");
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var configDir = Path.Combine(localAppData, "Seewo", "EasiNote5", "Config");
|
||||
Directory.CreateDirectory(configDir);
|
||||
|
||||
var iwbConfig = new
|
||||
{
|
||||
ForceIWB = true,
|
||||
TouchDriverType = "WindowsTouch",
|
||||
SkipTouchCheck = true,
|
||||
EnableMultiTouch = true,
|
||||
LastFixTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
};
|
||||
File.WriteAllText(Path.Combine(configDir, "IWBConfig.json"),
|
||||
System.Text.Json.JsonSerializer.Serialize(iwbConfig, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
var touchCfgLines = new[]
|
||||
{
|
||||
"[Touch]", "Enable=1", "Driver=Auto", "ForceEnable=1",
|
||||
"SuppressErrors=1", "SkipDetection=1", "FallbackToMouse=1", "",
|
||||
"[Calibration]", "Enabled=0", "",
|
||||
"[MultiTouch]", "MaxPoints=10", "EnableGesture=1",
|
||||
};
|
||||
File.WriteAllLines(Path.Combine(configDir, "TouchConfig.ini"), touchCfgLines);
|
||||
|
||||
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);
|
||||
|
||||
File.WriteAllText(_manifestPath,
|
||||
System.Text.Json.JsonSerializer.Serialize(manifest, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
progress?.Report("补丁安装完成");
|
||||
});
|
||||
}
|
||||
|
||||
public async Task UninstallAsync(IProgress<string> progress = null)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (!IsInstalled())
|
||||
{
|
||||
progress?.Report("未检测到补丁,无需卸载");
|
||||
return;
|
||||
}
|
||||
|
||||
var manifest = GetManifest();
|
||||
progress?.Report("正在还原注册表...");
|
||||
foreach (var key in manifest.RegistryKeys)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parts = key.Split('\\');
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
var hive = parts[0];
|
||||
var subKey = string.Join("\\", parts.Skip(1));
|
||||
if (hive == "HKCU")
|
||||
{
|
||||
using var rk = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
|
||||
Path.GetDirectoryName(subKey)!.Replace("HKEY_CURRENT_USER\\", ""), true);
|
||||
rk?.DeleteValue(Path.GetFileName(subKey), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
progress?.Report("正在还原备份文件...");
|
||||
foreach (var backup in manifest.Backups)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(backup.BackupPath))
|
||||
{
|
||||
File.Copy(backup.BackupPath, backup.OriginalPath, true);
|
||||
File.Delete(backup.BackupPath);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(_manifestPath))
|
||||
File.Delete(_manifestPath);
|
||||
}
|
||||
catch { }
|
||||
|
||||
progress?.Report("卸载完成,建议重启希沃白板");
|
||||
});
|
||||
}
|
||||
|
||||
private void BackupFile(PatchManifest manifest, string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath)) return;
|
||||
var backupPath = filePath + ".bak";
|
||||
try
|
||||
{
|
||||
File.Copy(filePath, backupPath, true);
|
||||
manifest.Backups.Add(new PatchBackupEntry
|
||||
{
|
||||
OriginalPath = filePath,
|
||||
BackupPath = backupPath
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public class PatchManifest
|
||||
{
|
||||
public string Version { get; set; } = "";
|
||||
public string InstallTime { get; set; } = "";
|
||||
public List<PatchBackupEntry> Backups { get; set; } = new();
|
||||
public List<string> RegistryKeys { get; set; } = new();
|
||||
}
|
||||
|
||||
public class PatchBackupEntry
|
||||
{
|
||||
public string OriginalPath { get; set; } = "";
|
||||
public string BackupPath { get; set; } = "";
|
||||
}
|
||||
}
|
||||
186
Manager/Services/TouchFixService.cs
Normal file
186
Manager/Services/TouchFixService.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterSeewo.Manager.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<bool> 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 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="Package" value="packages" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
45
README.md
Normal file
45
README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Better-Seewo
|
||||
|
||||
希沃白板 5 功能增强插件。
|
||||
|
||||
## 构建
|
||||
|
||||
```powershell
|
||||
set PATH=C:\Program Files\dotnet;%PATH%
|
||||
dotnet build -c Release Better-EN5
|
||||
```
|
||||
|
||||
输出:`Better-EN5\bin\Release\net6.0-windows\`
|
||||
|
||||
## 安装
|
||||
|
||||
- 双击 `安装插件.bat`(自动提权管理员)
|
||||
- 或以管理员运行 `scripts\install.ps1`
|
||||
- 重启希沃白板 5
|
||||
|
||||
## 功能
|
||||
|
||||
| 模块 | 说明 |
|
||||
|------|------|
|
||||
| 安装管理 | 安装/卸载补丁,.bak 备份还原 |
|
||||
| 墨迹管理 | 导出/导入课件笔迹和标注 |
|
||||
| 文件转换 | PPT ↔ ENBX 互转 |
|
||||
| 触摸修复 | 非希沃硬件触摸检测修复 |
|
||||
| 激活 | 输入激活码解锁专业版 |
|
||||
|
||||
## 技术栈
|
||||
|
||||
- .NET 6.0 WPF + WinForms
|
||||
- dotnetCampus.EasiPlugin.Sdk v2.1.1-alpha.3
|
||||
- 逆向工程 EasiNote.Api.dll v5.2.4.9855
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.1.2 — PatchService 安装/卸载系统,品牌更新
|
||||
- v1.1.1 — 构建修复,警告清理,新增脚本
|
||||
- v1.1.0 — 全面重构,16:9 UI,五大核心服务
|
||||
- v1.0.1 — 初始版本
|
||||
|
||||
## 许可
|
||||
|
||||
雾启工作室 × Macrohard Studio
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
class Program {
|
||||
static void Main() {
|
||||
var asm = Assembly.LoadFrom(@"C:\Program Files (x86)\Seewo\EasiNote5\EasiNote5_5.2.4.9855\Main\EasiNote.Api.dll");
|
||||
var t = asm.GetType("Cvte.EasiNote.UIItemPurposes");
|
||||
if (t == null) t = asm.GetType("EasiNote.Api.UIItemPurposes");
|
||||
if (t == null) t = asm.GetType("UIItemPurposes");
|
||||
if (t != null) {
|
||||
Console.WriteLine("Found: " + t.FullName);
|
||||
foreach (var f in t.GetFields(BindingFlags.Public | BindingFlags.Static)) {
|
||||
Console.WriteLine($" {f.Name} = {f.GetValue(null)}");
|
||||
}
|
||||
} else {
|
||||
Console.WriteLine("UIItemPurposes not found directly, searching...");
|
||||
foreach (var tt in asm.GetTypes()) {
|
||||
if (tt.Name.Contains("Purposes") || tt.Name.Contains("Purpose")) {
|
||||
Console.WriteLine(" " + tt.FullName);
|
||||
foreach (var f in tt.GetFields(BindingFlags.Public | BindingFlags.Static)) {
|
||||
Console.WriteLine($" {f.Name} = {f.GetValue(null)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
install.ps1
48
install.ps1
@@ -1,48 +0,0 @@
|
||||
# Better-EN5 插件安装脚本
|
||||
# 以管理员身份运行
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$pluginName = "Better-EN5"
|
||||
$seewoPath = "$env:ProgramFiles(x86)\Seewo\EasiNote5"
|
||||
|
||||
# 查找最新版本目录
|
||||
$versionDirs = Get-ChildItem "$seewoPath\EasiNote5_*" -Directory | Sort-Object Name -Descending
|
||||
if ($versionDirs.Count -eq 0) {
|
||||
Write-Host "未找到希沃白板安装目录" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$targetDir = Join-Path $versionDirs[0].FullName "Main\Extensions\$pluginName"
|
||||
|
||||
Write-Host "安装到: $targetDir" -ForegroundColor Cyan
|
||||
|
||||
# 创建插件目录
|
||||
if (-not (Test-Path $targetDir)) {
|
||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# 复制插件文件
|
||||
$sourceDir = Join-Path $PSScriptRoot "Better-EN5\bin\Release\net6.0-windows"
|
||||
if (-not (Test-Path $sourceDir)) {
|
||||
$sourceDir = Join-Path $PSScriptRoot "Better-EN5\bin\Debug\net6.0-windows"
|
||||
}
|
||||
|
||||
if (Test-Path $sourceDir) {
|
||||
Copy-Item "$sourceDir\*" $targetDir -Recurse -Force
|
||||
Write-Host "插件文件已复制" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "未找到构建输出目录,请先构建项目" -ForegroundColor Yellow
|
||||
Write-Host "期望路径: $sourceDir" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# 复制 manifest.coin
|
||||
$manifestSource = Join-Path $PSScriptRoot "Better-EN5\manifest.coin"
|
||||
if (Test-Path $manifestSource) {
|
||||
Copy-Item $manifestSource $targetDir -Force
|
||||
Write-Host "manifest.coin 已复制" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "安装完成!请重启希沃白板以加载插件。" -ForegroundColor Green
|
||||
Write-Host "在希沃白板的右键菜单中可以找到 'Better-Seewo 设置' 入口。" -ForegroundColor Cyan
|
||||
98
使用说明.txt
98
使用说明.txt
@@ -1,98 +0,0 @@
|
||||
Better-Seewo 增强插件 - 使用说明
|
||||
====================================
|
||||
版本:1.1.2 | 作者:雾启工作室 × Macrohard Studio
|
||||
项目地址:http://171.80.3.149:4321/miao-moe/Better-Seewo
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
一、安装方法
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
方式一:自动安装(推荐)
|
||||
1. 以管理员身份运行 PowerShell
|
||||
2. 执行:.\scripts\install.ps1
|
||||
3. 重启希沃白板 5
|
||||
|
||||
方式二:安装包安装
|
||||
- 运行 bin\Release\Better-Seewo 增强插件.1.1.0.exe
|
||||
|
||||
方式三:手动安装
|
||||
1. 将 Better-EN5\bin\Release\net6.0-windows\ 下所有文件
|
||||
复制到希沃白板 Extensions\Better-EN5\ 目录
|
||||
2. 将 Better-EN5\manifest.coin 复制到同一目录
|
||||
3. 重启希沃白板 5
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
二、功能入口
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
安装成功后,在希沃白板中可以找到以下入口:
|
||||
|
||||
1. 右键菜单(备课模式)
|
||||
在课件板书区域右键 → "导出墨迹"
|
||||
在课件板书区域右键 → "Better-Seewo"
|
||||
|
||||
2. 顶部工具栏
|
||||
在工具栏找到 "Better-Seewo" 图标按钮
|
||||
|
||||
点击以上任意入口即可打开 Better-Seewo 主界面。
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
三、功能模块
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
1. 墨迹管理(Ink Manager)
|
||||
- 导出当前课件中的所有笔迹/标注为 JSON 文件
|
||||
- 从 JSON 文件导入笔迹/标注到当前课件
|
||||
- 支持备份和迁移课堂板书内容
|
||||
|
||||
2. 文件转换(File Converter)
|
||||
- PPT(.pptx)转希沃课件(.enbx)
|
||||
- 希沃课件(.enbx)转 PPT(.pptx)
|
||||
- 清理转换缓存,修复转换失败问题
|
||||
|
||||
3. 触摸修复(Touch Fix)
|
||||
- 检测希沃白板的触摸状态
|
||||
- 强制启用 IWB 模式
|
||||
- 配置注册表和配置文件以跳过触摸检测
|
||||
- 抑制触摸相关错误提示
|
||||
|
||||
4. 大屏支持(Board Support)
|
||||
- 为非触摸大屏启用完整白板模式
|
||||
- 创建"希沃白板5-大屏模式"桌面快捷方式
|
||||
- 配置大屏相关注册表和配置文件
|
||||
|
||||
5. 激活(Activation)
|
||||
- 社区版与专业版自由切换
|
||||
- 输入激活码解锁全部功能
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
四、开发者信息
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
构建环境要求:
|
||||
- .NET SDK 6.0
|
||||
- 希沃白板 5(版本 5.2.2.653 ~ 5.3.0.0)
|
||||
- Visual Studio 2022 或 dotnet CLI
|
||||
|
||||
构建命令:
|
||||
dotnet build -c Release Better-EN5
|
||||
|
||||
更多信息请参阅 DEVELOPER_GUIDE.md
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
五、常见问题
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Q: 安装后希沃白板看不到插件入口?
|
||||
A: 请确保以管理员身份运行 install.ps1,
|
||||
然后完全退出希沃白板,重新启动。
|
||||
|
||||
Q: 插件加载失败怎么办?
|
||||
A: 检查希沃白板版本是否在 5.2.2.653 ~ 5.3.0.0 范围内。
|
||||
|
||||
Q: 触摸修复不起作用?
|
||||
A: 部分硬件需要重启电脑才能生效。
|
||||
如果仍然无效,请尝试"大屏支持"中的 IWB 模式。
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
雾生万象,启以为光
|
||||
68
安装插件.bat
68
安装插件.bat
@@ -1,68 +0,0 @@
|
||||
@echo off
|
||||
title Better-Seewo 插件安装
|
||||
cd /d "%~dp0"
|
||||
|
||||
:: 检查管理员权限
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% neq 0 (
|
||||
echo 正在请求管理员权限...
|
||||
powershell start-process "%~f0" -verb runas
|
||||
exit /b
|
||||
)
|
||||
|
||||
echo ========================================
|
||||
echo Better-Seewo 增强插件 v1.1.2 安装
|
||||
echo 雾启工作室
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
:: 设置 dotnet 路径
|
||||
set PATH=C:\Program Files\dotnet;%PATH%
|
||||
|
||||
:: 构建
|
||||
echo [1/3] 构建项目...
|
||||
dotnet build -c Release Better-EN5
|
||||
if %errorLevel% neq 0 (
|
||||
echo 构建失败!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo 构建成功
|
||||
echo.
|
||||
|
||||
:: 查找希沃白板安装目录
|
||||
echo [2/3] 查找希沃白板安装目录...
|
||||
set "seewoRoot=%ProgramFiles(x86)%\Seewo\EasiNote5"
|
||||
if not exist "%seewoRoot%" (
|
||||
echo 未找到希沃白板安装目录!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "tokens=*" %%d in ('dir "%seewoRoot%\EasiNote5_*" /b /o-n') do (
|
||||
set "versionDir=%seewoRoot%\%%d"
|
||||
goto :found
|
||||
)
|
||||
echo 未找到希沃白板版本目录!
|
||||
pause
|
||||
exit /b 1
|
||||
|
||||
:found
|
||||
set "targetDir=%versionDir%\Main\Extensions\Better-EN5"
|
||||
echo 安装到: %targetDir%
|
||||
|
||||
:: 创建目标目录
|
||||
if not exist "%targetDir%" mkdir "%targetDir%"
|
||||
|
||||
:: 复制文件
|
||||
echo [3/3] 复制插件文件...
|
||||
xcopy "Better-EN5\bin\Release\net6.0-windows\*" "%targetDir%\" /y /q
|
||||
copy /y "Better-EN5\manifest.coin" "%targetDir%\" >nul
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo 安装完成!
|
||||
echo 请重启希沃白板以加载插件。
|
||||
echo ========================================
|
||||
echo.
|
||||
pause
|
||||
Reference in New Issue
Block a user