New AppViewer plugin for .msi

This commit is contained in:
ema
2025-05-24 15:03:54 +08:00
parent 77c332d825
commit 86e7803cd7
12 changed files with 876 additions and 1 deletions

View File

@@ -0,0 +1,27 @@
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
namespace QuickLook.Plugin.AppViewer;
public interface IAppInfoPanel
{
public void DisplayInfo(string path);
public object Tag { get; set; }
public bool Stop { get; set; }
}

View File

@@ -0,0 +1,31 @@
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
namespace QuickLook.Plugin.AppViewer.MsiImageParser;
public sealed class MsiInfo
{
public string ProductVersion { get; set; }
public string ProductName { get; set; }
public string Manufacturer { get; set; }
public string ProductCode { get; set; }
public string UpgradeCode { get; set; }
}

View File

@@ -0,0 +1,45 @@
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
using WixToolset.Dtf.WindowsInstaller;
namespace QuickLook.Plugin.AppViewer.MsiImageParser;
public static class MsiParser
{
public static MsiInfo Parse(string path)
{
MsiInfo info = new();
using var database = new Database(path, DatabaseOpenMode.ReadOnly);
info.ProductVersion = database.ReadPropertyString(nameof(MsiInfo.ProductVersion));
info.ProductName = database.ReadPropertyString(nameof(MsiInfo.ProductName));
info.Manufacturer = database.ReadPropertyString(nameof(MsiInfo.Manufacturer));
info.ProductCode = database.ReadPropertyString(nameof(MsiInfo.ProductCode));
info.UpgradeCode = database.ReadPropertyString(nameof(MsiInfo.UpgradeCode));
return info;
}
private static string ReadPropertyString(this Database database, string propertyName)
{
using View view = database.OpenView($"SELECT `Value` FROM `Property` WHERE `Property`='{propertyName}'");
view.Execute(null);
using Record record = view.Fetch();
return record?.GetString(1);
}
}

View File

@@ -0,0 +1,171 @@
<UserControl x:Class="QuickLook.Plugin.AppViewer.MsiInfoPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:QuickLook.Plugin.AppViewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Height="230"
FontSize="14"
UseLayoutRounding="True"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/QuickLook.Common;component/Styles/MainWindowStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="image"
Grid.Row="1"
Grid.Column="0"
Width="128"
Height="128"
Margin="0,-20,0,0"
Opacity="0"
Stretch="Fill">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source, ElementName=image}" Value="{x:Null}">
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation BeginTime="0:0:0"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.05" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
MaxHeight="60"
Padding="3"
FontSize="19"
FontWeight="SemiBold"
LineHeight="25"
TextTrimming="CharacterEllipsis"
TextWrapping="Wrap">
<TextBlock.Inlines>
<Run x:Name="filename" Text="FilenameFilenameFilenameFilenameFilenameFilenameFilenameFilenameFilenameFilename.ext" />
</TextBlock.Inlines>
</TextBlock>
</Grid>
<!-- Product Name -->
<TextBlock x:Name="productNameTitle"
Grid.Row="3"
Grid.Column="1"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Product Name" />
<TextBlock x:Name="productName"
Grid.Row="3"
Grid.Column="2"
Margin="8,0,0,0"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Searching..."
TextTrimming="CharacterEllipsis"
TextWrapping="Wrap" />
<!-- Product Version -->
<TextBlock x:Name="productVersionTitle"
Grid.Row="4"
Grid.Column="1"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Product Version" />
<TextBlock x:Name="productVersion"
Grid.Row="4"
Grid.Column="2"
Margin="8,0,0,0"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Searching..."
TextTrimming="CharacterEllipsis"
TextWrapping="Wrap" />
<!-- Manufacturer -->
<TextBlock x:Name="manufacturerTitle"
Grid.Row="5"
Grid.Column="1"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Manufacturer" />
<TextBlock x:Name="manufacturer"
Grid.Row="5"
Grid.Column="2"
Margin="8,0,0,0"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Searching..."
TextTrimming="CharacterEllipsis"
TextWrapping="Wrap" />
<!-- Total Size -->
<TextBlock x:Name="totalSizeTitle"
Grid.Row="6"
Grid.Column="1"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Total Size" />
<TextBlock x:Name="totalSize"
Grid.Row="6"
Grid.Column="2"
Margin="8,0,0,0"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Calculating size..." />
<!-- Last Modified -->
<TextBlock x:Name="modDateTitle"
Grid.Row="7"
Grid.Column="1"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Last Modified" />
<TextBlock x:Name="modDate"
Grid.Row="7"
Grid.Column="2"
Margin="8,0,0,0"
Padding="3"
Foreground="{DynamicResource WindowTextForegroundAlternative}"
Text="Searching..."
TextTrimming="CharacterEllipsis" />
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,97 @@
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
using QuickLook.Common.ExtensionMethods;
using QuickLook.Common.Helpers;
using QuickLook.Plugin.AppViewer.MsiImageParser;
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace QuickLook.Plugin.AppViewer;
public partial class MsiInfoPanel : UserControl, IAppInfoPanel
{
private bool _stop;
public MsiInfoPanel()
{
InitializeComponent();
// apply global theme
Resources.MergedDictionaries[0].Clear();
string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config");
productNameTitle.Text = TranslationHelper.Get("PRODUCT_NAME", translationFile);
productVersionTitle.Text = TranslationHelper.Get("PRODUCT_VERSION", translationFile);
manufacturerTitle.Text = TranslationHelper.Get("MANUFACTURER", translationFile);
totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile);
modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile);
}
public bool Stop
{
set => _stop = value;
get => _stop;
}
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;
Stop = false;
_ = Task.Run(() =>
{
if (File.Exists(path))
{
var size = new FileInfo(path).Length;
MsiInfo msiInfo = MsiParser.Parse(path);
var last = File.GetLastWriteTime(path);
Dispatcher.Invoke(() =>
{
productName.Text = msiInfo.ProductName;
productVersion.Text = msiInfo.ProductVersion;
manufacturer.Text = msiInfo.Manufacturer;
totalSize.Text = size.ToPrettySize(2);
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
});
}
});
}
}

View File

@@ -0,0 +1,99 @@
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
using QuickLook.Common.Plugin;
using System;
using System.IO;
using System.Linq;
using System.Windows;
namespace QuickLook.Plugin.AppViewer;
public class Plugin : IViewer
{
private static readonly string[] _extensions =
[
// Android
//".apk", ".apk.1", // Android Package
//".aar", // Android Archive
//".aab", // Android App Bundle
// Windows
//".appx", ".appxbundle", // Windows APPX installer
".msi", // Windows MSI installer
//".msix", ".msixbundle", // Windows MSIX installer
// macOS
//".dmg", // macOS DMG
// iOS
//".ipa", // iOS IPA
// HarmonyOS
//".hap", // HarmonyOS Package
//".har", // HarmonyOS Archive
// Others
//".wgt", ".wgtu", // UniApp Widget
];
private IAppInfoPanel _ip;
private string _path;
public int Priority => 0;
public void Init()
{
}
public bool CanHandle(string path)
{
return !Directory.Exists(path) && _extensions.Any(path.ToLower().EndsWith);
}
public void Prepare(string path, ContextObject context)
{
context.PreferredSize = Path.GetExtension(path) switch
{
".msi" => new Size { Width = 520, Height = 230 },
_ => throw new NotSupportedException("Extension is not supported."),
};
}
public void View(string path, ContextObject context)
{
_path = path;
_ip = Path.GetExtension(path) switch
{
".msi" => new MsiInfoPanel(),
_ => throw new NotSupportedException("Extension is not supported."),
};
_ip.DisplayInfo(_path);
_ip.Tag = context;
context.ViewerContent = _ip;
context.IsBusy = false;
}
public void Cleanup()
{
GC.SuppressFinalize(this);
_ip = null;
}
}

View File

@@ -0,0 +1,50 @@
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("QuickLook.Plugin.AppViewer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("pooi.moe")]
[assembly: AssemblyProduct("QuickLook.Plugin.AppViewer")]
[assembly: AssemblyCopyright("Copyright © 2017-2025 QL-Win Contributors")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("00d8f3b1-5be9-4ab8-ae68-16374001887a")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]

View File

@@ -0,0 +1,79 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net462</TargetFramework>
<RootNamespace>QuickLook.Plugin.AppViewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.AppViewer</AssemblyName>
<FileAlignment>512</FileAlignment>
<SignAssembly>false</SignAssembly>
<UseWPF>true</UseWPF>
<LangVersion>latest</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<ProjectGuid>{00D8F3B1-5BE9-4AB8-AE68-16374001887A}</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Build\Debug\QuickLook.Plugin\QuickLook.Plugin.AppViewer\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Build\Release\QuickLook.Plugin\QuickLook.Plugin.AppViewer\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<OutputPath>..\..\Build\Debug\QuickLook.Plugin\QuickLook.Plugin.AppViewer\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>..\..\Build\Release\QuickLook.Plugin\QuickLook.Plugin.AppViewer\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\QuickLook.Common\QuickLook.Common.csproj">
<Project>{85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}</Project>
<Name>QuickLook.Common</Name>
<Private>False</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\GitVersion.cs">
<Link>Properties\GitVersion.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="Translations.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Translations>
<en>
<PRODUCT_VERSION>Product Version</PRODUCT_VERSION>
<PRODUCT_NAME>Product Name</PRODUCT_NAME>
<MANUFACTURER>Manufacturer</MANUFACTURER>
<TOTAL_SIZE>Total Size</TOTAL_SIZE>
<LAST_MODIFIED>Last Modified</LAST_MODIFIED>
</en>
<pt-BR>
<PRODUCT_VERSION>Versão do produto</PRODUCT_VERSION>
<PRODUCT_NAME>Nome do produto</PRODUCT_NAME>
<MANUFACTURER>Fabricante</MANUFACTURER>
<TOTAL_SIZE>Tamanho total</TOTAL_SIZE>
<LAST_MODIFIED>Modificado em</LAST_MODIFIED>
</pt-BR>
<zh-CN>
<PRODUCT_VERSION>产品版本</PRODUCT_VERSION>
<PRODUCT_NAME>产品名称</PRODUCT_NAME>
<MANUFACTURER>制造商</MANUFACTURER>
<TOTAL_SIZE>总大小</TOTAL_SIZE>
<LAST_MODIFIED>修改时间</LAST_MODIFIED>
</zh-CN>
<zh-TW>
<PRODUCT_VERSION>產品版本</PRODUCT_VERSION>
<PRODUCT_NAME>產品名稱</PRODUCT_NAME>
<MANUFACTURER>製造商</MANUFACTURER>
<TOTAL_SIZE>縂大小</TOTAL_SIZE>
<LAST_MODIFIED>修改時間</LAST_MODIFIED>
</zh-TW>
<ja>
<PRODUCT_VERSION>製品バージョン</PRODUCT_VERSION>
<PRODUCT_NAME>製品名</PRODUCT_NAME>
<MANUFACTURER>製造元</MANUFACTURER>
<TOTAL_SIZE>合計サイズ</TOTAL_SIZE>
<LAST_MODIFIED>更新日時</LAST_MODIFIED>
</ja>
</Translations>

View File

@@ -0,0 +1,225 @@
// Copyright © 2017-2025 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 <http://www.gnu.org/licenses/>.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace QuickLook.Plugin.AppViewer;
[Flags]
internal enum ThumbnailOptions
{
None = 0x00,
BiggerSizeOk = 0x01,
InMemoryOnly = 0x02,
IconOnly = 0x04,
ThumbnailOnly = 0x08,
InCacheOnly = 0x10,
IconBackground = 0x80,
ScaleUp = 0x100
}
internal static class WindowsThumbnailProvider
{
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int SHCreateItemFromParsingName(
[MarshalAs(UnmanagedType.LPWStr)] string path,
// The following parameter is not used - binding context.
IntPtr pbc,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject(IntPtr hObject);
public static Bitmap GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
var hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
if (hBitmap == IntPtr.Zero)
return null!;
try
{
// return a System.Drawing.Bitmap from the hBitmap
return GetBitmapFromHBitmap(hBitmap);
}
finally
{
// delete HBitmap to avoid memory leaks
DeleteObject(hBitmap);
}
}
internal static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap)
{
var bmp = Image.FromHbitmap(nativeHBitmap);
if (Image.GetPixelFormatSize(bmp.PixelFormat) < 32)
return bmp;
return CreateAlphaBitmap(bmp, PixelFormat.Format32bppArgb);
}
internal static Bitmap CreateAlphaBitmap(Bitmap srcBitmap, PixelFormat targetPixelFormat)
{
var result = new Bitmap(srcBitmap.Width, srcBitmap.Height, targetPixelFormat);
var bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
var srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
var isAlplaBitmap = false;
try
{
for (var y = 0; y <= srcData.Height - 1; y++)
for (var x = 0; x <= srcData.Width - 1; x++)
{
var pixelColor = Color.FromArgb(
Marshal.ReadInt32(srcData.Scan0, srcData.Stride * y + 4 * x));
if ((pixelColor.A > 0) & (pixelColor.A < 255))
isAlplaBitmap = true;
result.SetPixel(x, y, pixelColor);
}
}
finally
{
srcBitmap.UnlockBits(srcData);
}
if (isAlplaBitmap)
return result;
return srcBitmap;
}
private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
{
var shellItem2Guid = new Guid(IShellItem2Guid);
var retCode =
SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out var nativeShellItem);
if (retCode != 0)
return IntPtr.Zero;
var nativeSize = new NativeSize
{
Width = width,
Height = height
};
var hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out var hBitmap);
Marshal.ReleaseComObject(nativeShellItem);
return hr == HResult.Ok ? hBitmap : IntPtr.Zero;
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
internal interface IShellItem
{
void BindToHandler(IntPtr pbc,
[MarshalAs(UnmanagedType.LPStruct)] Guid bhid,
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
out IntPtr ppv);
void GetParent(out IShellItem ppsi);
void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItem psi, uint hint, out int piOrder);
}
internal enum SIGDN : uint
{
NORMALDISPLAY = 0,
PARENTRELATIVEPARSING = 0x80018001,
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
DESKTOPABSOLUTEPARSING = 0x80028000,
PARENTRELATIVEEDITING = 0x80031001,
DESKTOPABSOLUTEEDITING = 0x8004c000,
FILESYSPATH = 0x80058000,
URL = 0x80068000
}
internal enum HResult
{
Ok = 0x0000,
False = 0x0001,
InvalidArguments = unchecked((int)0x80070057),
OutOfMemory = unchecked((int)0x8007000E),
NoInterface = unchecked((int)0x80004002),
Fail = unchecked((int)0x80004005),
ElementNotFound = unchecked((int)0x80070490),
TypeElementNotFound = unchecked((int)0x8002802B),
NoObject = unchecked((int)0x800401E5),
Win32ErrorCanceled = 1223,
Canceled = unchecked((int)0x800704C7),
ResourceInUse = unchecked((int)0x800700AA),
AccessDenied = unchecked((int)0x80030005)
}
[ComImport]
[Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItemImageFactory
{
[PreserveSig]
HResult GetImage(
[In][MarshalAs(UnmanagedType.Struct)] NativeSize size,
[In] ThumbnailOptions flags,
[Out] out IntPtr phbm);
}
[StructLayout(LayoutKind.Sequential)]
internal struct NativeSize
{
private int width;
private int height;
public int Width
{
set => width = value;
}
public int Height
{
set => height = value;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RGBQUAD
{
public byte rgbBlue;
public byte rgbGreen;
public byte rgbRed;
public byte rgbReserved;
}
}

View File

@@ -52,7 +52,7 @@ public partial class PEInfoPanel : UserControl
public void DisplayInfo(string path)
{
Task.Run(() =>
_ = Task.Run(() =>
{
var scale = DisplayDeviceHelper.GetCurrentScaleFactor();

View File

@@ -52,6 +52,7 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "QuickLook.Installer", "Quic
{8D50A1DA-601C-4C26-9A7E-7D477EA8430A} = {8D50A1DA-601C-4C26-9A7E-7D477EA8430A}
{CE40160D-5E3C-4A41-9BDA-C4F414C174EB} = {CE40160D-5E3C-4A41-9BDA-C4F414C174EB}
{538FD6BA-2AF1-4DDA-93A2-6C0A12B00843} = {538FD6BA-2AF1-4DDA-93A2-6C0A12B00843}
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30} = {EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QuickLook.Native64", "QuickLook.Native\QuickLook.Native64\QuickLook.Native64.vcxproj", "{794E4DCF-F715-4836-9D30-ABD296586D23}"
@@ -80,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.FontViewer
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.CLSIDViewer", "QuickLook.Plugin\QuickLook.Plugin.CLSIDViewer\QuickLook.Plugin.CLSIDViewer.csproj", "{538FD6BA-2AF1-4DDA-93A2-6C0A12B00843}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.AppViewer", "QuickLook.Plugin\QuickLook.Plugin.AppViewer\QuickLook.Plugin.AppViewer.csproj", "{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -234,6 +237,14 @@ Global
{538FD6BA-2AF1-4DDA-93A2-6C0A12B00843}.Release|Any CPU.Build.0 = Release|Any CPU
{538FD6BA-2AF1-4DDA-93A2-6C0A12B00843}.Release|x86.ActiveCfg = Release|Any CPU
{538FD6BA-2AF1-4DDA-93A2-6C0A12B00843}.Release|x86.Build.0 = Release|Any CPU
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}.Debug|x86.ActiveCfg = Debug|Any CPU
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}.Debug|x86.Build.0 = Debug|Any CPU
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}.Release|Any CPU.Build.0 = Release|Any CPU
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}.Release|x86.ActiveCfg = Release|Any CPU
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -255,6 +266,7 @@ Global
{8D50A1DA-601C-4C26-9A7E-7D477EA8430A} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
{CE40160D-5E3C-4A41-9BDA-C4F414C174EB} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
{538FD6BA-2AF1-4DDA-93A2-6C0A12B00843} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
{EB7D1F10-6E04-4F7E-96A0-4E18359FBD30} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D3761C32-8C5F-498A-892B-3B0882994B62}