mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-20 16:34:55 +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;
|
||||
}
|
||||
else if (primaryIcons.TryGetValue("CFBundleIconFile", out object iconFileNode) && iconFileNode is object iconFile)
|
||||
{
|
||||
IconName = iconFile as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(IconName))
|
||||
@@ -165,6 +169,13 @@ public class IpaReader
|
||||
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))
|
||||
{
|
||||
foreach (ZipEntry entry in zip)
|
||||
|
@@ -39,7 +39,7 @@ public class Plugin : IViewer
|
||||
".msix", ".msixbundle", // Windows MSIX installer
|
||||
|
||||
// macOS
|
||||
//".dmg", // macOS DMG
|
||||
".dmg", // macOS DMG
|
||||
|
||||
// iOS
|
||||
".ipa", // iOS IPA
|
||||
@@ -79,6 +79,7 @@ public class Plugin : IViewer
|
||||
".msi" => new Size { Width = 560, Height = 230 },
|
||||
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new Size { Width = 560, Height = 328 },
|
||||
".deb" => new Size { Width = 600, Height = 345 },
|
||||
".dmg" => new Size { Width = 560, Height = 510 },
|
||||
".wgt" or ".wgtu" => new Size { Width = 600, Height = 345 },
|
||||
_ => throw new NotSupportedException("Extension is not supported."),
|
||||
};
|
||||
@@ -100,6 +101,7 @@ public class Plugin : IViewer
|
||||
".msi" => new MsiInfoPanel(context),
|
||||
".msix" or ".msixbundle" or ".appx" or ".appxbundle" => new AppxInfoPanel(context),
|
||||
".deb" => new DebInfoPanel(context),
|
||||
".dmg" => new DmgInfoPanel(context),
|
||||
".wgt" or ".wgtu" => new WgtInfoPanel(context),
|
||||
_ => throw new NotSupportedException("Extension is not supported."),
|
||||
};
|
||||
|
@@ -77,6 +77,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="ApkReader" Version="2.0.1.1" />
|
||||
<PackageReference Include="PureSharpCompress" Version="0.39.0" />
|
||||
<PackageReference Include="QuickLook.HfsPlus" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
Reference in New Issue
Block a user