diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml new file mode 100644 index 0000000..1605e82 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml.cs new file mode 100644 index 0000000..79cd9fe --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml.cs @@ -0,0 +1,93 @@ +// 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 . + +using QuickLook.Common.ExtensionMethods; +using QuickLook.Common.Helpers; +using QuickLook.Common.Plugin; +using QuickLook.Plugin.AppViewer.PackageParsers.AppImage; +using QuickLook.Plugin.AppViewer.PackageParsers.Rpm; +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 RpmInfoPanel : UserControl, IAppInfoPanel +{ + private readonly ContextObject _context; + + public RpmInfoPanel(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); + versionTitle.Text = TranslationHelper.Get("APP_VERSION", translationFile); + architectureTitle.Text = TranslationHelper.Get("ARCHITECTURE", translationFile); + typeTitle.Text = TranslationHelper.Get("TYPE", translationFile); + terminalTitle.Text = TranslationHelper.Get("TERMINAL", translationFile); + totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile); + modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile); + environmentGroupBox.Header = TranslationHelper.Get("ENVIRONMENT", 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; + RpmInfo rpmInfo = RpmParser.Parse(path); + var last = File.GetLastWriteTime(path); + + Dispatcher.Invoke(() => + { + applicationName.Text = rpmInfo.Name; + version.Text = rpmInfo.Version; + architectureName.Text = rpmInfo.Arch; + type.Text = rpmInfo.Type; + terminal.Text = rpmInfo.Terminal; + totalSize.Text = size.ToPrettySize(2); + modDate.Text = last.ToString(CultureInfo.CurrentCulture); + permissions.ItemsSource = rpmInfo.Env; + + if (rpmInfo.HasIcon) + { + image.Source = rpmInfo.Logo.ToBitmapSource(); + } + else + { + image.Source = new BitmapImage(new Uri("pack://application:,,,/QuickLook.Plugin.AppViewer;component/Resources/rpm.png")); + } + + _context.IsBusy = false; + }); + } + }); + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmInfo.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmInfo.cs new file mode 100644 index 0000000..8f65de7 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmInfo.cs @@ -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 . + +using System.Drawing; + +namespace QuickLook.Plugin.AppViewer.PackageParsers.Rpm; + +public class RpmInfo +{ + public string Arch { get; set; } + + public string Version { get; set; } + + public string Name { get; set; } + + public string Exec { get; set; } + + public string Icon { get; set; } + + public Bitmap Logo { get; set; } + + public bool HasIcon => !string.IsNullOrEmpty(Icon) && Logo != null; + + public string Type { get; set; } + + public string Terminal { get; set; } + + public string[] Env { get; set; } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmParser.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmParser.cs new file mode 100644 index 0000000..f3a4327 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmParser.cs @@ -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 . + +using QuickLook.Plugin.AppViewer.PackageParsers.AppImage; + +namespace QuickLook.Plugin.AppViewer.PackageParsers.Rpm; + +public class RpmParser +{ + public static RpmInfo Parse(string path) + { + var reader = new RpmReader(path); + + return new RpmInfo() + { + Arch = reader.Arch, + Version = reader.Version, + Name = reader.Name, + Exec = reader.Exec, + Icon = reader.Icon, + Logo = reader.Logo, + Type = reader.Type, + Terminal = reader.Terminal, + Env = reader.Env, + }; + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmReader.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmReader.cs new file mode 100644 index 0000000..0056a96 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmReader.cs @@ -0,0 +1,117 @@ +// 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 . + +using PureSharpCompress.Common; +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Text; + +namespace QuickLook.Plugin.AppViewer.PackageParsers.Rpm; + +public class RpmReader +{ + public string Arch { get; set; } + + public string Version { get; set; } + + public string Name { get; set; } + + public string Exec { get; set; } + + public string Icon { get; set; } + + public Bitmap Logo { get; set; } + + public string Type { get; set; } + + public string Terminal { get; set; } + + public string[] Env { get; set; } + + public RpmReader(Stream stream) + { + Open(stream); + } + + public RpmReader(string path) + { + using FileStream fs = File.OpenRead(path); + Open(fs); + } + + private void Open(Stream stream) + { + using var br = new BinaryReader(stream); + + // Step 1: Read the lead (96 bytes) + byte[] lead = br.ReadBytes(96); + Debug.WriteLine($"[lead] 96 bytes read, magic: {BitConverter.ToString(lead, 0, 4)}"); + + // Step 2: Read signature header + RpmHeader sigHeader = ReadHeader(br); + Debug.WriteLine($"[signature header] IndexCount: {sigHeader.IndexCount}, DataSize: {sigHeader.DataSize}"); + + // Step 3: Read main header + RpmHeader mainHeader = ReadHeader(br); + Debug.WriteLine($"[main header] IndexCount: {mainHeader.IndexCount}, DataSize: {mainHeader.DataSize}"); + + // Step 4: Remaining is the payload (cpio archive + compression) + long payloadSize = stream.Length - stream.Position; + Debug.WriteLine($"[payload] Size: {payloadSize} bytes at offset: {stream.Position}"); + + // (Optional) Detect compression (e.g. gzip, xz, zstd) + byte[] magic = br.ReadBytes(6); + string type = magic[0] switch + { + 0x1F when magic[1] == 0x8B => "gzip", + 0xFD when magic[1] == 0x37 => "xz", + 0x28 when magic[1] == 0xB5 => "zstd", + _ => "unknown" + }; + Debug.WriteLine($"Detected payload compression: {type}"); + } + + private static RpmHeader ReadHeader(BinaryReader br) + { + // ed ab ee db 03 + byte[] magic = br.ReadBytes(3); + //if (Encoding.ASCII.GetString(magic) != "\x8e\xad\xe8") + //throw new InvalidDataException("Invalid RPM header magic"); + + byte version = br.ReadByte(); // Usually 1 + byte[] reserved = br.ReadBytes(4); + int indexCount = ReadBigEndianInt32(br); + int dataSize = ReadBigEndianInt32(br); + return new RpmHeader { IndexCount = indexCount, DataSize = dataSize }; + } + + private static int ReadBigEndianInt32(BinaryReader br) + { + byte[] b = br.ReadBytes(4); + if (b.Length < 4) throw new EndOfStreamException(); + return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; + } + + private class RpmHeader + { + public int IndexCount { get; set; } + + public int DataSize { get; set; } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs index 9e57119..eb9430e 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs @@ -51,6 +51,7 @@ public class Plugin : IViewer // Ubuntu ".deb", // Debian Package ".appimage", // AppImage Format + // ".rpm", // Red Hat Package Manager // Others ".wgt", ".wgtu", // UniApp Widget @@ -82,6 +83,7 @@ public class Plugin : IViewer ".deb" => new Size { Width = 600, Height = 345 }, ".dmg" => new Size { Width = 560, Height = 510 }, ".appimage" => new Size { Width = 600, Height = 300 }, + ".rpm" => new Size { Width = 600, Height = 300 }, ".wgt" or ".wgtu" => new Size { Width = 600, Height = 345 }, _ => throw new NotSupportedException("Extension is not supported."), }; @@ -105,6 +107,7 @@ public class Plugin : IViewer ".deb" => new DebInfoPanel(context), ".dmg" => new DmgInfoPanel(context), ".appimage" => new AppImageInfoPanel(context), + ".rpm" => new RpmInfoPanel(context), ".wgt" or ".wgtu" => new WgtInfoPanel(context), _ => throw new NotSupportedException("Extension is not supported."), }; diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Resources/rpm.png b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Resources/rpm.png new file mode 100644 index 0000000..b153973 Binary files /dev/null and b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Resources/rpm.png differ