diff --git a/src/libappimage/desktop_integration/integrator/DesktopEntryEditor.cpp b/src/libappimage/desktop_integration/integrator/DesktopEntryEditor.cpp index ebe96503..81be8ef9 100644 --- a/src/libappimage/desktop_integration/integrator/DesktopEntryEditor.cpp +++ b/src/libappimage/desktop_integration/integrator/DesktopEntryEditor.cpp @@ -102,6 +102,12 @@ namespace appimage { for (const auto& path: iconEntriesPaths) { std::string iconName = desktopEntry.get(path); + // Extract base name from icon name (e.g. "co.anysphere.cursor" -> "cursor") + size_t lastDot = iconName.find_last_of('.'); + if (lastDot != std::string::npos) { + iconName = iconName.substr(lastDot + 1); + } + // create new icon name as "__" std::stringstream newIcon; diff --git a/src/libappimage/desktop_integration/integrator/Integrator.cpp b/src/libappimage/desktop_integration/integrator/Integrator.cpp index e1555192..0c3bccb5 100644 --- a/src/libappimage/desktop_integration/integrator/Integrator.cpp +++ b/src/libappimage/desktop_integration/integrator/Integrator.cpp @@ -16,6 +16,7 @@ // local #include +#include #include #include #include @@ -187,27 +188,114 @@ namespace appimage { throw DesktopIntegrationError("Icon field contains path"); } - auto iconPaths = resourcesExtractor.getIconFilePaths(desktopEntryIconName); + // First try to find icons in the hicolor directory + std::vector hicolorIconPaths; + + // Try both the desktop entry icon name and the base name without prefix + std::string baseName = desktopEntryIconName; + size_t lastDot = baseName.find_last_of('.'); + if (lastDot != std::string::npos) { + baseName = baseName.substr(lastDot + 1); + } + + // Get all potential icon paths + auto iconPaths = resourcesExtractor.getIconFilePaths(baseName + ".png"); + + // Filter to only include hicolor icons from apps directory + for (const auto& path : iconPaths) { + if (path.find("usr/share/icons/hicolor") != std::string::npos && + path.find("/apps/") != std::string::npos) { + hicolorIconPaths.push_back(path); + } + } - // If the main app icon is not usr/share/icons we should deploy the .DirIcon in its place - if (iconPaths.empty()) { - Logger::warning(std::string("No icons found at \"") + iconsDirPath + "\""); + // If no hicolor icons found, try using .DirIcon + if (hicolorIconPaths.empty()) { + Logger::warning(std::string("No hicolor icons found at \"") + iconsDirPath + "/hicolor\""); try { - Logger::warning("Using .DirIcon as default app icon"); - auto dirIconData = resourcesExtractor.extract(dirIconPath); - deployApplicationIcon(desktopEntryIconName, dirIconData);; + // Since .DirIcon might be a symlink, try to extract it directly + try { + // Try to extract the icon data - this will automatically follow symlinks + auto iconData = resourcesExtractor.extract(dirIconPath); + Logger::warning("Using " + dirIconPath + " as default app icon"); + deployApplicationIcon(desktopEntryIconName, iconData); + } catch (const PayloadIteratorError& error) { + Logger::error(error.what()); + Logger::error("No icon was generated for: " + appImage.getPath()); + } } catch (const PayloadIteratorError& error) { Logger::error(error.what()); Logger::error("No icon was generated for: " + appImage.getPath()); } - } else { - // Generate the target paths were the Desktop Entry icons will be deployed - std::map iconFilesTargetPaths; - for (const auto& itr: iconPaths) - iconFilesTargetPaths[itr] = generateDeployPath(itr).string(); + return; + } + + // Find the largest size icon + std::string largestIconPath; + int largestSize = 0; + for (const auto& path : hicolorIconPaths) { + // Extract size from path like "usr/share/icons/hicolor/512x512/apps/" + std::string sizeStr; + size_t sizePos = path.find("/hicolor/"); + if (sizePos != std::string::npos) { + sizePos = path.find('/', sizePos + 9); + if (sizePos != std::string::npos) { + size_t endPos = path.find('/', sizePos + 1); + if (endPos != std::string::npos) { + sizeStr = path.substr(sizePos + 1, endPos - sizePos - 1); + // Handle NxN format + size_t xPos = sizeStr.find('x'); + if (xPos != std::string::npos) { + try { + int size = std::stoi(sizeStr.substr(0, xPos)); + if (size > largestSize) { + largestSize = size; + largestIconPath = path; + } + } catch (...) { + // Skip invalid size format + continue; + } + } + } + } + } + } - resourcesExtractor.extractTo(iconFilesTargetPaths); + // Deploy all hicolor icons to their corresponding directories + std::map iconFilesTargetPaths; + for (const auto& itr: hicolorIconPaths) { + iconFilesTargetPaths[itr] = generateDeployPath(itr).string(); + } + resourcesExtractor.extractTo(iconFilesTargetPaths); + + // Create symlink for the largest icon + if (!largestIconPath.empty()) { + std::filesystem::path targetPath = generateDeployPath(largestIconPath); + + // Extract base name from icon name (e.g. "co.anysphere.cursor" -> "cursor") + std::string symlinkIconName = desktopEntryIconName; + size_t lastDot = symlinkIconName.find_last_of('.'); + if (lastDot != std::string::npos) { + symlinkIconName = symlinkIconName.substr(lastDot + 1); + } + + // Create the symlink path using the desktop entry icon name + std::filesystem::path symlinkPath = xdgDataHome / "icons/hicolor" / + (std::to_string(largestSize) + "x" + std::to_string(largestSize)) / + "apps" / (VENDOR_PREFIX + "_" + appImageId + "_" + symlinkIconName + ".png"); + + // Create parent directories if they don't exist + std::filesystem::create_directories(symlinkPath.parent_path()); + + // Remove existing symlink if it exists + if (std::filesystem::exists(symlinkPath)) { + std::filesystem::remove(symlinkPath); + } + + // Create the symlink + std::filesystem::create_symlink(targetPath, symlinkPath); } } @@ -231,10 +319,17 @@ namespace appimage { std::stringstream iconNameBuilder; + // Extract base name from icon name (e.g. "co.anysphere.cursor" -> "cursor") + std::string baseIconName = iconName; + size_t lastDot = baseIconName.find_last_of('.'); + if (lastDot != std::string::npos) { + baseIconName = baseIconName.substr(lastDot + 1); + } + // we don't trust the icon name inside the desktop file, so we sanitize the filename before // calculating the integrated icon's path // this keeps the filename understandable while mitigating risks for potential attacks - iconNameBuilder << StringSanitizer(iconName).sanitizeForPath(); + iconNameBuilder << StringSanitizer(baseIconName).sanitizeForPath(); // in case of vectorial images use ".svg" as extension and "scalable" as size if (icon.format() == "svg") {