Update Program.cs

This commit is contained in:
2026-01-24 04:04:20 +00:00
parent dea62235b8
commit 7639804a82

View File

@@ -2,6 +2,7 @@ using System.CommandLine;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
namespace OpenMaui.AppImage;
@@ -88,7 +89,7 @@ class Program
}
}
public class AppImageOptions
public record AppImageOptions
{
public required DirectoryInfo InputDirectory { get; init; }
public required FileInfo OutputFile { get; init; }
@@ -145,6 +146,17 @@ public class AppImageBuilder
}
}
// Auto-detect icon if not specified
if (options.IconPath == null || !options.IconPath.Exists)
{
var detectedIcon = FindIcon(options.InputDirectory.FullName, execName);
if (detectedIcon != null)
{
options = options with { IconPath = new FileInfo(detectedIcon) };
Console.WriteLine($" Auto-detected icon: {Path.GetFileName(detectedIcon)}");
}
}
// Create temporary AppDir structure
var tempDir = Path.Combine(Path.GetTempPath(), $"appimage-{Guid.NewGuid():N}");
var appDir = Path.Combine(tempDir, $"{options.AppName}.AppDir");
@@ -301,11 +313,21 @@ if [ -n ""$APPIMAGE"" ]; then
APPIMAGE_BASENAME=$(basename ""$APPIMAGE"")
if [ ! -f ""$INSTALLED_MARKER/$APPIMAGE_BASENAME"" ] || [ ""$SHOW_INSTALLER"" = ""1"" ]; then
if command -v zenity &> /dev/null; then
# Find app icon
SANITIZED=$(echo ""$APPIMAGE_NAME"" | tr ' ' '_')
ICON_PATH=""""
for ext in svg png ico; do
[ -f ""$HERE/${{SANITIZED}}.${{ext}}"" ] && ICON_PATH=""$HERE/${{SANITIZED}}.${{ext}}"" && break
done
ICON_OPT=""""
[ -n ""$ICON_PATH"" ] && ICON_OPT=""--window-icon=$ICON_PATH""
CHOICE=$(zenity --question --title=""$APPIMAGE_NAME"" \
--text=""<b>$APPIMAGE_NAME</b>\nVersion $APPIMAGE_VERSION\n\n$APPIMAGE_COMMENT\n\nWould you like to install this application?"" \
--ok-label=""Install"" --cancel-label=""Run Without Installing"" \
--extra-button=""Cancel"" \
--width=350 --icon-name=application-x-executable 2>/dev/null; echo $?)
--width=350 $ICON_OPT 2>/dev/null; echo $?)
case ""$CHOICE"" in
0) # Install clicked
@@ -313,7 +335,7 @@ if [ -n ""$APPIMAGE"" ]; then
if [ $? -eq 0 ]; then
zenity --info --title=""Installation Complete"" \
--text=""$APPIMAGE_NAME has been installed.\n\nYou can find it in your application menu."" \
--width=300 2>/dev/null
--width=300 $ICON_OPT 2>/dev/null
fi
;;
1) # Run Without Installing
@@ -436,6 +458,211 @@ X-AppImage-Version={options.Version}
return false;
}
private string? FindIcon(string inputDir, string execName)
{
// First, try to find and parse the .csproj to get MauiIcon
var csprojIcon = TryGetIconFromCsproj(inputDir, execName);
if (csprojIcon != null)
return csprojIcon;
// Fallback to searching for icon files directly
var extensions = new[] { ".svg", ".png", ".ico" };
var searchPatterns = new[]
{
"appicon_combined", // Combined icon for Linux desktop
"appicon", // MAUI standard: appicon.svg
"AppIcon", // AppIcon.svg
execName, // ShellDemo.svg
execName.ToLowerInvariant(), // shelldemo.svg
"icon", // icon.svg
"logo", // logo.svg
};
var searchDirs = new[]
{
Path.Combine(inputDir, "Resources", "AppIcon"),
Path.Combine(inputDir, "Resources", "Splash"),
inputDir,
Path.Combine(inputDir, "Resources"),
Path.Combine(inputDir, "Resources", "Images"),
};
foreach (var dir in searchDirs)
{
if (!Directory.Exists(dir)) continue;
foreach (var pattern in searchPatterns)
{
foreach (var ext in extensions)
{
var iconPath = Path.Combine(dir, pattern + ext);
if (File.Exists(iconPath))
return iconPath;
}
}
}
return null;
}
private string? TryGetIconFromCsproj(string inputDir, string execName)
{
// Find the project directory (go up from publish directory)
var projectDir = FindProjectDirectory(inputDir, execName);
if (projectDir == null)
return null;
// Find .csproj file
var csprojFiles = Directory.GetFiles(projectDir, "*.csproj");
if (csprojFiles.Length == 0)
return null;
var csprojPath = csprojFiles[0];
Console.WriteLine($" Found project: {Path.GetFileName(csprojPath)}");
try
{
var doc = XDocument.Load(csprojPath);
var ns = doc.Root?.Name.Namespace ?? XNamespace.None;
// Find MauiIcon element
var mauiIcon = doc.Descendants("MauiIcon").FirstOrDefault();
if (mauiIcon == null)
return null;
var includeAttr = mauiIcon.Attribute("Include")?.Value;
var foregroundAttr = mauiIcon.Attribute("ForegroundFile")?.Value;
var colorAttr = mauiIcon.Attribute("Color")?.Value ?? "#FFFFFF";
var bgColorAttr = mauiIcon.Attribute("BackgroundColor")?.Value;
if (string.IsNullOrEmpty(includeAttr))
return null;
// Resolve paths (convert backslashes to forward slashes for Linux)
var bgPath = Path.Combine(projectDir, includeAttr.Replace('\\', '/'));
var fgPath = !string.IsNullOrEmpty(foregroundAttr)
? Path.Combine(projectDir, foregroundAttr.Replace('\\', '/'))
: null;
if (!File.Exists(bgPath))
return null;
// If we have both background and foreground, composite them
if (fgPath != null && File.Exists(fgPath))
{
var compositedIcon = CompositeIcon(bgPath, fgPath, bgColorAttr, colorAttr);
if (compositedIcon != null)
{
Console.WriteLine($" Composited icon from MauiIcon (bg + fg)");
return compositedIcon;
}
}
// Otherwise, just use the background with the BackgroundColor
if (!string.IsNullOrEmpty(bgColorAttr))
{
Console.WriteLine($" Using MauiIcon background: {Path.GetFileName(bgPath)}");
}
return bgPath;
}
catch (Exception ex)
{
Console.WriteLine($" Warning: Could not parse csproj: {ex.Message}");
return null;
}
}
private string? FindProjectDirectory(string inputDir, string execName)
{
// The publish directory is typically: ProjectDir/bin/Release/net9.0/linux-arm64/publish
// We need to go up to find the project directory
var dir = new DirectoryInfo(inputDir);
// Go up looking for a .csproj file
while (dir != null)
{
var csprojFiles = dir.GetFiles("*.csproj");
if (csprojFiles.Length > 0)
return dir.FullName;
// Also check if we find a matching project name
if (dir.GetFiles($"{execName}.csproj").Length > 0)
return dir.FullName;
dir = dir.Parent;
// Don't go too far up (max 10 levels)
if (dir?.FullName.Split(Path.DirectorySeparatorChar).Length < 3)
break;
}
return null;
}
private string? CompositeIcon(string bgPath, string fgPath, string? bgColor, string fgColor)
{
try
{
// Read the foreground SVG to extract the path
var fgContent = File.ReadAllText(fgPath);
var fgDoc = XDocument.Parse(fgContent);
// Find path elements in the foreground
var svgNs = XNamespace.Get("http://www.w3.org/2000/svg");
var pathElements = fgDoc.Descendants(svgNs + "path")
.Concat(fgDoc.Descendants("path"))
.ToList();
if (pathElements.Count == 0)
return null;
// Extract path data
var pathData = string.Join(" ", pathElements
.Select(p => p.Attribute("d")?.Value)
.Where(d => !string.IsNullOrEmpty(d)));
if (string.IsNullOrEmpty(pathData))
return null;
// Get the viewBox or size from foreground
var fgRoot = fgDoc.Root;
var fgViewBox = fgRoot?.Attribute("viewBox")?.Value ?? "0 0 24 24";
// Parse viewBox to get dimensions
var vbParts = fgViewBox.Split(' ');
var fgWidth = vbParts.Length >= 3 ? double.Parse(vbParts[2]) : 24;
var fgHeight = vbParts.Length >= 4 ? double.Parse(vbParts[3]) : 24;
// Create composited SVG (256x256 with rounded corners)
var finalBgColor = bgColor ?? "#512BD4"; // Default MAUI purple
var scale = 160.0 / Math.Max(fgWidth, fgHeight); // Scale to fit in 160px centered in 256px
var offsetX = (256 - fgWidth * scale) / 2;
var offsetY = (256 - fgHeight * scale) / 2;
var compositedSvg = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<svg width=""256"" height=""256"" viewBox=""0 0 256 256"" xmlns=""http://www.w3.org/2000/svg"">
<rect width=""256"" height=""256"" rx=""48"" fill=""{finalBgColor}""/>
<g transform=""translate({offsetX:F1}, {offsetY:F1}) scale({scale:F3})"">
<path fill=""{fgColor}"" d=""{pathData}""/>
</g>
</svg>";
// Write to temp file
var tempIcon = Path.Combine(Path.GetTempPath(), $"appicon_{Guid.NewGuid():N}.svg");
File.WriteAllText(tempIcon, compositedSvg);
return tempIcon;
}
catch (Exception ex)
{
Console.WriteLine($" Warning: Could not composite icon: {ex.Message}");
return null;
}
}
private async Task<string?> FindAppImageTool()
{
var candidates = new[]