From ccf80c07cf49e59c3896178c5d65a7287fd1cdf5 Mon Sep 17 00:00:00 2001 From: ema Date: Wed, 22 Apr 2026 00:40:19 +0800 Subject: [PATCH] Add MSP installer support (info panel/parser) --- .../InfoPanels/MspInfoPanel.xaml | 167 ++++++++++++++++++ .../InfoPanels/MspInfoPanel.xaml.cs | 92 ++++++++++ .../PackageParsers/Msp/MspInfo.cs | 33 ++++ .../PackageParsers/Msp/MspParser.cs | 46 +++++ .../QuickLook.Plugin.AppViewer/Plugin.cs | 4 +- 5 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/MspInfoPanel.xaml create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/MspInfoPanel.xaml.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Msp/MspInfo.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Msp/MspParser.cs diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/MspInfoPanel.xaml b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/MspInfoPanel.xaml new file mode 100644 index 0000000..19854e2 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/MspInfoPanel.xaml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/MspInfoPanel.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/MspInfoPanel.xaml.cs new file mode 100644 index 0000000..9b2a652 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/MspInfoPanel.xaml.cs @@ -0,0 +1,92 @@ +// 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.Common.ExtensionMethods; +using QuickLook.Common.Helpers; +using QuickLook.Common.Plugin; +using QuickLook.Plugin.AppViewer.PackageParsers.Msp; +using System; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows.Controls; + +namespace QuickLook.Plugin.AppViewer.InfoPanels; + +public partial class MspInfoPanel : UserControl, IAppInfoPanel +{ + private readonly ContextObject _context; + + public MspInfoPanel(ContextObject context) + { + _context = context; + + InitializeComponent(); + + string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config"); + productNameTitle.Text = TranslationHelper.Get("PRODUCT_NAME", translationFile); + // MSP has no product version; show the patch description instead. + productVersionTitle.Text = TranslationHelper.Get("DESCRIPTION", translationFile); + manufacturerTitle.Text = TranslationHelper.Get("MANUFACTURER", translationFile); + totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile); + modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile); + } + + public void DisplayInfo(string path) + { + _ = Task.Run(() => + { + var scale = DisplayDeviceHelper.GetCurrentScaleFactor(); + + var icon = + WindowsThumbnailProvider.GetThumbnail(path, + (int)(128 * scale.Horizontal), + (int)(128 * scale.Vertical), + ThumbnailOptions.ScaleUp); + + var source = icon?.ToBitmapSource(); + icon?.Dispose(); + + Dispatcher.BeginInvoke(new Action(() => image.Source = source)); + }); + + var name = Path.GetFileName(path); + filename.Text = string.IsNullOrEmpty(name) ? path : name; + + _ = Task.Run(() => + { + if (File.Exists(path)) + { + var size = new FileInfo(path).Length; + MspInfo mspInfo = MspParser.Parse(path); + var last = File.GetLastWriteTime(path); + + Dispatcher.Invoke(() => + { + productName.Text = mspInfo.DisplayName; + productVersion.Text = mspInfo.Description; + manufacturer.Text = mspInfo.Manufacturer; + totalSize.Text = size.ToPrettySize(2); + modDate.Text = last.ToString(CultureInfo.CurrentCulture); + + _context.IsBusy = false; + }); + } + }); + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Msp/MspInfo.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Msp/MspInfo.cs new file mode 100644 index 0000000..a74c7ca --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Msp/MspInfo.cs @@ -0,0 +1,33 @@ +// 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 . + +namespace QuickLook.Plugin.AppViewer.PackageParsers.Msp; + +public sealed class MspInfo +{ + /// Summary Information Subject — patch display name. + public string DisplayName { get; set; } + + /// Summary Information Comments — patch description. + public string Description { get; set; } + + /// Summary Information Author — manufacturer / creator. + public string Manufacturer { get; set; } + + /// Summary Information RevisionNumber — patch GUID. + public string PatchCode { get; set; } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Msp/MspParser.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Msp/MspParser.cs new file mode 100644 index 0000000..0594bce --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Msp/MspParser.cs @@ -0,0 +1,46 @@ +// 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 WixToolset.Dtf.WindowsInstaller; + +namespace QuickLook.Plugin.AppViewer.PackageParsers.Msp; + +public static class MspParser +{ + /// + /// Reads metadata from an MSP patch file via the Summary Information stream. + /// MSP files cannot be opened with ; use + /// (wraps MsiGetSummaryInformation) instead. + /// + public static MspInfo Parse(string path) + { + MspInfo info = new(); + + using var si = new SummaryInfo(path, enableWrite: false); + + // Subject is the human-readable patch name; fall back to Title if absent. + info.DisplayName = string.IsNullOrWhiteSpace(si.Subject) + ? (si.Title ?? string.Empty) + : si.Subject; + + info.Description = si.Comments ?? string.Empty; + info.Manufacturer = si.Author ?? string.Empty; + info.PatchCode = si.RevisionNumber ?? string.Empty; + + return info; + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs index d56cd19..d607f09 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs @@ -36,6 +36,7 @@ public sealed class Plugin : IViewer // Windows ".appx", ".appxbundle", // Windows APPX installer ".msi", // Windows MSI installer + ".msp", // Windows MSP installer ".msix", ".msixbundle", // Windows MSIX installer // macOS @@ -79,7 +80,7 @@ public sealed class Plugin : IViewer ".apk" or ".aab" => new Size { Width = 600, Height = 510 }, ".ipa" => new Size { Width = 560, Height = 510 }, ".hap" => new Size { Width = 560, Height = 500 }, - ".msi" => new Size { Width = 560, Height = 230 }, + ".msi" or ".msp" => new Size { Width = 560, Height = 230 }, ".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new Size { Width = 560, Height = 328 }, ".ddeb" or ".deb" => new Size { Width = 600, Height = 345 }, ".dmg" => new Size { Width = 560, Height = 510 }, @@ -104,6 +105,7 @@ public sealed class Plugin : IViewer ".ipa" => new IpaInfoPanel(context), ".hap" => new HapInfoPanel(context), ".msi" => new MsiInfoPanel(context), + ".msp" => new MspInfoPanel(context), ".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new AppxInfoPanel(context), ".ddeb" or ".deb" => new DebInfoPanel(context), ".dmg" => new DmgInfoPanel(context),