mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-21 00:44:52 +00:00
Support .dmg
This commit is contained in:
@@ -0,0 +1,274 @@
|
|||||||
|
<UserControl x:Class="QuickLook.Plugin.AppViewer.InfoPanels.DmgInfoPanel"
|
||||||
|
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">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="GroupBox.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="120"
|
||||||
|
Height="120"
|
||||||
|
Margin="8"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Opacity="0"
|
||||||
|
RenderOptions.BitmapScalingMode="HighQuality"
|
||||||
|
SnapsToDevicePixels="True"
|
||||||
|
Stretch="Fill"
|
||||||
|
UseLayoutRounding="True">
|
||||||
|
<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="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>
|
||||||
|
<!-- Application Name -->
|
||||||
|
<TextBlock x:Name="applicationNameTitle"
|
||||||
|
Grid.Row="3"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Application Name" />
|
||||||
|
<TextBlock x:Name="applicationName"
|
||||||
|
Grid.Row="3"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- Version Name -->
|
||||||
|
<TextBlock x:Name="versionNameTitle"
|
||||||
|
Grid.Row="4"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Version Name" />
|
||||||
|
<TextBlock x:Name="versionName"
|
||||||
|
Grid.Row="4"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- Version Code -->
|
||||||
|
<TextBlock x:Name="versionCodeTitle"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Version Code" />
|
||||||
|
<TextBlock x:Name="versionCode"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- Package Name -->
|
||||||
|
<TextBlock x:Name="packageNameTitle"
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Package Name" />
|
||||||
|
<TextBlock x:Name="packageName"
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- Device Family -->
|
||||||
|
<TextBlock x:Name="deviceFamilyTitle"
|
||||||
|
Grid.Row="7"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Device Family" />
|
||||||
|
<TextBlock x:Name="deviceFamily"
|
||||||
|
Grid.Row="7"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- MinimumOSVersion -->
|
||||||
|
<TextBlock x:Name="minimumOSVersionTitle"
|
||||||
|
Grid.Row="8"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="MinimumOSVersion" />
|
||||||
|
<TextBlock x:Name="minimumOSVersion"
|
||||||
|
Grid.Row="8"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<!-- PlatformVersion -->
|
||||||
|
<TextBlock x:Name="platformVersionTitle"
|
||||||
|
Grid.Row="9"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="PlatformVersion" />
|
||||||
|
<TextBlock x:Name="platformVersion"
|
||||||
|
Grid.Row="9"
|
||||||
|
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="10"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Total Size" />
|
||||||
|
<TextBlock x:Name="totalSize"
|
||||||
|
Grid.Row="10"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Calculating size..." />
|
||||||
|
<!-- Last Modified -->
|
||||||
|
<TextBlock x:Name="modDateTitle"
|
||||||
|
Grid.Row="11"
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Last Modified" />
|
||||||
|
<TextBlock x:Name="modDate"
|
||||||
|
Grid.Row="11"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Padding="3"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
Text="Searching..."
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
<!-- Permissions -->
|
||||||
|
<GroupBox x:Name="permissionsGroupBox"
|
||||||
|
Grid.Row="12"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="3,3,16,16"
|
||||||
|
Padding="3"
|
||||||
|
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||||
|
Foreground="{DynamicResource WindowTextForegroundAlternative}"
|
||||||
|
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="permissions">
|
||||||
|
<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,96 @@
|
|||||||
|
// 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.Common.Plugin;
|
||||||
|
using QuickLook.Plugin.AppViewer.PackageParsers.Dmg;
|
||||||
|
using System;
|
||||||
|
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.InfoPanels;
|
||||||
|
|
||||||
|
public partial class DmgInfoPanel : UserControl, IAppInfoPanel
|
||||||
|
{
|
||||||
|
private readonly ContextObject _context;
|
||||||
|
|
||||||
|
public DmgInfoPanel(ContextObject context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
|
||||||
|
DataContext = this;
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config");
|
||||||
|
applicationNameTitle.Text = TranslationHelper.Get("APP_NAME", translationFile);
|
||||||
|
versionNameTitle.Text = TranslationHelper.Get("APP_VERSION_NAME", translationFile);
|
||||||
|
versionCodeTitle.Text = TranslationHelper.Get("APP_VERSION_CODE", translationFile);
|
||||||
|
packageNameTitle.Text = TranslationHelper.Get("PACKAGE_NAME", translationFile);
|
||||||
|
minimumOSVersionTitle.Text = TranslationHelper.Get("APP_MIN_OS_VERSION", translationFile);
|
||||||
|
deviceFamilyTitle.Text = TranslationHelper.Get("DEVICE_FAMILY", translationFile);
|
||||||
|
platformVersionTitle.Text = TranslationHelper.Get("APP_TARGET_OS_VERSION", translationFile);
|
||||||
|
totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile);
|
||||||
|
modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile);
|
||||||
|
permissionsGroupBox.Header = TranslationHelper.Get("PERMISSIONS", 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;
|
||||||
|
DmgInfo dmgInfo = DmgParser.Parse(path);
|
||||||
|
var last = File.GetLastWriteTime(path);
|
||||||
|
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
applicationName.Text = dmgInfo.DisplayName;
|
||||||
|
versionName.Text = dmgInfo.VersionName;
|
||||||
|
versionCode.Text = dmgInfo.VersionCode;
|
||||||
|
packageName.Text = dmgInfo.Identifier;
|
||||||
|
minimumOSVersion.Text = dmgInfo.MinimumOSVersion;
|
||||||
|
platformVersion.Text = dmgInfo.PlatformVersion;
|
||||||
|
deviceFamily.Text = dmgInfo.SupportedPlatforms;
|
||||||
|
totalSize.Text = size.ToPrettySize(2);
|
||||||
|
modDate.Text = last.ToString(CultureInfo.CurrentCulture);
|
||||||
|
permissions.ItemsSource = dmgInfo.Permissions;
|
||||||
|
|
||||||
|
if (dmgInfo.HasIcon)
|
||||||
|
{
|
||||||
|
image.Source = dmgInfo.Logo.ToBitmapSource();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
image.Source = new BitmapImage(new Uri("pack://application:,,,/QuickLook.Plugin.AppViewer;component/Resources/ios.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.IsBusy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
// 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 DiscUtils.HfsPlus;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.PackageParsers.Dmg;
|
||||||
|
|
||||||
|
public class DmgArchive : IDisposable
|
||||||
|
{
|
||||||
|
public string Entry { get; set; }
|
||||||
|
|
||||||
|
public HfsPlusFileSystem FileSystem { get; set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
FileSystem?.Dispose();
|
||||||
|
FileSystem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetBytes()
|
||||||
|
{
|
||||||
|
if (Entry is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (FileSystem is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
using var stream = FileSystem.OpenFile(Entry, FileMode.Open, FileAccess.Read);
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
stream.CopyTo(ms);
|
||||||
|
byte[] fileBytes = ms.ToArray();
|
||||||
|
|
||||||
|
return fileBytes;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
// 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.PackageParsers.Dmg;
|
||||||
|
|
||||||
|
public class DmgInfo
|
||||||
|
{
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
public string VersionName { get; set; }
|
||||||
|
|
||||||
|
public string VersionCode { get; set; }
|
||||||
|
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
|
||||||
|
public string MinimumOSVersion { get; set; }
|
||||||
|
|
||||||
|
public string PlatformVersion { get; set; }
|
||||||
|
|
||||||
|
public string SupportedPlatforms { get; set; }
|
||||||
|
|
||||||
|
public string[] Permissions { get; set; } = [];
|
||||||
|
|
||||||
|
public Bitmap Logo { get; set; }
|
||||||
|
|
||||||
|
public bool HasIcon => Logo is not null;
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.PackageParsers.Dmg;
|
||||||
|
|
||||||
|
public static class DmgParser
|
||||||
|
{
|
||||||
|
public static DmgInfo Parse(string path)
|
||||||
|
{
|
||||||
|
using DmgReader reader = new(path);
|
||||||
|
|
||||||
|
return new DmgInfo()
|
||||||
|
{
|
||||||
|
DisplayName = reader.DisplayName,
|
||||||
|
VersionName = reader.ShortVersionString,
|
||||||
|
VersionCode = reader.Version,
|
||||||
|
Identifier = reader.Identifier,
|
||||||
|
MinimumOSVersion = reader.MinimumOSVersion,
|
||||||
|
SupportedPlatforms = reader.SupportedPlatforms,
|
||||||
|
PlatformVersion = reader.PlatformVersion,
|
||||||
|
Permissions = [.. reader.InfoPlistDict.Keys.Where(key => key.StartsWith("NS") && key.EndsWith("UsageDescription"))],
|
||||||
|
Logo = reader.Logo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,216 @@
|
|||||||
|
// 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 DiscUtils;
|
||||||
|
using DiscUtils.HfsPlus;
|
||||||
|
using QuickLook.Plugin.AppViewer.PackageParsers.Ipa;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.PackageParsers.Dmg;
|
||||||
|
|
||||||
|
public class DmgReader : IDisposable
|
||||||
|
{
|
||||||
|
public string VolumeLabel { get; set; }
|
||||||
|
|
||||||
|
public string ContentsEntry { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, DmgArchive> Archives { get; } = [];
|
||||||
|
|
||||||
|
public Dictionary<string, object> InfoPlistDict { get; set; }
|
||||||
|
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
public string ShortVersionString { get; set; }
|
||||||
|
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
|
||||||
|
public byte[] Icon { get; set; }
|
||||||
|
|
||||||
|
public string IconName { get; set; }
|
||||||
|
|
||||||
|
public string IconEntry { get; set; }
|
||||||
|
|
||||||
|
public Bitmap Logo { get; set; }
|
||||||
|
|
||||||
|
public string MinimumOSVersion { get; set; }
|
||||||
|
|
||||||
|
public string PlatformVersion { get; set; }
|
||||||
|
|
||||||
|
public string SupportedPlatforms { get; set; }
|
||||||
|
|
||||||
|
static DmgReader()
|
||||||
|
{
|
||||||
|
DiscUtils.Complete.SetupHelper.SetupComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DmgReader(string path)
|
||||||
|
{
|
||||||
|
Open(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Archives is not null)
|
||||||
|
{
|
||||||
|
foreach (var archive in Archives.Values)
|
||||||
|
{
|
||||||
|
archive.Dispose();
|
||||||
|
}
|
||||||
|
Archives.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Open(string path)
|
||||||
|
{
|
||||||
|
using var disk = VirtualDisk.OpenDisk(path, FileAccess.Read, useAsync: false);
|
||||||
|
if (disk is null)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Failed to open '{path}' as virtual disk.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first (and supposedly, only, HFS partition)
|
||||||
|
foreach (var volume in VolumeManager.GetPhysicalVolumes(disk))
|
||||||
|
{
|
||||||
|
foreach (var fileSystem in FileSystemManager.DetectFileSystems(volume))
|
||||||
|
{
|
||||||
|
// Apple HFS+
|
||||||
|
if (fileSystem.Name == "HFS+")
|
||||||
|
{
|
||||||
|
using var hfs = (HfsPlusFileSystem)fileSystem.Open(volume);
|
||||||
|
|
||||||
|
VolumeLabel = hfs.VolumeLabel;
|
||||||
|
ListFiles(hfs, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] infoPlistData = null;
|
||||||
|
|
||||||
|
foreach (var archive in Archives.Values)
|
||||||
|
{
|
||||||
|
Match m = Regex.Match(archive.Entry, @".*\.app\\Contents\\Info\.plist$");
|
||||||
|
|
||||||
|
if (m.Success)
|
||||||
|
{
|
||||||
|
ContentsEntry = Path.GetDirectoryName(archive.Entry);
|
||||||
|
infoPlistData = archive.GetBytes();
|
||||||
|
if (Plist.ReadPlist(infoPlistData) is Dictionary<string, object> dict)
|
||||||
|
{
|
||||||
|
InfoPlistDict = dict;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("CFBundleDisplayName", out object value) && value is string stringValue)
|
||||||
|
{
|
||||||
|
DisplayName = stringValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("CFBundleShortVersionString", out object value) && value is string stringValue)
|
||||||
|
{
|
||||||
|
ShortVersionString = stringValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("CFBundleVersion", out object value) && value is string stringValue)
|
||||||
|
{
|
||||||
|
Version = stringValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("CFBundleIdentifier", out object value) && value is string stringValue)
|
||||||
|
{
|
||||||
|
Identifier = stringValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("LSMinimumSystemVersion", out object value) && value is string stringValue)
|
||||||
|
{
|
||||||
|
MinimumOSVersion = $"macOS {stringValue}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("DTPlatformVersion", out object value) && value is string stringValue)
|
||||||
|
{
|
||||||
|
PlatformVersion = $"macOS {stringValue}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("CFBundleSupportedPlatforms", out object familyNode) && familyNode is IEnumerable<object> list)
|
||||||
|
{
|
||||||
|
SupportedPlatforms = string.Join(", ", list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("CFBundleIconFile", out object iconFilesNode) && iconFilesNode is object iconFile)
|
||||||
|
{
|
||||||
|
IconName = iconFile as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(IconName))
|
||||||
|
{
|
||||||
|
foreach (var archive in Archives.Values)
|
||||||
|
{
|
||||||
|
if (archive.Entry.StartsWith($@"{ContentsEntry}\Resources\{IconName}."))
|
||||||
|
{
|
||||||
|
IconEntry = archive.Entry;
|
||||||
|
Icon = archive.GetBytes();
|
||||||
|
|
||||||
|
if (Path.GetExtension(IconEntry).ToLower() == ".icns")
|
||||||
|
{
|
||||||
|
Logo = IcnsParser.Parse(Icon);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListFiles(HfsPlusFileSystem fs, string path)
|
||||||
|
{
|
||||||
|
foreach (var entry in fs.GetFileSystemEntries(path))
|
||||||
|
{
|
||||||
|
Debug.WriteLine(entry);
|
||||||
|
|
||||||
|
if (fs.DirectoryExists(entry))
|
||||||
|
{
|
||||||
|
ListFiles(fs, entry);
|
||||||
|
}
|
||||||
|
else if (fs.FileExists(entry))
|
||||||
|
{
|
||||||
|
Archives.Add(entry, new DmgArchive()
|
||||||
|
{
|
||||||
|
Entry = entry,
|
||||||
|
FileSystem = fs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace QuickLook.Plugin.AppViewer.PackageParsers.Dmg;
|
||||||
|
|
||||||
|
public static class IcnsParser
|
||||||
|
{
|
||||||
|
public static Bitmap Parse(byte[] icnsBytes)
|
||||||
|
{
|
||||||
|
// Temporary method
|
||||||
|
|
||||||
|
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.Where((assembly) => assembly.FullName.StartsWith("QuickLook.Plugin.ImageViewer"))
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (assembly == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Type type = assembly.GetTypes()
|
||||||
|
.Where(type => type.Name.StartsWith("IcnsImageParser"))
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (type == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
MethodInfo method = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.Where(method => method.Name == "GetImages"
|
||||||
|
&& method.GetParameters().FirstOrDefault()?.ParameterType == typeof(Stream))
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (method == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
using MemoryStream stream = new(icnsBytes);
|
||||||
|
dynamic[] images = method.Invoke(null, [stream]) as dynamic[];
|
||||||
|
List<Bitmap> bitmaps = [];
|
||||||
|
|
||||||
|
foreach (dynamic image in images)
|
||||||
|
{
|
||||||
|
if (image.GetType().GetProperty("Bitmap") is PropertyInfo property)
|
||||||
|
{
|
||||||
|
var bitmap = property.GetValue(image);
|
||||||
|
bitmaps.Add(bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap imageResult = bitmaps
|
||||||
|
.Where(bitmap => bitmap != null)
|
||||||
|
.OrderByDescending(bitmap => bitmap.Width)
|
||||||
|
.FirstOrDefault()
|
||||||
|
?.Clone() as Bitmap;
|
||||||
|
|
||||||
|
foreach (dynamic image in images)
|
||||||
|
{
|
||||||
|
if (image.GetType().GetProperty("Bitmap") is PropertyInfo property)
|
||||||
|
{
|
||||||
|
if (property.GetValue(image) is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageResult;
|
||||||
|
}
|
||||||
|
}
|
@@ -156,6 +156,10 @@ public class IpaReader
|
|||||||
{
|
{
|
||||||
IconName = iconFiles.LastOrDefault() as string;
|
IconName = iconFiles.LastOrDefault() as string;
|
||||||
}
|
}
|
||||||
|
else if (primaryIcons.TryGetValue("CFBundleIconFile", out object iconFileNode) && iconFileNode is object iconFile)
|
||||||
|
{
|
||||||
|
IconName = iconFile as string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (string.IsNullOrWhiteSpace(IconName))
|
if (string.IsNullOrWhiteSpace(IconName))
|
||||||
@@ -165,6 +169,13 @@ public class IpaReader
|
|||||||
IconName = iconFiles.LastOrDefault() as string;
|
IconName = iconFiles.LastOrDefault() as string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(IconName))
|
||||||
|
{
|
||||||
|
if (InfoPlistDict.TryGetValue("CFBundleIconFile", out object iconFilesNode) && iconFilesNode is object iconFile)
|
||||||
|
{
|
||||||
|
IconName = iconFile as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(IconName))
|
if (!string.IsNullOrWhiteSpace(IconName))
|
||||||
{
|
{
|
||||||
foreach (ZipEntry entry in zip)
|
foreach (ZipEntry entry in zip)
|
||||||
|
@@ -39,7 +39,7 @@ public class Plugin : IViewer
|
|||||||
".msix", ".msixbundle", // Windows MSIX installer
|
".msix", ".msixbundle", // Windows MSIX installer
|
||||||
|
|
||||||
// macOS
|
// macOS
|
||||||
//".dmg", // macOS DMG
|
".dmg", // macOS DMG
|
||||||
|
|
||||||
// iOS
|
// iOS
|
||||||
".ipa", // iOS IPA
|
".ipa", // iOS IPA
|
||||||
@@ -79,6 +79,7 @@ public class Plugin : IViewer
|
|||||||
".msi" => new Size { Width = 560, Height = 230 },
|
".msi" => new Size { Width = 560, Height = 230 },
|
||||||
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new Size { Width = 560, Height = 328 },
|
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new Size { Width = 560, Height = 328 },
|
||||||
".deb" => new Size { Width = 600, Height = 345 },
|
".deb" => new Size { Width = 600, Height = 345 },
|
||||||
|
".dmg" => new Size { Width = 560, Height = 510 },
|
||||||
".wgt" or ".wgtu" => new Size { Width = 600, Height = 345 },
|
".wgt" or ".wgtu" => new Size { Width = 600, Height = 345 },
|
||||||
_ => throw new NotSupportedException("Extension is not supported."),
|
_ => throw new NotSupportedException("Extension is not supported."),
|
||||||
};
|
};
|
||||||
@@ -100,6 +101,7 @@ public class Plugin : IViewer
|
|||||||
".msi" => new MsiInfoPanel(context),
|
".msi" => new MsiInfoPanel(context),
|
||||||
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new AppxInfoPanel(context),
|
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new AppxInfoPanel(context),
|
||||||
".deb" => new DebInfoPanel(context),
|
".deb" => new DebInfoPanel(context),
|
||||||
|
".dmg" => new DmgInfoPanel(context),
|
||||||
".wgt" or ".wgtu" => new WgtInfoPanel(context),
|
".wgt" or ".wgtu" => new WgtInfoPanel(context),
|
||||||
_ => throw new NotSupportedException("Extension is not supported."),
|
_ => throw new NotSupportedException("Extension is not supported."),
|
||||||
};
|
};
|
||||||
|
@@ -77,6 +77,7 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="ApkReader" Version="2.0.1.1" />
|
<PackageReference Include="ApkReader" Version="2.0.1.1" />
|
||||||
<PackageReference Include="PureSharpCompress" Version="0.39.0" />
|
<PackageReference Include="PureSharpCompress" Version="0.39.0" />
|
||||||
|
<PackageReference Include="QuickLook.HfsPlus" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
Reference in New Issue
Block a user