mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-20 16:34:55 +00:00
Support .appx and .msix
This commit is contained in:
191
QuickLook.Plugin/QuickLook.Plugin.AppViewer/AppxInfoPanel.xaml
Normal file
191
QuickLook.Plugin/QuickLook.Plugin.AppViewer/AppxInfoPanel.xaml
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<UserControl x:Class="QuickLook.Plugin.AppViewer.AppxInfoPanel"
|
||||||
|
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"
|
||||||
|
FontSize="14"
|
||||||
|
UseLayoutRounding="True"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<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"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
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" />
|
||||||
|
<RowDefinition Height="* " />
|
||||||
|
</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" />
|
||||||
|
<!-- Publisher -->
|
||||||
|
<TextBlock x:Name="publisherTitle"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Publisher" />
|
||||||
|
<TextBlock x:Name="publisher"
|
||||||
|
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" />
|
||||||
|
<!-- Capabilities -->
|
||||||
|
<GroupBox x:Name="capabilitiesGroupBox"
|
||||||
|
Grid.Row="8"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0,0,16,16"
|
||||||
|
Header="Capabilities">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<ScrollViewer.Resources>
|
||||||
|
<Style TargetType="{x:Type TextBox}">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</ScrollViewer.Resources>
|
||||||
|
<ItemsControl x:Name="capabilities">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBox Margin="8,3,16,3"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Text="{Binding ., Mode=OneTime}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</GroupBox>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
@@ -0,0 +1,87 @@
|
|||||||
|
// 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.AppxPackageParser;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer;
|
||||||
|
|
||||||
|
public partial class AppxInfoPanel : UserControl, IAppInfoPanel
|
||||||
|
{
|
||||||
|
public AppxInfoPanel()
|
||||||
|
{
|
||||||
|
DataContext = this;
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
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);
|
||||||
|
publisherTitle.Text = TranslationHelper.Get("PUBLISHER", translationFile);
|
||||||
|
totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile);
|
||||||
|
modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile);
|
||||||
|
capabilitiesGroupBox.Header = TranslationHelper.Get("CAPABILITIES", translationFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisplayInfo(string path)
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(path);
|
||||||
|
filename.Text = string.IsNullOrEmpty(name) ? path : name;
|
||||||
|
|
||||||
|
_ = Task.Run(() =>
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var size = new FileInfo(path).Length;
|
||||||
|
AppxInfo appxInfo = AppxParser.Parse(path);
|
||||||
|
var last = File.GetLastWriteTime(path);
|
||||||
|
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
productName.Text = appxInfo.ProductName;
|
||||||
|
productVersion.Text = appxInfo.ProductVersion;
|
||||||
|
publisher.Text = appxInfo.Publisher;
|
||||||
|
totalSize.Text = size.ToPrettySize(2);
|
||||||
|
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
|
||||||
|
capabilities.ItemsSource = appxInfo.Capabilities;
|
||||||
|
|
||||||
|
using var icon = appxInfo.Logo;
|
||||||
|
image.Source = icon?.ToBitmapSource() ?? GetWindowsThumbnail(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static BitmapSource GetWindowsThumbnail(string path)
|
||||||
|
{
|
||||||
|
var scale = DisplayDeviceHelper.GetCurrentScaleFactor();
|
||||||
|
using var icon =
|
||||||
|
WindowsThumbnailProvider.GetThumbnail(path,
|
||||||
|
(int)(128 * scale.Horizontal),
|
||||||
|
(int)(128 * scale.Vertical),
|
||||||
|
ThumbnailOptions.ScaleUp);
|
||||||
|
var source = icon?.ToBitmapSource();
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,136 @@
|
|||||||
|
// 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 ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.AppxPackageParser;
|
||||||
|
|
||||||
|
public class AppxBundleReader : IDisposable
|
||||||
|
{
|
||||||
|
private ZipFile zip;
|
||||||
|
private AppxReader appxReader = null;
|
||||||
|
|
||||||
|
private string name;
|
||||||
|
private string publisher;
|
||||||
|
private string version;
|
||||||
|
|
||||||
|
public string Name => name ?? appxReader?.Name;
|
||||||
|
public string Publisher => publisher ?? appxReader?.Publisher;
|
||||||
|
public string Version => version ?? appxReader?.Version;
|
||||||
|
public string DisplayName => appxReader?.DisplayName;
|
||||||
|
public string PublisherDisplayName => appxReader?.PublisherDisplayName;
|
||||||
|
public string Description => appxReader?.Description;
|
||||||
|
public string Logo => appxReader?.Logo;
|
||||||
|
public string[] Capabilities => appxReader?.Capabilities;
|
||||||
|
public Bitmap Icon => appxReader?.Icon;
|
||||||
|
|
||||||
|
public AppxBundleReader(Stream stream)
|
||||||
|
{
|
||||||
|
Open(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppxBundleReader(string path)
|
||||||
|
{
|
||||||
|
Open(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
appxReader?.Dispose();
|
||||||
|
appxReader = null;
|
||||||
|
zip?.Close();
|
||||||
|
zip = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Open(Stream stream)
|
||||||
|
{
|
||||||
|
zip = new ZipFile(stream);
|
||||||
|
ZipEntry entry = zip.GetEntry("AppxMetadata/AppxBundleManifest.xml")
|
||||||
|
?? throw new InvalidDataException("AppxMetadata/AppxBundleManifest.xml not found");
|
||||||
|
|
||||||
|
XmlDocument xml = new() { XmlResolver = null };
|
||||||
|
xml.Load(zip.GetInputStream(entry));
|
||||||
|
|
||||||
|
XmlElement bundleNode = xml.DocumentElement;
|
||||||
|
|
||||||
|
// Identity
|
||||||
|
{
|
||||||
|
XmlElement identityNode = bundleNode["Identity"];
|
||||||
|
|
||||||
|
if (identityNode != null)
|
||||||
|
{
|
||||||
|
name = identityNode.Attributes["Name"]?.Value;
|
||||||
|
version = identityNode.Attributes["Version"]?.Value;
|
||||||
|
publisher = identityNode.Attributes["Publisher"]?.Value;
|
||||||
|
|
||||||
|
Match m = Regex.Match(Publisher, @"CN=([^,]*),?");
|
||||||
|
if (m.Success) publisher = m.Groups[1].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packages
|
||||||
|
{
|
||||||
|
XmlElement packagesNode = bundleNode["Packages"];
|
||||||
|
|
||||||
|
if (packagesNode != null)
|
||||||
|
{
|
||||||
|
string arch = (RuntimeInformation.OSArchitecture == Architecture.Arm64)
|
||||||
|
? "arm64"
|
||||||
|
: (Environment.Is64BitProcess ? "x64" : "x86");
|
||||||
|
|
||||||
|
var packages = packagesNode.ChildNodes.Select(package =>
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Type = package.Attributes["Type"]?.Value,
|
||||||
|
Architecture = package.Attributes["Architecture"]?.Value,
|
||||||
|
FileName = package.Attributes["FileName"]?.Value,
|
||||||
|
Version = package.Attributes["Version"]?.Value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var package = packages
|
||||||
|
.Where(p => p.Type == "application" && p.Architecture == arch)
|
||||||
|
.FirstOrDefault() ?? packages.FirstOrDefault();
|
||||||
|
|
||||||
|
if (package != null)
|
||||||
|
{
|
||||||
|
appxReader = new AppxReader(zip.GetInputStream(zip.GetEntry(package.FileName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file static class LinqExtension
|
||||||
|
{
|
||||||
|
public static IEnumerable<dynamic> Select(this XmlNodeList nodes, Func<XmlNode, dynamic> selector)
|
||||||
|
{
|
||||||
|
foreach (XmlNode node in nodes)
|
||||||
|
{
|
||||||
|
yield return selector(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
// 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.Drawing;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.AppxPackageParser;
|
||||||
|
|
||||||
|
public class AppxInfo
|
||||||
|
{
|
||||||
|
public string ProductName { get; set; }
|
||||||
|
|
||||||
|
public string ProductVersion { get; set; }
|
||||||
|
|
||||||
|
public string Publisher { get; set; }
|
||||||
|
|
||||||
|
public Bitmap Logo { get; set; }
|
||||||
|
|
||||||
|
public string[] Capabilities { get; set; }
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
// 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.IO;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.AppxPackageParser;
|
||||||
|
|
||||||
|
public static class AppxParser
|
||||||
|
{
|
||||||
|
public static AppxInfo Parse(string path)
|
||||||
|
{
|
||||||
|
bool isBundle = Path.GetExtension(path).ToLower() switch
|
||||||
|
{
|
||||||
|
".msixbundle" or ".appxbundle" => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isBundle)
|
||||||
|
{
|
||||||
|
AppxBundleReader appxReader = new(path);
|
||||||
|
|
||||||
|
return new AppxInfo
|
||||||
|
{
|
||||||
|
ProductName = appxReader.DisplayName,
|
||||||
|
ProductVersion = appxReader.Version,
|
||||||
|
Publisher = appxReader.Publisher,
|
||||||
|
Logo = appxReader.Icon,
|
||||||
|
Capabilities = appxReader.Capabilities,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AppxReader appxReader = new(path);
|
||||||
|
|
||||||
|
return new AppxInfo
|
||||||
|
{
|
||||||
|
ProductName = appxReader.DisplayName,
|
||||||
|
ProductVersion = appxReader.Version,
|
||||||
|
Publisher = appxReader.Publisher,
|
||||||
|
Logo = appxReader.Icon,
|
||||||
|
Capabilities = appxReader.Capabilities,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,155 @@
|
|||||||
|
// 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 ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.AppxPackageParser;
|
||||||
|
|
||||||
|
public class AppxReader : IDisposable
|
||||||
|
{
|
||||||
|
private ZipFile zip;
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Publisher { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
public string PublisherDisplayName { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Logo { get; set; }
|
||||||
|
public string[] Capabilities { get; set; }
|
||||||
|
|
||||||
|
public Bitmap Icon
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Logo)) return null;
|
||||||
|
|
||||||
|
string extension = Path.GetExtension(Logo);
|
||||||
|
string name = Logo.Substring(0, Logo.Length - extension.Length);
|
||||||
|
|
||||||
|
ZipEntry logoEntry = null;
|
||||||
|
int logoScale = 0;
|
||||||
|
|
||||||
|
foreach (ZipEntry entry in zip)
|
||||||
|
{
|
||||||
|
Match m = Regex.Match(entry.Name, @$"{name}(\.scale\-(\d+))?{extension}");
|
||||||
|
|
||||||
|
if (m.Success)
|
||||||
|
{
|
||||||
|
if (int.TryParse(m.Groups[2].Value, out int currentScale))
|
||||||
|
{
|
||||||
|
if (currentScale > logoScale)
|
||||||
|
{
|
||||||
|
logoEntry = entry;
|
||||||
|
logoScale = currentScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logoEntry != null)
|
||||||
|
{
|
||||||
|
return new Bitmap(zip.GetInputStream(logoEntry));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppxReader(Stream stream)
|
||||||
|
{
|
||||||
|
zip = new ZipFile(stream);
|
||||||
|
Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppxReader(string path)
|
||||||
|
{
|
||||||
|
zip = new ZipFile(path);
|
||||||
|
Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
zip?.Close();
|
||||||
|
zip = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Open()
|
||||||
|
{
|
||||||
|
ZipEntry entry = zip.GetEntry("AppxManifest.xml")
|
||||||
|
?? throw new InvalidDataException("AppxManifest.xml not found");
|
||||||
|
|
||||||
|
XmlDocument xml = new() { XmlResolver = null };
|
||||||
|
xml.Load(zip.GetInputStream(entry));
|
||||||
|
|
||||||
|
XmlElement packageNode = xml.DocumentElement;
|
||||||
|
|
||||||
|
// Identity
|
||||||
|
{
|
||||||
|
XmlElement identityNode = packageNode["Identity"];
|
||||||
|
|
||||||
|
if (identityNode != null)
|
||||||
|
{
|
||||||
|
Name = identityNode.Attributes["Name"]?.Value;
|
||||||
|
Version = identityNode.Attributes["Version"]?.Value;
|
||||||
|
Publisher = identityNode.Attributes["Publisher"]?.Value;
|
||||||
|
|
||||||
|
Match m = Regex.Match(Publisher, @"CN=([^,]*),?");
|
||||||
|
if (m.Success) Publisher = m.Groups[1].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
{
|
||||||
|
XmlElement propertiesNode = packageNode["Properties"];
|
||||||
|
|
||||||
|
if (propertiesNode != null)
|
||||||
|
{
|
||||||
|
DisplayName = propertiesNode["DisplayName"]?.FirstChild?.Value;
|
||||||
|
PublisherDisplayName = propertiesNode["PublisherDisplayName"]?.FirstChild?.Value;
|
||||||
|
Description = propertiesNode["Description"]?.FirstChild?.Value;
|
||||||
|
Logo = propertiesNode["Logo"]?.FirstChild?.Value.Replace(@"\", "/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capabilities
|
||||||
|
{
|
||||||
|
XmlElement capabilitiesNode = packageNode["Capabilities"];
|
||||||
|
|
||||||
|
if (capabilitiesNode != null)
|
||||||
|
{
|
||||||
|
Capabilities = [.. capabilitiesNode.ChildNodes.Select(capability => capability.Attributes["Name"]?.Value)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file static class LinqExtension
|
||||||
|
{
|
||||||
|
public static IEnumerable<dynamic> Select(this XmlNodeList nodes, Func<XmlNode, dynamic> selector)
|
||||||
|
{
|
||||||
|
foreach (XmlNode node in nodes)
|
||||||
|
{
|
||||||
|
yield return selector(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -22,6 +22,4 @@ public interface IAppInfoPanel
|
|||||||
public void DisplayInfo(string path);
|
public void DisplayInfo(string path);
|
||||||
|
|
||||||
public object Tag { get; set; }
|
public object Tag { get; set; }
|
||||||
|
|
||||||
public bool Stop { get; set; }
|
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
using QuickLook.Common.ExtensionMethods;
|
using QuickLook.Common.ExtensionMethods;
|
||||||
using QuickLook.Common.Helpers;
|
using QuickLook.Common.Helpers;
|
||||||
using QuickLook.Plugin.AppViewer.MsiImageParser;
|
using QuickLook.Plugin.AppViewer.MsiPackageParser;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -29,8 +29,6 @@ namespace QuickLook.Plugin.AppViewer;
|
|||||||
|
|
||||||
public partial class MsiInfoPanel : UserControl, IAppInfoPanel
|
public partial class MsiInfoPanel : UserControl, IAppInfoPanel
|
||||||
{
|
{
|
||||||
private bool _stop;
|
|
||||||
|
|
||||||
public MsiInfoPanel()
|
public MsiInfoPanel()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -43,12 +41,6 @@ public partial class MsiInfoPanel : UserControl, IAppInfoPanel
|
|||||||
modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile);
|
modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Stop
|
|
||||||
{
|
|
||||||
set => _stop = value;
|
|
||||||
get => _stop;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisplayInfo(string path)
|
public void DisplayInfo(string path)
|
||||||
{
|
{
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
@@ -70,8 +62,6 @@ public partial class MsiInfoPanel : UserControl, IAppInfoPanel
|
|||||||
var name = Path.GetFileName(path);
|
var name = Path.GetFileName(path);
|
||||||
filename.Text = string.IsNullOrEmpty(name) ? path : name;
|
filename.Text = string.IsNullOrEmpty(name) ? path : name;
|
||||||
|
|
||||||
Stop = false;
|
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
namespace QuickLook.Plugin.AppViewer.MsiImageParser;
|
namespace QuickLook.Plugin.AppViewer.MsiPackageParser;
|
||||||
|
|
||||||
public sealed class MsiInfo
|
public sealed class MsiInfo
|
||||||
{
|
{
|
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
using WixToolset.Dtf.WindowsInstaller;
|
using WixToolset.Dtf.WindowsInstaller;
|
||||||
|
|
||||||
namespace QuickLook.Plugin.AppViewer.MsiImageParser;
|
namespace QuickLook.Plugin.AppViewer.MsiPackageParser;
|
||||||
|
|
||||||
public static class MsiParser
|
public static class MsiParser
|
||||||
{
|
{
|
@@ -33,9 +33,9 @@ public class Plugin : IViewer
|
|||||||
//".aab", // Android App Bundle
|
//".aab", // Android App Bundle
|
||||||
|
|
||||||
// Windows
|
// Windows
|
||||||
//".appx", ".appxbundle", // Windows APPX installer
|
".appx", ".appxbundle", // Windows APPX installer
|
||||||
".msi", // Windows MSI installer
|
".msi", // Windows MSI installer
|
||||||
//".msix", ".msixbundle", // Windows MSIX installer
|
".msix", ".msixbundle", // Windows MSIX installer
|
||||||
|
|
||||||
// macOS
|
// macOS
|
||||||
//".dmg", // macOS DMG
|
//".dmg", // macOS DMG
|
||||||
@@ -67,9 +67,10 @@ public class Plugin : IViewer
|
|||||||
|
|
||||||
public void Prepare(string path, ContextObject context)
|
public void Prepare(string path, ContextObject context)
|
||||||
{
|
{
|
||||||
context.PreferredSize = Path.GetExtension(path) switch
|
context.PreferredSize = Path.GetExtension(path).ToLower() switch
|
||||||
{
|
{
|
||||||
".msi" => new Size { Width = 520, Height = 230 },
|
".msi" => new Size { Width = 520, Height = 230 },
|
||||||
|
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new Size { Width = 560, Height = 320 },
|
||||||
_ => throw new NotSupportedException("Extension is not supported."),
|
_ => throw new NotSupportedException("Extension is not supported."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -77,9 +78,10 @@ public class Plugin : IViewer
|
|||||||
public void View(string path, ContextObject context)
|
public void View(string path, ContextObject context)
|
||||||
{
|
{
|
||||||
_path = path;
|
_path = path;
|
||||||
_ip = Path.GetExtension(path) switch
|
_ip = Path.GetExtension(path).ToLower() switch
|
||||||
{
|
{
|
||||||
".msi" => new MsiInfoPanel(),
|
".msi" => new MsiInfoPanel(),
|
||||||
|
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new AppxInfoPanel(),
|
||||||
_ => throw new NotSupportedException("Extension is not supported."),
|
_ => throw new NotSupportedException("Extension is not supported."),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -66,8 +66,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
<PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="6.0.0" />
|
<PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="6.0.0" />
|
||||||
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -7,6 +7,8 @@
|
|||||||
<MANUFACTURER>Manufacturer</MANUFACTURER>
|
<MANUFACTURER>Manufacturer</MANUFACTURER>
|
||||||
<TOTAL_SIZE>Total Size</TOTAL_SIZE>
|
<TOTAL_SIZE>Total Size</TOTAL_SIZE>
|
||||||
<LAST_MODIFIED>Last Modified</LAST_MODIFIED>
|
<LAST_MODIFIED>Last Modified</LAST_MODIFIED>
|
||||||
|
<PUBLISHER>Publisher</PUBLISHER>
|
||||||
|
<CAPABILITIES>Capabilities</CAPABILITIES>
|
||||||
</en>
|
</en>
|
||||||
<pt-BR>
|
<pt-BR>
|
||||||
<PRODUCT_VERSION>Versão do produto</PRODUCT_VERSION>
|
<PRODUCT_VERSION>Versão do produto</PRODUCT_VERSION>
|
||||||
@@ -14,6 +16,8 @@
|
|||||||
<MANUFACTURER>Fabricante</MANUFACTURER>
|
<MANUFACTURER>Fabricante</MANUFACTURER>
|
||||||
<TOTAL_SIZE>Tamanho total</TOTAL_SIZE>
|
<TOTAL_SIZE>Tamanho total</TOTAL_SIZE>
|
||||||
<LAST_MODIFIED>Modificado em</LAST_MODIFIED>
|
<LAST_MODIFIED>Modificado em</LAST_MODIFIED>
|
||||||
|
<PUBLISHER>Editora</PUBLISHER>
|
||||||
|
<CAPABILITIES>Recursos</CAPABILITIES>
|
||||||
</pt-BR>
|
</pt-BR>
|
||||||
<zh-CN>
|
<zh-CN>
|
||||||
<PRODUCT_VERSION>产品版本</PRODUCT_VERSION>
|
<PRODUCT_VERSION>产品版本</PRODUCT_VERSION>
|
||||||
@@ -21,6 +25,8 @@
|
|||||||
<MANUFACTURER>制造商</MANUFACTURER>
|
<MANUFACTURER>制造商</MANUFACTURER>
|
||||||
<TOTAL_SIZE>总大小</TOTAL_SIZE>
|
<TOTAL_SIZE>总大小</TOTAL_SIZE>
|
||||||
<LAST_MODIFIED>修改时间</LAST_MODIFIED>
|
<LAST_MODIFIED>修改时间</LAST_MODIFIED>
|
||||||
|
<PUBLISHER>发布者</PUBLISHER>
|
||||||
|
<CAPABILITIES>功能</CAPABILITIES>
|
||||||
</zh-CN>
|
</zh-CN>
|
||||||
<zh-TW>
|
<zh-TW>
|
||||||
<PRODUCT_VERSION>產品版本</PRODUCT_VERSION>
|
<PRODUCT_VERSION>產品版本</PRODUCT_VERSION>
|
||||||
@@ -28,6 +34,8 @@
|
|||||||
<MANUFACTURER>製造商</MANUFACTURER>
|
<MANUFACTURER>製造商</MANUFACTURER>
|
||||||
<TOTAL_SIZE>縂大小</TOTAL_SIZE>
|
<TOTAL_SIZE>縂大小</TOTAL_SIZE>
|
||||||
<LAST_MODIFIED>修改時間</LAST_MODIFIED>
|
<LAST_MODIFIED>修改時間</LAST_MODIFIED>
|
||||||
|
<PUBLISHER>發行者</PUBLISHER>
|
||||||
|
<CAPABILITIES>功能</CAPABILITIES>
|
||||||
</zh-TW>
|
</zh-TW>
|
||||||
<ja>
|
<ja>
|
||||||
<PRODUCT_VERSION>製品バージョン</PRODUCT_VERSION>
|
<PRODUCT_VERSION>製品バージョン</PRODUCT_VERSION>
|
||||||
@@ -35,5 +43,7 @@
|
|||||||
<MANUFACTURER>製造元</MANUFACTURER>
|
<MANUFACTURER>製造元</MANUFACTURER>
|
||||||
<TOTAL_SIZE>合計サイズ</TOTAL_SIZE>
|
<TOTAL_SIZE>合計サイズ</TOTAL_SIZE>
|
||||||
<LAST_MODIFIED>更新日時</LAST_MODIFIED>
|
<LAST_MODIFIED>更新日時</LAST_MODIFIED>
|
||||||
|
<PUBLISHER>発行元</PUBLISHER>
|
||||||
|
<CAPABILITIES>機能</CAPABILITIES>
|
||||||
</ja>
|
</ja>
|
||||||
</Translations>
|
</Translations>
|
||||||
|
Reference in New Issue
Block a user