From 92c2d9ed4c92b728320ff69a7ca3462727312eb2 Mon Sep 17 00:00:00 2001 From: ema Date: Tue, 28 Apr 2026 02:29:02 +0800 Subject: [PATCH] Add CHM viewer plugin (WebView2-based) This commit does not implement the file link --- .../QuickLook.Plugin.ChmViewer/.gitattributes | 8 ++ .../ChmWebpagePanel.cs | 125 ++++++++++++++++++ .../QuickLook.Plugin.ChmViewer/Helper.cs | 52 ++++++++ .../QuickLook.Plugin.ChmViewer/Plugin.cs | 23 +++- .../QuickLook.Plugin.ChmViewer.csproj | 15 +++ .../Resources/chm2html.html | 85 ++++++++++++ 6 files changed, 301 insertions(+), 7 deletions(-) create mode 100644 QuickLook.Plugin/QuickLook.Plugin.ChmViewer/.gitattributes create mode 100644 QuickLook.Plugin/QuickLook.Plugin.ChmViewer/ChmWebpagePanel.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Helper.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Resources/chm2html.html diff --git a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/.gitattributes b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/.gitattributes new file mode 100644 index 0000000..8bca8e4 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/.gitattributes @@ -0,0 +1,8 @@ +# Ignore HTML files in Resources and subdirectories +Resources/**/*.html linguist-vendored + +# Ignore CSS files in Resources and subdirectories +Resources/**/*.css linguist-vendored + +# Ignore JS files in Resources and subdirectories +Resources/**/*.js linguist-vendored diff --git a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/ChmWebpagePanel.cs b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/ChmWebpagePanel.cs new file mode 100644 index 0000000..c9dc326 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/ChmWebpagePanel.cs @@ -0,0 +1,125 @@ +// Copyright © 2017-2026 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using QuickLook.Plugin.HtmlViewer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace QuickLook.Plugin.ChmViewer; + +public class ChmWebpagePanel : WebpagePanel +{ + private const string _resourcePrefix = "QuickLook.Plugin.ChmViewer.Resources."; + private static readonly Dictionary _resources = []; + private readonly byte[] _homePage; + + static ChmWebpagePanel() + { + InitializeResources(); + } + + public ChmWebpagePanel() + { + _homePage = ReadResource("/chm2html.html") ?? []; + } + + private static void InitializeResources() + { + if (_resources.Any()) + return; + + var assembly = Assembly.GetExecutingAssembly(); + foreach (var resourceName in assembly.GetManifestResourceNames()) + { + if (!resourceName.StartsWith(_resourcePrefix, StringComparison.Ordinal)) + continue; + + var relativePath = resourceName.Substring(_resourcePrefix.Length).Replace('\\', '/'); + if (string.IsNullOrEmpty(relativePath)) + continue; + + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream == null) + continue; + + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + _resources[$"/{relativePath}"] = memoryStream.ToArray(); + } + } + + public void PreviewCompiledHtmlHelp(string path) + { + FallbackPath = Path.GetDirectoryName(path); + + var chmFileUrl = Helper.FilePathToFileUrl(path); + var pluginUri = new Uri($"file://quicklook/?plugin=1&chm={Uri.EscapeDataString(chmFileUrl.AbsoluteUri)}"); + + NavigateToUri(pluginUri); + } + + protected override void WebView_WebResourceRequested(object sender, Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequestedEventArgs args) + { + try + { + if (string.IsNullOrWhiteSpace(args.Request.Uri)) + { + base.WebView_WebResourceRequested(sender, args); + return; + } + + var requestedUri = new Uri(args.Request.Uri); + if (requestedUri.Scheme != "file") + { + base.WebView_WebResourceRequested(sender, args); + return; + } + + var requestedPath = Uri.UnescapeDataString(requestedUri.AbsolutePath); + if (requestedPath == "/") + { + var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse( + new MemoryStream(_homePage), 200, "OK", MimeTypes.GetContentType(".html")); + args.Response = response; + return; + } + + if (ContainsKey(requestedPath)) + { + var response = _webView.CoreWebView2.Environment.CreateWebResourceResponse( + ReadStream(requestedPath), 200, "OK", MimeTypes.GetContentType(Path.GetExtension(requestedPath))); + args.Response = response; + return; + } + + base.WebView_WebResourceRequested(sender, args); + } + catch + { + base.WebView_WebResourceRequested(sender, args); + } + } + + private static bool ContainsKey(string key) => _resources.ContainsKey(key); + + private static Stream ReadStream(string key) => new MemoryStream(_resources[key]); + + private static byte[] ReadResource(string key) => _resources.TryGetValue(key, out var value) ? value : null; +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Helper.cs b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Helper.cs new file mode 100644 index 0000000..4ed82fc --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Helper.cs @@ -0,0 +1,52 @@ +// Copyright © 2017-2026 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.IO; +using System.Text; + +namespace QuickLook.Plugin.ChmViewer; + +internal static class Helper +{ + public static Uri FilePathToFileUrl(string filePath) + { + var uri = new StringBuilder(); + foreach (var v in filePath) + if (v >= 'a' && v <= 'z' || v >= 'A' && v <= 'Z' || v >= '0' && v <= '9' || + v == '+' || v == '/' || v == ':' || v == '.' || v == '-' || v == '_' || v == '~' || + v > '\x80') + uri.Append(v); + else if (v == Path.DirectorySeparatorChar || v == Path.AltDirectorySeparatorChar) + uri.Append('/'); + else + uri.Append($"%{(int)v:X2}"); + if (uri.Length >= 2 && uri[0] == '/' && uri[1] == '/') // UNC path - Universal Naming Convention + uri.Insert(0, "file:"); + else + uri.Insert(0, "file:///"); + + try + { + return new Uri(uri.ToString()); + } + catch + { + return null; + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Plugin.cs index a8d958b..9d725b5 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Plugin.cs @@ -24,7 +24,13 @@ namespace QuickLook.Plugin.ChmViewer; public sealed class Plugin : IViewer { - public int Priority => 0; + private ChmWebpagePanel _panel; + + /// + /// The implementation of this plugin is better than following + /// https://github.com/emako/QuickLook.Plugin.SumatraPDFReader + /// + public int Priority => 2; public void Init() { @@ -32,26 +38,29 @@ public sealed class Plugin : IViewer public bool CanHandle(string path) { - return false; -#pragma warning disable CS0162 // Unreachable code detected return !Directory.Exists(path) && Path.GetExtension(path).Equals(".chm", StringComparison.OrdinalIgnoreCase); -#pragma warning restore CS0162 // Unreachable code detected } public void Prepare(string path, ContextObject context) { - context.Title = Path.GetFileName(path); - context.IsBlocked = true; - context.PreferredSize = new Size { Width = 800, Height = 600 }; + context.PreferredSize = new Size(1280, 720); } public void View(string path, ContextObject context) { + _panel = new ChmWebpagePanel(); + _panel.PreviewCompiledHtmlHelp(path); + + context.ViewerContent = _panel; + context.Title = Path.GetFileName(path); context.IsBusy = false; } public void Cleanup() { + _panel?.Dispose(); + _panel = null; + GC.SuppressFinalize(this); } } diff --git a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/QuickLook.Plugin.ChmViewer.csproj b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/QuickLook.Plugin.ChmViewer.csproj index 4d6aa2f..3fc929f 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/QuickLook.Plugin.ChmViewer.csproj +++ b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/QuickLook.Plugin.ChmViewer.csproj @@ -76,6 +76,21 @@ QuickLook.Common False + + {CE22A1F3-7F2C-4EC8-BFDE-B58D0EB625FC} + QuickLook.Plugin.HtmlViewer + False + + + + + + all + + + + + diff --git a/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Resources/chm2html.html b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Resources/chm2html.html new file mode 100644 index 0000000..70666b3 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.ChmViewer/Resources/chm2html.html @@ -0,0 +1,85 @@ + + + + + + chm2html — CHM Viewer (Plugin) + + + + + + + + +
+
+ + +
+ chm2html +
+ +
+
+ + + + + +
+ +
+
+
+
+
+ + + + + +