Better auto-detection

This commit is contained in:
2026-01-24 06:56:15 +00:00
parent 90007fb2dc
commit 530f36cda8
2 changed files with 184 additions and 46 deletions

View File

@@ -143,7 +143,24 @@ public class AppImageBuilder
} }
// Find the main executable // Find the main executable
var execName = options.ExecutableName ?? options.AppName; var execName = options.ExecutableName;
// Auto-detect executable if not specified
if (string.IsNullOrEmpty(execName))
{
execName = AutoDetectExecutable(options.InputDirectory.FullName, options.AppName);
if (execName != null)
{
Console.WriteLine($" Auto-detected executable: {execName}");
}
}
// Fallback to app name variations
if (string.IsNullOrEmpty(execName))
{
execName = options.AppName;
}
var mainExec = Path.Combine(options.InputDirectory.FullName, execName); var mainExec = Path.Combine(options.InputDirectory.FullName, execName);
if (!File.Exists(mainExec)) if (!File.Exists(mainExec))
{ {
@@ -156,8 +173,14 @@ public class AppImageBuilder
Path.Combine(options.InputDirectory.FullName, execName.Replace(" ", "") + ".dll") Path.Combine(options.InputDirectory.FullName, execName.Replace(" ", "") + ".dll")
}; };
mainExec = candidates.FirstOrDefault(File.Exists) ?? ""; var found = candidates.FirstOrDefault(File.Exists);
if (string.IsNullOrEmpty(mainExec)) if (found != null)
{
// Update execName to the actual name (without spaces)
execName = Path.GetFileNameWithoutExtension(found);
mainExec = found;
}
else
{ {
// List available executables // List available executables
var dlls = Directory.GetFiles(options.InputDirectory.FullName, "*.dll") var dlls = Directory.GetFiles(options.InputDirectory.FullName, "*.dll")
@@ -201,7 +224,7 @@ public class AppImageBuilder
// Create AppRun script // Create AppRun script
Console.WriteLine(" Creating AppRun script..."); Console.WriteLine(" Creating AppRun script...");
var appRunPath = Path.Combine(appDir, "AppRun"); var appRunPath = Path.Combine(appDir, "AppRun");
await CreateAppRunScript(appRunPath, options); await CreateAppRunScript(appRunPath, options, execName);
// Create .desktop file // Create .desktop file
Console.WriteLine(" Creating .desktop file..."); Console.WriteLine(" Creating .desktop file...");
@@ -240,9 +263,8 @@ public class AppImageBuilder
} }
} }
private async Task CreateAppRunScript(string path, PackageOptions options) private async Task CreateAppRunScript(string path, PackageOptions options, string execName)
{ {
var execName = options.ExecutableName ?? options.AppName;
var script = $@"#!/bin/bash var script = $@"#!/bin/bash
# AppRun script for OpenMaui applications # AppRun script for OpenMaui applications
@@ -263,7 +285,8 @@ export XDG_DATA_DIRS=""$HERE/usr/share:${{XDG_DATA_DIRS:-/usr/local/share:/usr/s
INSTALLED_MARKER=""$HOME/.local/share/openmaui-installed"" INSTALLED_MARKER=""$HOME/.local/share/openmaui-installed""
BIN_DIR=""$HOME/.local/bin"" BIN_DIR=""$HOME/.local/bin""
APPS_DIR=""$HOME/.local/share/applications"" APPS_DIR=""$HOME/.local/share/applications""
ICONS_DIR=""$HOME/.local/share/icons/hicolor/256x256/apps"" ICONS_DIR_SCALABLE=""$HOME/.local/share/icons/hicolor/scalable/apps""
ICONS_DIR_256=""$HOME/.local/share/icons/hicolor/256x256/apps""
# Handle command line flags # Handle command line flags
if [ ""$1"" = ""--install"" ]; then if [ ""$1"" = ""--install"" ]; then
@@ -275,8 +298,12 @@ elif [ ""$1"" = ""--uninstall"" ]; then
SANITIZED_NAME=$(echo ""$APPIMAGE_NAME"" | tr ' ' '_') SANITIZED_NAME=$(echo ""$APPIMAGE_NAME"" | tr ' ' '_')
rm -f ""$BIN_DIR/$APPIMAGE_BASENAME"" rm -f ""$BIN_DIR/$APPIMAGE_BASENAME""
rm -f ""$APPS_DIR/${{SANITIZED_NAME}}.desktop"" rm -f ""$APPS_DIR/${{SANITIZED_NAME}}.desktop""
rm -f ""$ICONS_DIR_SCALABLE/${{SANITIZED_NAME}}.svg""
rm -f ""$ICONS_DIR_256/${{SANITIZED_NAME}}.png""
rm -f ""$ICONS_DIR_256/${{SANITIZED_NAME}}.ico""
rm -f ""$INSTALLED_MARKER/$APPIMAGE_BASENAME"" rm -f ""$INSTALLED_MARKER/$APPIMAGE_BASENAME""
command -v update-desktop-database &> /dev/null && update-desktop-database ""$APPS_DIR"" 2>/dev/null command -v update-desktop-database &> /dev/null && update-desktop-database ""$APPS_DIR"" 2>/dev/null
command -v gtk-update-icon-cache &> /dev/null && gtk-update-icon-cache -f -t ""$HOME/.local/share/icons/hicolor"" 2>/dev/null
if command -v zenity &> /dev/null; then if command -v zenity &> /dev/null; then
zenity --info --title=""Uninstall Complete"" --text=""$APPIMAGE_NAME has been removed."" --width=300 2>/dev/null zenity --info --title=""Uninstall Complete"" --text=""$APPIMAGE_NAME has been removed."" --width=300 2>/dev/null
else else
@@ -298,19 +325,20 @@ do_install() {{
APPIMAGE_BASENAME=$(basename ""$APPIMAGE"") APPIMAGE_BASENAME=$(basename ""$APPIMAGE"")
SANITIZED=$(echo ""$APPIMAGE_NAME"" | tr ' ' '_') SANITIZED=$(echo ""$APPIMAGE_NAME"" | tr ' ' '_')
mkdir -p ""$BIN_DIR"" ""$APPS_DIR"" ""$ICONS_DIR"" ""$INSTALLED_MARKER"" mkdir -p ""$BIN_DIR"" ""$APPS_DIR"" ""$ICONS_DIR_SCALABLE"" ""$ICONS_DIR_256"" ""$INSTALLED_MARKER""
# Copy AppImage # Copy AppImage
cp ""$APPIMAGE"" ""$BIN_DIR/$APPIMAGE_BASENAME"" cp ""$APPIMAGE"" ""$BIN_DIR/$APPIMAGE_BASENAME""
chmod +x ""$BIN_DIR/$APPIMAGE_BASENAME"" chmod +x ""$BIN_DIR/$APPIMAGE_BASENAME""
# Copy icon if available # Copy icon if available (SVG to scalable, PNG/ICO to 256x256)
for ext in svg png ico; do if [ -f ""$HERE/${{SANITIZED}}.svg"" ]; then
if [ -f ""$HERE/${{SANITIZED}}.${{ext}}"" ]; then cp ""$HERE/${{SANITIZED}}.svg"" ""$ICONS_DIR_SCALABLE/${{SANITIZED}}.svg""
cp ""$HERE/${{SANITIZED}}.${{ext}}"" ""$ICONS_DIR/${{SANITIZED}}.${{ext}}"" elif [ -f ""$HERE/${{SANITIZED}}.png"" ]; then
break cp ""$HERE/${{SANITIZED}}.png"" ""$ICONS_DIR_256/${{SANITIZED}}.png""
fi elif [ -f ""$HERE/${{SANITIZED}}.ico"" ]; then
done cp ""$HERE/${{SANITIZED}}.ico"" ""$ICONS_DIR_256/${{SANITIZED}}.ico""
fi
# Create .desktop file # Create .desktop file
cat > ""$APPS_DIR/${{SANITIZED}}.desktop"" << DESKTOP cat > ""$APPS_DIR/${{SANITIZED}}.desktop"" << DESKTOP
@@ -328,8 +356,9 @@ DESKTOP
# Mark as installed # Mark as installed
echo ""$(date -Iseconds)"" > ""$INSTALLED_MARKER/$APPIMAGE_BASENAME"" echo ""$(date -Iseconds)"" > ""$INSTALLED_MARKER/$APPIMAGE_BASENAME""
# Update desktop database # Update desktop database and icon cache
command -v update-desktop-database &> /dev/null && update-desktop-database ""$APPS_DIR"" 2>/dev/null command -v update-desktop-database &> /dev/null && update-desktop-database ""$APPS_DIR"" 2>/dev/null
command -v gtk-update-icon-cache &> /dev/null && gtk-update-icon-cache -f -t ""$HOME/.local/share/icons/hicolor"" 2>/dev/null
return 0 return 0
}} }}
@@ -349,33 +378,47 @@ if [ -n ""$APPIMAGE"" ]; then
ICON_OPT="""" ICON_OPT=""""
[ -n ""$ICON_PATH"" ] && ICON_OPT=""--window-icon=$ICON_PATH"" [ -n ""$ICON_PATH"" ] && ICON_OPT=""--window-icon=$ICON_PATH""
CHOICE=$(zenity --question --title=""$APPIMAGE_NAME"" \ # Run zenity and capture exit code properly
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?"" \ --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"" \ --ok-label=""Install"" --cancel-label=""Run Only"" \
--extra-button=""Cancel"" \ --width=350 $ICON_OPT 2>/dev/null
--width=350 $ICON_OPT 2>/dev/null; echo $?) ZENITY_EXIT=$?
case ""$CHOICE"" in if [ $ZENITY_EXIT -eq 0 ]; then
0) # Install clicked # Install clicked
do_install do_install
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
zenity --info --title=""Installation Complete"" \ zenity --info --title=""Installation Complete"" \
--text=""$APPIMAGE_NAME has been installed.\n\nYou can find it in your application menu."" \ --text=""$APPIMAGE_NAME has been installed.\n\nYou can find it in your application menu."" \
--width=300 $ICON_OPT 2>/dev/null --width=300 $ICON_OPT 2>/dev/null
fi fi
;; elif [ $ZENITY_EXIT -eq 1 ]; then
1) # Run Without Installing # Run Only clicked - continue to run the app
;; :
*) # Cancel or closed else
exit 0 # Dialog closed or error
;; exit 0
esac fi
fi fi
fi fi
fi fi
cd ""$HERE/usr/bin"" cd ""$HERE/usr/bin""
exec dotnet ""$EXEC_NAME.dll"" ""$@""
# Check if this is a self-contained app (native executable exists)
if [ -x ""$HERE/usr/bin/$EXEC_NAME"" ]; then
exec ""$HERE/usr/bin/$EXEC_NAME"" ""$@""
else
# Framework-dependent - find and use dotnet
DOTNET_CMD=""dotnet""
if ! command -v dotnet &> /dev/null; then
for d in /usr/share/dotnet /usr/lib/dotnet /opt/dotnet ""$HOME/.dotnet""; do
[ -x ""$d/dotnet"" ] && DOTNET_CMD=""$d/dotnet"" && break
done
fi
exec ""$DOTNET_CMD"" ""$EXEC_NAME.dll"" ""$@""
fi
"; ";
await File.WriteAllTextAsync(path, script); await File.WriteAllTextAsync(path, script);
await RunCommandAsync("chmod", $"+x \"{path}\""); await RunCommandAsync("chmod", $"+x \"{path}\"");
@@ -407,7 +450,7 @@ X-AppImage-Version={options.Version}
var destIcon = Path.Combine(appDir, $"{iconName}{ext}"); var destIcon = Path.Combine(appDir, $"{iconName}{ext}");
File.Copy(options.IconPath.FullName, destIcon); File.Copy(options.IconPath.FullName, destIcon);
// Also copy to standard locations // Also copy to standard locations for desktop integration
var iconDirs = new[] var iconDirs = new[]
{ {
Path.Combine(appDir, "usr", "share", "icons", "hicolor", "256x256", "apps"), Path.Combine(appDir, "usr", "share", "icons", "hicolor", "256x256", "apps"),
@@ -421,6 +464,12 @@ X-AppImage-Version={options.Version}
if (!File.Exists(iconPath)) if (!File.Exists(iconPath))
File.Copy(options.IconPath.FullName, iconPath); File.Copy(options.IconPath.FullName, iconPath);
} }
// Also copy to usr/bin as appicon.svg/png for runtime window icon
// The app looks for appicon.svg or appicon.png in AppContext.BaseDirectory
var runtimeIconPath = Path.Combine(appDir, "usr", "bin", $"appicon{ext}");
if (!File.Exists(runtimeIconPath))
File.Copy(options.IconPath.FullName, runtimeIconPath);
} }
else else
{ {
@@ -439,6 +488,10 @@ X-AppImage-Version={options.Version}
var svgDir = Path.Combine(appDir, "usr", "share", "icons", "hicolor", "scalable", "apps"); var svgDir = Path.Combine(appDir, "usr", "share", "icons", "hicolor", "scalable", "apps");
Directory.CreateDirectory(svgDir); Directory.CreateDirectory(svgDir);
await File.WriteAllTextAsync(Path.Combine(svgDir, $"{iconName}.svg"), defaultIcon); await File.WriteAllTextAsync(Path.Combine(svgDir, $"{iconName}.svg"), defaultIcon);
// Also create in usr/bin as appicon.svg for runtime window icon
var runtimeIconPath = Path.Combine(appDir, "usr", "bin", "appicon.svg");
await File.WriteAllTextAsync(runtimeIconPath, defaultIcon);
} }
} }
@@ -484,6 +537,71 @@ X-AppImage-Version={options.Version}
return false; return false;
} }
private string? AutoDetectExecutable(string inputDir, string appName)
{
// Strategy 1: Look for a native executable (ELF file without extension)
// These are created when publishing with --self-contained
var files = Directory.GetFiles(inputDir);
foreach (var file in files)
{
var fileName = Path.GetFileName(file);
// Skip files with extensions (DLLs, configs, etc.)
if (fileName.Contains('.')) continue;
// Skip common non-app files
if (fileName == "createdump") continue;
// Check if it's an executable (has execute permission or is ELF)
try
{
var firstBytes = new byte[4];
using (var fs = File.OpenRead(file))
{
fs.Read(firstBytes, 0, 4);
}
// ELF magic number: 0x7F 'E' 'L' 'F'
if (firstBytes[0] == 0x7F && firstBytes[1] == 'E' && firstBytes[2] == 'L' && firstBytes[3] == 'F')
{
return fileName;
}
}
catch { }
}
// Strategy 2: Look for a .dll that matches the app name pattern
var sanitizedName = appName.Replace(" ", "");
var dllCandidates = new[]
{
$"{sanitizedName}.dll",
$"{appName}.dll",
};
foreach (var dll in dllCandidates)
{
if (File.Exists(Path.Combine(inputDir, dll)))
{
return Path.GetFileNameWithoutExtension(dll);
}
}
// Strategy 3: Look for any .dll with a matching .runtimeconfig.json (indicates main app)
var runtimeConfigs = Directory.GetFiles(inputDir, "*.runtimeconfig.json");
foreach (var config in runtimeConfigs)
{
var baseName = Path.GetFileNameWithoutExtension(config).Replace(".runtimeconfig", "");
var dllPath = Path.Combine(inputDir, baseName + ".dll");
if (File.Exists(dllPath))
{
// Skip Microsoft/System DLLs
if (!baseName.StartsWith("Microsoft.") && !baseName.StartsWith("System."))
{
return baseName;
}
}
}
return null;
}
private string? FindIcon(string inputDir, string execName) private string? FindIcon(string inputDir, string execName)
{ {
// First, try to find and parse the .csproj to get MauiIcon // First, try to find and parse the .csproj to get MauiIcon

View File

@@ -58,7 +58,7 @@ if [ -n "$APPIMAGE" ]; then
# Check if already installed # Check if already installed
if [ ! -f "$INSTALLED_MARKER/$APPIMAGE_BASENAME" ] || [ "$SHOW_INSTALLER" = "1" ]; then if [ ! -f "$INSTALLED_MARKER/$APPIMAGE_BASENAME" ] || [ "$SHOW_INSTALLER" = "1" ]; then
# First run - show installer dialog # First run - show installer dialog
if [ -f "$HERE/usr/bin/OpenMaui.AppImage.Installer.dll" ]; then if [ -f "$HERE/usr/bin/OpenMaui.AppImage.Installer.dll" ] || [ -x "$HERE/usr/bin/OpenMaui.AppImage.Installer" ]; then
# Find icon # Find icon
ICON_PATH="" ICON_PATH=""
for ext in svg png ico; do for ext in svg png ico; do
@@ -74,13 +74,26 @@ if [ -n "$APPIMAGE" ]; then
done done
cd "$HERE/usr/bin" cd "$HERE/usr/bin"
RESULT=$(dotnet OpenMaui.AppImage.Installer.dll \ # Check if self-contained installer exists
--name "$APPIMAGE_NAME" \ if [ -x "$HERE/usr/bin/OpenMaui.AppImage.Installer" ]; then
--appimage "$APPIMAGE" \ "$HERE/usr/bin/OpenMaui.AppImage.Installer" \
--comment "$APPIMAGE_COMMENT" \ --name "$APPIMAGE_NAME" \
--category "$APPIMAGE_CATEGORY" \ --appimage "$APPIMAGE" \
--version "$APPIMAGE_VERSION" \ --comment "$APPIMAGE_COMMENT" \
${ICON_PATH:+--icon "$ICON_PATH"}; echo $?) --category "$APPIMAGE_CATEGORY" \
--version "$APPIMAGE_VERSION" \
${ICON_PATH:+--icon "$ICON_PATH"}
RESULT=$?
else
dotnet OpenMaui.AppImage.Installer.dll \
--name "$APPIMAGE_NAME" \
--appimage "$APPIMAGE" \
--comment "$APPIMAGE_COMMENT" \
--category "$APPIMAGE_CATEGORY" \
--version "$APPIMAGE_VERSION" \
${ICON_PATH:+--icon "$ICON_PATH"}
RESULT=$?
fi
# Check result: 0=run, 1=cancel, 2=installed # Check result: 0=run, 1=cancel, 2=installed
if [ "$RESULT" = "1" ]; then if [ "$RESULT" = "1" ]; then
@@ -92,4 +105,11 @@ fi
# Run the application # Run the application
cd "$HERE/usr/bin" cd "$HERE/usr/bin"
exec dotnet "$EXEC_NAME.dll" "$@"
# Check if this is a self-contained app (native executable exists)
if [ -x "$HERE/usr/bin/$EXEC_NAME" ]; then
exec "$HERE/usr/bin/$EXEC_NAME" "$@"
else
# Framework-dependent - use dotnet
exec dotnet "$EXEC_NAME.dll" "$@"
fi