From 08fd27ffb0f7c7a3dae9e105215b61a31f225dc7 Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Wed, 22 Oct 2025 22:20:53 -0400 Subject: [PATCH 1/6] Support .icon files --- librtt/Rtt_HTTPClientCommon.cpp | 1 + .../CoronaBuilder.xcodeproj/project.pbxproj | 6 + platform/mac/Rtt_IOSAppPackager.mm | 2 + platform/mac/Rtt_OSXAppPackager.mm | 3 + platform/mac/Rtt_TVOSAppPackager.mm | 3 + .../mac/ratatouille.xcodeproj/project.pbxproj | 8 + .../resources/CoronaIconComposerSupport.lua | 719 ++++++++++++++++++ platform/resources/iPhonePackageApp.lua | 84 +- tools/CoronaBuilder/Rtt_CoronaBuilder.cpp | 2 + 9 files changed, 825 insertions(+), 3 deletions(-) create mode 100644 platform/resources/CoronaIconComposerSupport.lua diff --git a/librtt/Rtt_HTTPClientCommon.cpp b/librtt/Rtt_HTTPClientCommon.cpp index 2ee9f93c0..b0ee45738 100644 --- a/librtt/Rtt_HTTPClientCommon.cpp +++ b/librtt/Rtt_HTTPClientCommon.cpp @@ -23,6 +23,7 @@ namespace Rtt { int luaload_json(lua_State *L); int luaload_dkjson(lua_State *L); int luaload_BuilderPluginDownloader(lua_State *L); +int luaload_CoronaIconComposerSupport(lua_State *L); int luaload_CoronaPListSupport(lua_State *L); int luaload_CoronaBuilderPluginCollector(lua_State *L); int luaload_dkjson(lua_State* L); diff --git a/platform/mac/CoronaBuilder.xcodeproj/project.pbxproj b/platform/mac/CoronaBuilder.xcodeproj/project.pbxproj index 18f40467f..72ec6ee5c 100644 --- a/platform/mac/CoronaBuilder.xcodeproj/project.pbxproj +++ b/platform/mac/CoronaBuilder.xcodeproj/project.pbxproj @@ -101,6 +101,8 @@ C29CB61219525CD800D1F011 /* re.lua in Sources */ = {isa = PBXBuildFile; fileRef = C29CB60219525BFA00D1F011 /* re.lua */; }; C2A2E8031EC2637300900729 /* Rtt_LuaLibBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2A2E8011EC2637300900729 /* Rtt_LuaLibBuilder.cpp */; }; C2AD19CE1C40391300217E3E /* CoronaLog.c in Sources */ = {isa = PBXBuildFile; fileRef = C2AD19CD1C40391300217E3E /* CoronaLog.c */; }; + DBBD8F902EA9A8AA00E890BB /* CoronaIconComposerSupport.lua in Resources */ = {isa = PBXBuildFile; fileRef = DBBD8F8F2EA9A8AA00E890BB /* CoronaIconComposerSupport.lua */; }; + DBBD8FA92EA9B35700E890BB /* CoronaIconComposerSupport.lua in Sources */ = {isa = PBXBuildFile; fileRef = DBBD8F8F2EA9A8AA00E890BB /* CoronaIconComposerSupport.lua */; }; F53C329F2404E30900BC2BED /* CoronaBuilderPluginCollector.lua in Sources */ = {isa = PBXBuildFile; fileRef = F53C328E2404E2C000BC2BED /* CoronaBuilderPluginCollector.lua */; }; F547C7F61ED28A2700C4ED92 /* Rtt_DownloadPluginsMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F547C7F41ED28A2700C4ED92 /* Rtt_DownloadPluginsMain.cpp */; }; F547C7F71ED28BA600C4ED92 /* BuilderPluginDownloader.lua in Sources */ = {isa = PBXBuildFile; fileRef = F547C7ED1ED289EA00C4ED92 /* BuilderPluginDownloader.lua */; }; @@ -527,6 +529,7 @@ C2AD19CD1C40391300217E3E /* CoronaLog.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = CoronaLog.c; path = ../../librtt/Corona/CoronaLog.c; sourceTree = ""; }; C2F1BAF71EF48EF500D77BD7 /* CoronaShell.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoronaShell.xcodeproj; path = CoronaShell/CoronaShell.xcodeproj; sourceTree = ""; }; C2F1BAFD1EF48F2F00D77BD7 /* ratatouille.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = ratatouille.xcodeproj; sourceTree = ""; }; + DBBD8F8F2EA9A8AA00E890BB /* CoronaIconComposerSupport.lua */ = {isa = PBXFileReference; lastKnownFileType = text; name = CoronaIconComposerSupport.lua; path = ../resources/CoronaIconComposerSupport.lua; sourceTree = SOURCE_ROOT; }; F53C328E2404E2C000BC2BED /* CoronaBuilderPluginCollector.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CoronaBuilderPluginCollector.lua; path = ../resources/CoronaBuilderPluginCollector.lua; sourceTree = ""; }; F547C7ED1ED289EA00C4ED92 /* BuilderPluginDownloader.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = BuilderPluginDownloader.lua; path = ../../tools/CoronaBuilder/BuilderPluginDownloader.lua; sourceTree = ""; }; F547C7F41ED28A2700C4ED92 /* Rtt_DownloadPluginsMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Rtt_DownloadPluginsMain.cpp; path = ../../tools/CoronaBuilder/Rtt_DownloadPluginsMain.cpp; sourceTree = ""; }; @@ -929,6 +932,7 @@ C2947E151EB29AC5007AFD48 /* Lua */ = { isa = PBXGroup; children = ( + DBBD8F8F2EA9A8AA00E890BB /* CoronaIconComposerSupport.lua */, F5DB5B202113701300EC5CBC /* linuxPackageApp.lua */, C205D56E1C7D2204000A8EE1 /* CoronaBuilder.lua */, F547C7ED1ED289EA00C4ED92 /* BuilderPluginDownloader.lua */, @@ -1231,6 +1235,7 @@ buildActionMask = 2147483647; files = ( C2471D7819882B260076CA1F /* CoronaSimulator.icns in Resources */, + DBBD8F902EA9A8AA00E890BB /* CoronaIconComposerSupport.lua in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1264,6 +1269,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBBD8FA92EA9B35700E890BB /* CoronaIconComposerSupport.lua in Sources */, F53C329F2404E30900BC2BED /* CoronaBuilderPluginCollector.lua in Sources */, F5BEA4B823FFE98200206A6A /* CoronaOfflineiOSPackager.lua in Sources */, F5DB5B312113702800EC5CBC /* linuxPackageApp.lua in Sources */, diff --git a/platform/mac/Rtt_IOSAppPackager.mm b/platform/mac/Rtt_IOSAppPackager.mm index 82a216dca..871301c16 100644 --- a/platform/mac/Rtt_IOSAppPackager.mm +++ b/platform/mac/Rtt_IOSAppPackager.mm @@ -58,6 +58,7 @@ // following function which loads the bytecodes via luaL_loadbuffer. int luaload_iPhonePackageApp(lua_State* L); int luaload_CoronaPListSupport(lua_State* L); + int luaload_CoronaIconComposerSupport(lua_State* L); int luaload_CoronaOfflineiOSPackager(lua_State* L); // ---------------------------------------------------------------------------- @@ -98,6 +99,7 @@ #endif Lua::RegisterModuleLoader( L, "CoronaPListSupport", Lua::Open< luaload_CoronaPListSupport > ); + Lua::RegisterModuleLoader( L, "CoronaIconComposerSupport", Lua::Open< luaload_CoronaIconComposerSupport > ); HTTPClient::registerFetcherModuleLoaders(L); Lua::DoBuffer( fVM, & luaload_iPhonePackageApp, NULL); diff --git a/platform/mac/Rtt_OSXAppPackager.mm b/platform/mac/Rtt_OSXAppPackager.mm index d23ab7aa5..7cfc2a8b6 100644 --- a/platform/mac/Rtt_OSXAppPackager.mm +++ b/platform/mac/Rtt_OSXAppPackager.mm @@ -49,6 +49,8 @@ // following function which loads the bytecodes via luaL_loadbuffer. int luaload_OSXPackageApp(lua_State* L); int luaload_CoronaPListSupport(lua_State* L); + int luaload_CoronaIconComposerSupport(lua_State* L); + // ---------------------------------------------------------------------------- @@ -88,6 +90,7 @@ #endif Lua::RegisterModuleLoader( L, "CoronaPListSupport", Lua::Open< luaload_CoronaPListSupport > ); + Lua::RegisterModuleLoader( L, "CoronaIconComposerSupport", Lua::Open< luaload_CoronaIconComposerSupport> ); Lua::RegisterModuleLoader( L, "dkjson", Lua::Open< luaload_dkjson > ); Lua::RegisterModuleLoader( L, "json", Lua::Open< luaload_json > ); Lua::RegisterModuleLoader( L, "lpeg", luaopen_lpeg ); diff --git a/platform/mac/Rtt_TVOSAppPackager.mm b/platform/mac/Rtt_TVOSAppPackager.mm index ef467ec2d..b58cee3e0 100644 --- a/platform/mac/Rtt_TVOSAppPackager.mm +++ b/platform/mac/Rtt_TVOSAppPackager.mm @@ -59,6 +59,7 @@ // following function which loads the bytecodes via luaL_loadbuffer. int luaload_tvosPackageApp(lua_State* L); int luaload_CoronaPListSupport(lua_State* L); + int luaload_CoronaIconComposerSupport(lua_State* L); int luaload_CoronaOfflineiOSPackager(lua_State* L); // ---------------------------------------------------------------------------- @@ -99,6 +100,8 @@ #endif Lua::RegisterModuleLoader( L, "CoronaPListSupport", Lua::Open< luaload_CoronaPListSupport > ); + Lua::RegisterModuleLoader( L, "CoronaIconComposerSupport", Lua::Open< luaload_CoronaIconComposerSupport> ); + HTTPClient::registerFetcherModuleLoaders(L); Lua::DoBuffer( fVM, & luaload_tvosPackageApp, NULL); diff --git a/platform/mac/ratatouille.xcodeproj/project.pbxproj b/platform/mac/ratatouille.xcodeproj/project.pbxproj index 0025bbd10..fb360b246 100644 --- a/platform/mac/ratatouille.xcodeproj/project.pbxproj +++ b/platform/mac/ratatouille.xcodeproj/project.pbxproj @@ -1612,6 +1612,8 @@ DB56B4CA284C2E7D00EC8B14 /* Rtt_AppleKeyServices.h in Headers */ = {isa = PBXBuildFile; fileRef = DB56B4C8284C2E7C00EC8B14 /* Rtt_AppleKeyServices.h */; }; DB56B4CB284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm in Sources */ = {isa = PBXBuildFile; fileRef = DB56B4C9284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm */; }; DB56B4CC284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm in Sources */ = {isa = PBXBuildFile; fileRef = DB56B4C9284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm */; }; + DBBD8FAB2EA9B4AE00E890BB /* CoronaIconComposerSupport.lua in Sources */ = {isa = PBXBuildFile; fileRef = DBBD8FA02EA9A90F00E890BB /* CoronaIconComposerSupport.lua */; }; + DBBD8FAC2EA9B4BD00E890BB /* CoronaIconComposerSupport.lua in Sources */ = {isa = PBXBuildFile; fileRef = DBBD8FA02EA9A90F00E890BB /* CoronaIconComposerSupport.lua */; }; E23CD30D147C725200BC9038 /* BuildSessionState.mm in Sources */ = {isa = PBXBuildFile; fileRef = E23CD30C147C725200BC9038 /* BuildSessionState.mm */; }; F500FB401FED5CE20050B28B /* webtemplate.zip in Resources */ = {isa = PBXBuildFile; fileRef = F500FB3F1FED5CE20050B28B /* webtemplate.zip */; }; F50F51B81B700C04006FEF92 /* Rtt_TextureResourceBitmap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F50F51B61B700C04006FEF92 /* Rtt_TextureResourceBitmap.cpp */; }; @@ -3362,6 +3364,8 @@ C2FCEBA51B701A1800587F1E /* gameoverlayrenderer.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = gameoverlayrenderer.dylib; path = /Applications/Steam.app/Contents/MacOS/gameoverlayrenderer.dylib; sourceTree = ""; }; DB56B4C8284C2E7C00EC8B14 /* Rtt_AppleKeyServices.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Rtt_AppleKeyServices.h; path = ../apple/Rtt_AppleKeyServices.h; sourceTree = ""; }; DB56B4C9284C2E7D00EC8B14 /* Rtt_AppleKeyServices.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = Rtt_AppleKeyServices.mm; path = ../apple/Rtt_AppleKeyServices.mm; sourceTree = ""; }; + DBBD8FA02EA9A90F00E890BB /* CoronaIconComposerSupport.lua */ = {isa = PBXFileReference; lastKnownFileType = text; name = CoronaIconComposerSupport.lua; path = ../resources/CoronaIconComposerSupport.lua; sourceTree = SOURCE_ROOT; }; + DBBD8FAA2EA9B48400E890BB /* CoronaIconComposerSupport.lua */ = {isa = PBXFileReference; lastKnownFileType = text; name = CoronaIconComposerSupport.lua; path = ../resources/CoronaIconComposerSupport.lua; sourceTree = ""; }; E23CD30B147C725200BC9038 /* BuildSessionState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BuildSessionState.h; sourceTree = ""; }; E23CD30C147C725200BC9038 /* BuildSessionState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BuildSessionState.mm; sourceTree = ""; }; F500FB3F1FED5CE20050B28B /* webtemplate.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; name = webtemplate.zip; path = ../resources/webtemplate.zip; sourceTree = ""; }; @@ -4136,6 +4140,7 @@ 00B73BAF12B71BDA0057F594 /* Resources */ = { isa = PBXGroup; children = ( + DBBD8FA02EA9A90F00E890BB /* CoronaIconComposerSupport.lua */, 1F46A96521136B51009CC875 /* linuxPackageApp.lua */, C2AF1E531DA6F0ED00907A65 /* valid_build_settings.lua */, C2AF1E541DA6F0ED00907A65 /* valid_config_lua.lua */, @@ -4495,6 +4500,7 @@ 29B97314FDCFA39411CA2CEA /* ratatouille */ = { isa = PBXGroup; children = ( + DBBD8FAA2EA9B48400E890BB /* CoronaIconComposerSupport.lua */, C21995A81C189E0100E88FD0 /* ios-syslog-helper.xcodeproj */, C26DD10A1BD9508800EC4F44 /* CoronaConsole.xcodeproj */, C2EF85011B336B6300731A45 /* CoronaShell.xcodeproj */, @@ -6916,6 +6922,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBBD8FAB2EA9B4AE00E890BB /* CoronaIconComposerSupport.lua in Sources */, F53C328C2404E2A300BC2BED /* CoronaBuilderPluginCollector.lua in Sources */, F5FFADBC23FFC7DB004E61A9 /* CoronaOfflineiOSPackager.lua in Sources */, C2AF1E571DA6F24500907A65 /* valid_build_settings.lua in Sources */, @@ -7524,6 +7531,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBBD8FAC2EA9B4BD00E890BB /* CoronaIconComposerSupport.lua in Sources */, F53C328D2404E2AB00BC2BED /* CoronaBuilderPluginCollector.lua in Sources */, F5FFADBE23FFC80E004E61A9 /* CoronaOfflineiOSPackager.lua in Sources */, F5DB5B1A21136F9D00EC5CBC /* linuxPackageApp.lua in Sources */, diff --git a/platform/resources/CoronaIconComposerSupport.lua b/platform/resources/CoronaIconComposerSupport.lua new file mode 100644 index 000000000..25d4a7e69 --- /dev/null +++ b/platform/resources/CoronaIconComposerSupport.lua @@ -0,0 +1,719 @@ +------------------------------------------------------------------------------ +-- +-- CoronaIconComposerSupport.lua +-- Handles Icon Composer .icon files for iOS, tvOS, and macOS builds +-- +------------------------------------------------------------------------------ + +local lfs = require('lfs') +local json = require('json') + +local CoronaIconComposerSupport = {} + +-- Double quote a string escaping backslashes and any double quotes +local function quoteString( str ) + str = str:gsub('\\', '\\\\') + str = str:gsub('"', '\\"') + return "\"" .. str .. "\"" +end + +-- Find ictool executable (Icon Composer's command-line tool) +local function findIconTool() + local paths = { + -- Check common Xcode paths (ictool is inside Icon Composer.app) + "/Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool", + "/Applications/Xcode-beta.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool" + } + + -- Check if user has multiple Xcodes (Xcode_15.app, Xcode_16.app, etc.) + for i = 10, 30 do + table.insert(paths, "/Applications/Xcode_" .. i .. ".app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") + end + + for _, path in ipairs(paths) do + local testCmd = "test -x " .. quoteString(path) + if os.execute(testCmd) == 0 then + return path + end + end + + -- Try to find using mdfind (Spotlight search) + local handle = io.popen("mdfind 'kMDItemFSName == ictool && kMDItemContentType == public.unix-executable' 2>/dev/null | head -1") + if handle then + local spotlightPath = handle:read("*l") + handle:close() + if spotlightPath and spotlightPath ~= "" then + local testCmd = "test -x " .. quoteString(spotlightPath) + if os.execute(testCmd) == 0 then + return spotlightPath + end + end + end + + return nil +end + +-- Build ictool command for exporting icons +-- Format: ictool input-document --export-preview platform appearance width height scale output-png-path +local function buildIconToolCommand(ictool, iconFilePath, outputPath, platform, size, appearanceMode, scale) + scale = scale or 1 + local appearance = appearanceMode or "Light" + + -- ictool command format (based on official usage) + -- ictool input-document --export-preview platform appearance width height scale output-png-path + local cmd = quoteString(ictool) .. " " .. quoteString(iconFilePath) .. + " --export-preview " .. platform .. " " .. appearance .. + " " .. size .. " " .. size .. " " .. scale .. + " " .. quoteString(outputPath) + + return cmd +end + +-- Detect ictool command format and return command builder function +local function getIconToolCommandBuilder(ictool, debugBuildProcess) + -- Get help output to detect command format + local handle = io.popen(quoteString(ictool) .. " --help 2>&1") + local helpOutput = "" + if handle then + helpOutput = handle:read("*a") or "" + handle:close() + end + + local usesExportSubcommand = helpOutput:match("export") ~= nil + + if debugBuildProcess and debugBuildProcess > 1 then + print("ictool format: " .. (usesExportSubcommand and "modern (export subcommand)" or "legacy")) + end + + -- Return function that builds commands based on detected format + return function(iconFilePath, outputPath, platform, size, appearanceMode) + scale = scale or 1 + local appearance = appearanceMode or "Light" + local cmd = quoteString(ictool) .. " " .. quoteString(iconFilePath) .. + " --export-preview " .. platform .. " " .. appearance .. + " " .. size .. " " .. size .. " " .. scale .. + " " .. quoteString(outputPath) + return cmd + end +end + +-- Platform-specific icon specifications +local function getIconSpecsForPlatform(platform) + if platform == "ios" or platform == "iphone" then + return { + platform = "iOS", + sizes = { + -- iPhone + {name = "Icon-App-20x20@2x", size = 40, idiom = "iphone", scale = "2x", sizeKey = "20x20"}, + {name = "Icon-App-20x20@3x", size = 60, idiom = "iphone", scale = "3x", sizeKey = "20x20"}, + {name = "Icon-App-29x29@2x", size = 58, idiom = "iphone", scale = "2x", sizeKey = "29x29"}, + {name = "Icon-App-29x29@3x", size = 87, idiom = "iphone", scale = "3x", sizeKey = "29x29"}, + {name = "Icon-App-40x40@2x", size = 80, idiom = "iphone", scale = "2x", sizeKey = "40x40"}, + {name = "Icon-App-40x40@3x", size = 120, idiom = "iphone", scale = "3x", sizeKey = "40x40"}, + {name = "Icon-App-60x60@2x", size = 120, idiom = "iphone", scale = "2x", sizeKey = "60x60"}, + {name = "Icon-App-60x60@3x", size = 180, idiom = "iphone", scale = "3x", sizeKey = "60x60"}, + -- iPad + {name = "Icon-App-20x20@1x", size = 20, idiom = "ipad", scale = "1x", sizeKey = "20x20"}, + {name = "Icon-App-20x20@2x", size = 40, idiom = "ipad", scale = "2x", sizeKey = "20x20"}, + {name = "Icon-App-29x29@1x", size = 29, idiom = "ipad", scale = "1x", sizeKey = "29x29"}, + {name = "Icon-App-29x29@2x", size = 58, idiom = "ipad", scale = "2x", sizeKey = "29x29"}, + {name = "Icon-App-40x40@1x", size = 40, idiom = "ipad", scale = "1x", sizeKey = "40x40"}, + {name = "Icon-App-40x40@2x", size = 80, idiom = "ipad", scale = "2x", sizeKey = "40x40"}, + {name = "Icon-App-76x76@1x", size = 76, idiom = "ipad", scale = "1x", sizeKey = "76x76"}, + {name = "Icon-App-76x76@2x", size = 152, idiom = "ipad", scale = "2x", sizeKey = "76x76"}, + {name = "Icon-App-83.5x83.5@2x", size = 167, idiom = "ipad", scale = "2x", sizeKey = "83.5x83.5"}, + -- App Store + {name = "Icon-App-1024x1024@1x", size = 1024, idiom = "ios-marketing", scale = "1x", sizeKey = "1024x1024"} + } + } + elseif platform == "tvos" or platform == "appletvos" then + return { + platform = "tvOS", + sizes = { + {name = "App-Icon-Small", size = 400, idiom = "tv", scale = "1x", sizeKey = "400x240"}, + {name = "App-Icon-Small@2x", size = 800, idiom = "tv", scale = "2x", sizeKey = "400x240"}, + {name = "App-Icon", size = 1280, idiom = "tv", scale = "1x", sizeKey = "1280x768"}, + {name = "App-Icon@2x", size = 2560, idiom = "tv", scale = "2x", sizeKey = "1280x768"}, + {name = "Top-Shelf-Image", size = 1920, idiom = "tv", scale = "1x", sizeKey = "1920x720"}, + {name = "Top-Shelf-Image@2x", size = 3840, idiom = "tv", scale = "2x", sizeKey = "1920x720"} + } + } + elseif platform == "macos" or platform == "osx" then + return { + platform = "macOS", + sizes = { + {name = "icon_16x16", size = 16, idiom = "mac", scale = "1x", sizeKey = "16x16"}, + {name = "icon_16x16@2x", size = 32, idiom = "mac", scale = "2x", sizeKey = "16x16"}, + {name = "icon_32x32", size = 32, idiom = "mac", scale = "1x", sizeKey = "32x32"}, + {name = "icon_32x32@2x", size = 64, idiom = "mac", scale = "2x", sizeKey = "32x32"}, + {name = "icon_128x128", size = 128, idiom = "mac", scale = "1x", sizeKey = "128x128"}, + {name = "icon_128x128@2x", size = 256, idiom = "mac", scale = "2x", sizeKey = "128x128"}, + {name = "icon_256x256", size = 256, idiom = "mac", scale = "1x", sizeKey = "256x256"}, + {name = "icon_256x256@2x", size = 512, idiom = "mac", scale = "2x", sizeKey = "256x256"}, + {name = "icon_512x512", size = 512, idiom = "mac", scale = "1x", sizeKey = "512x512"}, + {name = "icon_512x512@2x", size = 1024, idiom = "mac", scale = "2x", sizeKey = "512x512"} + } + } + end + + return nil +end + +-- Check if .icon file is a valid bundle +local function validateIconFile(iconFilePath) + -- Check if it's a directory + local attr = lfs.attributes(iconFilePath) + if not attr or attr.mode ~= "directory" then + return false, "Not a directory: " .. iconFilePath + end + + -- Check for icon.json (Icon Composer project) + local iconJson = iconFilePath .. "/icon.json" + local hasIconJson = os.execute("test -f " .. quoteString(iconJson)) == 0 + + if hasIconJson then + return true, "icon_composer" + end + + -- Check for Contents.json (Asset Catalog format inside .icon) + local contentsJson = iconFilePath .. "/Contents.json" + local hasContentsJson = os.execute("test -f " .. quoteString(contentsJson)) == 0 + + if hasContentsJson then + return true, "asset_catalog" + end + + -- Check if it contains PNG files directly + local hasPngs = false + for file in lfs.dir(iconFilePath) do + if file:match("%.png$") then + hasPngs = true + break + end + end + + if hasPngs then + return true, "png_bundle" + end + + return false, "Invalid .icon file structure (no icon.json, Contents.json, or PNG files)" +end + +-- Copy PNG files directly from .icon bundle to xcassets +local function copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, debugBuildProcess) + local contentsImages = {} + local copiedFiles = {} + + -- Copy all PNG files from .icon directory + for file in lfs.dir(iconFilePath) do + if file:match("%.png$") then + local srcPath = iconFilePath .. "/" .. file + local dstPath = tempAppIconSet .. "/" .. file + + -- Copy the file + local copyCmd = "cp " .. quoteString(srcPath) .. " " .. quoteString(dstPath) + os.execute(copyCmd) + + if debugBuildProcess and debugBuildProcess > 1 then + print(" Copied: " .. file) + end + + table.insert(copiedFiles, file) + end + end + + -- Try to match files to expected icon sizes + for _, spec in ipairs(iconSpecs.sizes) do + -- Look for matching files + local basePattern = spec.size .. "x" .. spec.size + local matchedFile = nil + + for _, file in ipairs(copiedFiles) do + if file:match(basePattern) or file:match(spec.name) then + matchedFile = file + break + end + end + + if matchedFile then + local imageEntry = { + filename = matchedFile, + idiom = spec.idiom, + scale = spec.scale, + size = spec.sizeKey + } + table.insert(contentsImages, imageEntry) + end + end + + return contentsImages, #copiedFiles +end + +-- Export icons from .icon file using ictool +function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDir, platform, debugBuildProcess) + -- Validate .icon file and determine its type + local isValid, iconType = validateIconFile(iconFilePath) + + if not isValid then + return nil, iconType -- iconType contains error message + end + + if debugBuildProcess and debugBuildProcess > 0 then + print("========================================") + print("Converting .icon file to xcassets") + print("Source: " .. iconFilePath) + print("Type: " .. iconType) + print("Platform: " .. platform) + print("========================================") + end + + -- Get platform-specific icon sizes + local iconSpecs = getIconSpecsForPlatform(platform) + if not iconSpecs then + return nil, "Unsupported platform for Icon Composer: " .. tostring(platform) + end + + -- Create temporary xcassets structure + local tempXCAssets = tmpDir .. "/GeneratedIconAssets.xcassets" + local tempAppIconSet = tempXCAssets .. "/AppIcon.appiconset" + + os.execute("mkdir -p " .. quoteString(tempAppIconSet)) + + local contentsImages = {} + local successCount = 0 + + -- Handle different .icon file types + if iconType == "png_bundle" then + -- Simple PNG bundle - just copy files + if debugBuildProcess and debugBuildProcess > 0 then + print("Processing PNG bundle...") + end + + contentsImages, successCount = copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, debugBuildProcess) + + elseif iconType == "asset_catalog" then + -- Asset catalog inside .icon - copy the whole structure + if debugBuildProcess and debugBuildProcess > 0 then + print("Processing asset catalog...") + end + + local copyCmd = "cp -R " .. quoteString(iconFilePath) .. "/* " .. quoteString(tempAppIconSet) .. "/" + os.execute(copyCmd) + + -- The Contents.json already exists, just return success + if debugBuildProcess and debugBuildProcess > 0 then + print("Copied asset catalog structure") + end + + return tempXCAssets, nil + + else -- iconType == "icon_composer" + -- Icon Composer project - use ictool to render + local ictool = findIconTool() + + if not ictool then + return nil, "ictool not found. Icon Composer must be installed with Xcode. Try: xcrun -f ictool" + end + + if debugBuildProcess and debugBuildProcess > 0 then + print("Using ictool: " .. ictool) + end + + -- Get command builder + local buildCommand = getIconToolCommandBuilder(ictool, debugBuildProcess) + -- Get command builder + local buildCommand = getIconToolCommandBuilder(ictool, debugBuildProcess) + + -- Appearance modes to export + local appearances = { + {mode = "Light", suffix = "", jsonAppearance = nil}, + {mode = "Dark", suffix = "-dark", jsonAppearance = "dark"} + } + + local failCount = 0 + + -- Export each icon size and appearance using ictool + for _, spec in ipairs(iconSpecs.sizes) do + for _, appearance in ipairs(appearances) do + local filename = spec.name .. appearance.suffix .. ".png" + local outputPath = tempAppIconSet .. "/" .. filename + + -- Build ictool command using detected format + local ictoolCmd = buildCommand(iconFilePath, outputPath, iconSpecs.platform, spec.size, appearance.mode) + + if debugBuildProcess and debugBuildProcess > 1 then + print(" Exporting: " .. filename .. " (" .. spec.size .. "x" .. spec.size .. ", " .. appearance.mode .. ")") + print(" Command: " .. ictoolCmd) + end + + local result = os.execute(ictoolCmd .. " 2>&1") + + -- Check if file was created + local testFile = io.open(outputPath, "r") + if testFile then + testFile:close() + successCount = successCount + 1 + + -- Build Contents.json entry + local imageEntry = { + filename = filename, + idiom = spec.idiom, + scale = spec.scale, + size = spec.sizeKey + } + + -- Add appearance info for dark mode + if appearance.jsonAppearance then + imageEntry.appearances = {{ + appearance = "luminosity", + value = appearance.jsonAppearance + }} + end + + table.insert(contentsImages, imageEntry) + else + failCount = failCount + 1 + if debugBuildProcess and debugBuildProcess > 1 then + print(" WARNING: Failed to export " .. filename) + end + end + end + end + + if debugBuildProcess and debugBuildProcess > 0 then + if failCount > 0 then + print("WARNING: " .. failCount .. " icon exports failed") + end + end + end + + if successCount == 0 then + return nil, "Failed to export any icons from " .. iconFilePath .. ". Check that the .icon file is valid." + end + + -- Generate Contents.json + local contentsFile = io.open(tempAppIconSet .. "/Contents.json", "w") + if not contentsFile then + return nil, "Failed to create Contents.json" + end + + -- Write Contents.json manually (simple JSON generation) + contentsFile:write('{\n "images" : [\n') + for i, img in ipairs(contentsImages) do + contentsFile:write(' {\n') + contentsFile:write(' "filename" : "' .. img.filename .. '",\n') + contentsFile:write(' "idiom" : "' .. img.idiom .. '",\n') + contentsFile:write(' "scale" : "' .. img.scale .. '",\n') + contentsFile:write(' "size" : "' .. img.size .. '"') + + if img.appearances then + contentsFile:write(',\n "appearances" : [\n') + contentsFile:write(' {\n') + contentsFile:write(' "appearance" : "luminosity",\n') + contentsFile:write(' "value" : "' .. img.appearances[1].value .. '"\n') + contentsFile:write(' }\n') + contentsFile:write(' ]') + end + + contentsFile:write('\n }') + if i < #contentsImages then + contentsFile:write(',') + end + contentsFile:write('\n') + end + contentsFile:write(' ],\n') + contentsFile:write(' "info" : {\n') + contentsFile:write(' "author" : "xcode",\n') + contentsFile:write(' "version" : 1\n') + contentsFile:write(' }\n') + contentsFile:write('}\n') + contentsFile:close() + + if debugBuildProcess and debugBuildProcess > 0 then + print("Successfully exported " .. successCount .. " icon variants") + if failCount > 0 then + print("WARNING: " .. failCount .. " icon exports failed") + end + print("Generated xcassets: " .. tempXCAssets) + print("========================================") + end + + return tempXCAssets, nil +end + +-- Export alternate icon from .icon file +function CoronaIconComposerSupport.convertAlternateIconToXCAssets(iconFilePath, iconName, tmpDir, platform, debugBuildProcess) + local ictool = findIconTool() + + if not ictool then + return nil, "ictool not found. Icon Composer must be installed with Xcode. Try: xcrun -f ictool" + end + + -- Verify .icon file exists + local iconJson = iconFilePath .. "/icon.json" + local hasIconJson = os.execute("test -f " .. quoteString(iconJson)) == 0 + + if not hasIconJson then + return nil, "Not a valid Icon Composer .icon file: " .. iconFilePath + end + + if debugBuildProcess and debugBuildProcess > 0 then + print("Converting alternate icon: " .. iconName .. " from " .. iconFilePath) + end + + -- Get platform-specific icon sizes (only need specific sizes for alternate icons) + local iconSpecs = getIconSpecsForPlatform(platform) + if not iconSpecs then + return nil, "Unsupported platform: " .. tostring(platform) + end + + -- Create xcassets structure for this alternate icon + local tempXCAssets = tmpDir .. "/AlternateIcons.xcassets" + local tempAppIconSet = tempXCAssets .. "/" .. iconName .. ".appiconset" + + os.execute("mkdir -p " .. quoteString(tempAppIconSet)) + + -- Appearance modes + local appearances = { + {mode = "Light", suffix = "", jsonAppearance = nil}, + {mode = "Dark", suffix = "-dark", jsonAppearance = "dark"} + } + + local contentsImages = {} + local successCount = 0 + + -- Export each icon size + for _, spec in ipairs(iconSpecs.sizes) do + -- Alternate icons typically don't need all sizes, focus on app icons + -- Skip iPad and marketing sizes for alternate icons unless needed + local skipSize = false + if platform == "ios" or platform == "iphone" then + -- Only include iPhone app icons for alternates + if spec.idiom ~= "iphone" then + skipSize = true + end + end + + if not skipSize then + for _, appearance in ipairs(appearances) do + local filename = spec.name .. appearance.suffix .. ".png" + local outputPath = tempAppIconSet .. "/" .. filename + + -- Build ictool command + local ictoolCmd = buildIconToolCommand(ictool, iconFilePath, outputPath, iconSpecs.platform, spec.size, appearance.mode, 1) + + if debugBuildProcess and debugBuildProcess > 1 then + print(" Exporting alternate: " .. filename) + end + + os.execute(ictoolCmd .. " 2>&1") + + local testFile = io.open(outputPath, "r") + if testFile then + testFile:close() + successCount = successCount + 1 + + local imageEntry = { + filename = filename, + idiom = spec.idiom, + scale = spec.scale, + size = spec.sizeKey + } + + if appearance.jsonAppearance then + imageEntry.appearances = {{ + appearance = "luminosity", + value = appearance.jsonAppearance + }} + end + + table.insert(contentsImages, imageEntry) + end + end + end + end + + if successCount == 0 then + return nil, "Failed to export any alternate icons from " .. iconFilePath + end + + -- Generate Contents.json for alternate icon + local contentsFile = io.open(tempAppIconSet .. "/Contents.json", "w") + if not contentsFile then + return nil, "Failed to create Contents.json for alternate icon" + end + + contentsFile:write('{\n "images" : [\n') + for i, img in ipairs(contentsImages) do + contentsFile:write(' {\n') + contentsFile:write(' "filename" : "' .. img.filename .. '",\n') + contentsFile:write(' "idiom" : "' .. img.idiom .. '",\n') + contentsFile:write(' "scale" : "' .. img.scale .. '",\n') + contentsFile:write(' "size" : "' .. img.size .. '"') + + if img.appearances then + contentsFile:write(',\n "appearances" : [\n') + contentsFile:write(' {\n') + contentsFile:write(' "appearance" : "luminosity",\n') + contentsFile:write(' "value" : "' .. img.appearances[1].value .. '"\n') + contentsFile:write(' }\n') + contentsFile:write(' ]') + end + + contentsFile:write('\n }') + if i < #contentsImages then + contentsFile:write(',') + end + contentsFile:write('\n') + end + contentsFile:write(' ],\n') + contentsFile:write(' "info" : {\n') + contentsFile:write(' "author" : "xcode",\n') + contentsFile:write(' "version" : 1\n') + contentsFile:write(' }\n') + contentsFile:write('}\n') + contentsFile:close() + + if debugBuildProcess and debugBuildProcess > 0 then + print("Exported " .. successCount .. " variants for alternate icon: " .. iconName) + end + + return tempXCAssets, nil +end + +-- Process all alternate icons from build.settings +function CoronaIconComposerSupport.processAlternateIcons(alternateIconsConfig, srcAssets, tmpDir, platform, debugBuildProcess) + if not alternateIconsConfig or type(alternateIconsConfig) ~= "table" then + return nil, nil + end + + if debugBuildProcess and debugBuildProcess > 0 then + print("========================================") + print("Processing alternate icons") + print("========================================") + end + + local alternateIconNames = {} + local tempXCAssets = tmpDir .. "/AlternateIcons.xcassets" + local hasAlternateIcons = false + + -- alternateIconsConfig can be: + -- 1. Array of icon names (existing behavior - looks for .xcassets) + -- 2. Table with iconFile paths (new .icon support) + + for iconName, iconConfig in pairs(alternateIconsConfig) do + -- Check if this is a .icon file reference + local iconFilePath = nil + + if type(iconConfig) == "string" then + -- Old format: alternateIcons = { "Icon1", "Icon2" } + -- This means look in existing xcassets + table.insert(alternateIconNames, iconConfig) + if debugBuildProcess and debugBuildProcess > 0 then + print("Alternate icon (xcassets): " .. iconConfig) + end + elseif type(iconConfig) == "table" and iconConfig.iconFile then + -- New format: alternateIcons = { DarkIcon = { iconFile = "DarkIcon.icon" } } + iconFilePath = srcAssets .. "/" .. iconConfig.iconFile + + if iconFilePath:match("%.icon/?$") and lfs.attributes(iconFilePath, "mode") == "directory" then + local altXCAssets, errMsg = CoronaIconComposerSupport.convertAlternateIconToXCAssets( + iconFilePath, + iconName, + tmpDir, + platform, + debugBuildProcess + ) + + if altXCAssets then + hasAlternateIcons = true + table.insert(alternateIconNames, iconName) + if debugBuildProcess and debugBuildProcess > 0 then + print("Converted alternate icon: " .. iconName .. " from " .. iconConfig.iconFile) + end + else + print("WARNING: Failed to convert alternate icon: " .. tostring(errMsg)) + end + else + print("WARNING: Alternate icon file not found: " .. iconFilePath) + end + end + end + + if #alternateIconNames == 0 then + return nil, nil + end + + if debugBuildProcess and debugBuildProcess > 0 then + print("Total alternate icons: " .. #alternateIconNames) + print("========================================") + end + + return alternateIconNames, (hasAlternateIcons and tempXCAssets or nil) +end + +-- Diagnostic function to help troubleshoot icon tool issues +function CoronaIconComposerSupport.diagnoseIconTool() + print("========================================") + print("Icon Composer Tool Diagnostics") + print("========================================") + + -- Check for ictool + local ictool = findIconTool() + if ictool then + print("✓ Found ictool: " .. ictool) + + -- Get version + local versionCmd = quoteString(ictool) .. " --version 2>&1" + print("\nVersion:") + os.execute(versionCmd) + + -- Show example usage + print("\nExample usage:") + print(" " .. ictool .. " input.icon --export-preview iOS Light 180 180 1 output.png") + + else + print("✗ ictool not found") + print("\nSearched in:") + print(" /Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") + print(" /Applications/Xcode_XX.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") + print("\nTo locate manually:") + print(" mdfind 'kMDItemFSName == ictool'") + print("\nIcon Composer.app should be inside Xcode.app bundle") + end + + print("========================================") +end + +-- Test if ictool is available and working +function CoronaIconComposerSupport.testIconTool() + local ictool = findIconTool() + + if not ictool then + print("ERROR: ictool not found") + print("Icon Composer must be inside Xcode.app bundle") + print("Path should be: /Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") + return false + end + + print("Found ictool at: " .. ictool) + + -- Test version command + local handle = io.popen(quoteString(ictool) .. " --version 2>&1") + local versionOutput = "" + if handle then + versionOutput = handle:read("*a") + handle:close() + end + + if versionOutput and versionOutput ~= "" then + print("ictool version: " .. versionOutput:gsub("\n", " ")) + print("ictool is working correctly") + return true + else + print("WARNING: ictool found but --version command failed") + return false + end +end + +return CoronaIconComposerSupport \ No newline at end of file diff --git a/platform/resources/iPhonePackageApp.lua b/platform/resources/iPhonePackageApp.lua index 3c37349a9..2493a5535 100644 --- a/platform/resources/iPhonePackageApp.lua +++ b/platform/resources/iPhonePackageApp.lua @@ -18,6 +18,7 @@ local simAvail, simulator = pcall(require, "simulator") -- defines modifyPlist() local CoronaPListSupport = require("CoronaPListSupport") +local CoronaIconComposerSupport = require("CoronaIconComposerSupport") local captureCommandOutput = CoronaPListSupport.captureCommandOutput local coronaLiveBuildAppDir = "_corona_live_build_app" -- "PackageApp" -- @@ -1687,18 +1688,95 @@ function iPhonePostPackage( params ) -- compile Xcode assets for icon if options.settings and options.settings.iphone then setStatus("Compiling Xcode assets catalog") + + -- Check if using Icon Composer .icon file + local iconFile = options.settings.iphone.iconFile + local xcassetsPath = srcAssets .. "/Assets.xcassets" + local platformName = "ios" -- default + local alternateIconsXCAssets = nil + + -- Determine platform + if options.targetPlatform == "tvos" or options.osPlatform == 3 then + platformName = "tvos" + elseif options.osPlatform == 2 then + platformName = "macos" + end + + -- Process primary app icon + if iconFile and iconFile:match("%.icon/?$") then + local iconPath = srcAssets .. "/" .. iconFile + + if lfs.attributes(iconPath, "mode") == "directory" then + local tempXCAssets, errMsg = CoronaIconComposerSupport.convertIconFileToXCAssets( + iconPath, + tmpDir, + platformName, + debugBuildProcess + ) + + if tempXCAssets then + xcassetsPath = tempXCAssets + print("Using Icon Composer generated assets from: " .. iconFile) + else + print("ERROR: Failed to convert .icon file: " .. tostring(errMsg)) + return errMsg + end + else + print("WARNING: iconFile specified but not found: " .. iconPath) + end + end + + -- Process alternate icons (both .icon and existing xcassets) + local alternateIcons = options.settings.iphone.alternateIcons + if alternateIcons then + local alternateIconNames, altXCAssets = CoronaIconComposerSupport.processAlternateIcons( + alternateIcons, + srcAssets, + tmpDir, + platformName, + debugBuildProcess + ) + + if alternateIconNames and #alternateIconNames > 0 then + -- Update the settings with processed alternate icon names + options.settings.iphone.alternateIcons = alternateIconNames + alternateIconsXCAssets = altXCAssets + end + end + + -- Compile primary app icon xcassets local xcassetPlatformOptions = { { "target-device", "iphone" }, { "target-device", "ipad" }, { "minimum-deployment-target", "8.0" }, { "platform", options.signingIdentity and "iphoneos" or "iphonesimulator" }, - - {"app-icon", "AppIcon"}, + {"app-icon", "AppIcon"}, } - err = CoronaPListSupport.compileXcassets(options, tmpDir, srcAssets, xcassetPlatformOptions, options.settings.iphone) + + err = CoronaPListSupport.compileXcassets(options, tmpDir, xcassetsPath, xcassetPlatformOptions, options.settings.iphone) if err then return err end + + -- Compile alternate icons xcassets if we generated any from .icon files + if alternateIconsXCAssets then + if debugBuildProcess and debugBuildProcess > 0 then + print("Compiling alternate icons xcassets...") + end + + err = CoronaPListSupport.compileXcassets(options, tmpDir, alternateIconsXCAssets, xcassetPlatformOptions, options.settings.iphone) + if err then + return err + end + end + + -- Clean up temporary xcassets + if iconFile and iconFile:match("%.icon/?$") and xcassetsPath ~= (srcAssets .. "/Assets.xcassets") then + os.execute("rm -rf " .. quoteString(xcassetsPath)) + end + if alternateIconsXCAssets then + os.execute("rm -rf " .. quoteString(alternateIconsXCAssets)) + end end setStatus("Packaging app") diff --git a/tools/CoronaBuilder/Rtt_CoronaBuilder.cpp b/tools/CoronaBuilder/Rtt_CoronaBuilder.cpp index b668fe655..e501d85ff 100644 --- a/tools/CoronaBuilder/Rtt_CoronaBuilder.cpp +++ b/tools/CoronaBuilder/Rtt_CoronaBuilder.cpp @@ -58,6 +58,7 @@ int luaload_json(lua_State *L); int luaload_dkjson(lua_State *L); int luaload_CoronaBuilder(lua_State *L); int luaload_CoronaPListSupport(lua_State *L); +int luaload_CoronaIconComposerSupport(lua_State *L); #ifdef Rtt_DEBUG void lua_stackdump(lua_State* L); @@ -229,6 +230,7 @@ CoronaBuilder::CoronaBuilder( #if defined(CORONABUILDER_IOS) || defined(CORONABUILDER_TVOS) || defined(CORONABUILDER_OSX) Lua::RegisterModuleLoader( fL, "CoronaPListSupport", Lua::Open< luaload_CoronaPListSupport > ); + Lua::RegisterModuleLoader( fL, "CoronaIconComposerSupport", Lua::Open< luaload_CoronaIconComposerSupport > ); #endif Lua::RegisterModuleLoader( fL, "dkjson", Lua::Open< luaload_dkjson >, 0 ); Lua::RegisterModuleLoader( fL, "json", Lua::Open< luaload_json >, 0 ); From 32a45d435b2743b188e9c9adde2dafc1e6112c70 Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Thu, 23 Oct 2025 02:47:41 -0400 Subject: [PATCH 2/6] Tweaks --- .../resources/CoronaIconComposerSupport.lua | 682 +++++++++--------- platform/resources/iPhonePackageApp.lua | 76 +- 2 files changed, 377 insertions(+), 381 deletions(-) diff --git a/platform/resources/CoronaIconComposerSupport.lua b/platform/resources/CoronaIconComposerSupport.lua index 25d4a7e69..26373e02f 100644 --- a/platform/resources/CoronaIconComposerSupport.lua +++ b/platform/resources/CoronaIconComposerSupport.lua @@ -2,6 +2,7 @@ -- -- CoronaIconComposerSupport.lua -- Handles Icon Composer .icon files for iOS, tvOS, and macOS builds +-- iOS now uses actool to compile .icon files with Liquid Glass effects -- ------------------------------------------------------------------------------ @@ -17,7 +18,8 @@ local function quoteString( str ) return "\"" .. str .. "\"" end --- Find ictool executable (Icon Composer's command-line tool) + +-- Find ictool executable (Icon Composer's command-line tool) - for tvOS/macOS fallback local function findIconTool() local paths = { -- Check common Xcode paths (ictool is inside Icon Composer.app) @@ -53,77 +55,68 @@ local function findIconTool() return nil end --- Build ictool command for exporting icons --- Format: ictool input-document --export-preview platform appearance width height scale output-png-path +-- Build actool command for compiling .icon file +local function buildActoolCommand(actool, iconFilePath, outputPath, platform, debugBuildProcess) + local plistPath = outputPath .. "/assetcatalog_generated_info.plist" + + -- Determine platform-specific parameters + local targetDevice = "iphone" + local platformName = "iphoneos" + local minDeployment = "13.0" + + if platform == "ios" or platform == "iphone" then + targetDevice = "iphone" + platformName = "iphoneos" + minDeployment = "13.0" + elseif platform == "tvos" or platform == "appletvos" then + targetDevice = "tv" + platformName = "appletvos" + minDeployment = "13.0" + elseif platform == "macos" or platform == "osx" then + targetDevice = "mac" + platformName = "macosx" + minDeployment = "10.15" + end + + -- Build actool command + -- actool compiles asset catalogs (.xcassets or .icon) into Assets.car files + local cmd = "xcrun actool " .. quoteString(iconFilePath) .. + " --compile " .. quoteString(outputPath) .. + " --output-format human-readable-text" .. + " --notices --warnings --errors" .. + " --output-partial-info-plist " .. quoteString(plistPath) .. + " --app-icon AppIcon" .. + " --enable-on-demand-resources NO" .. + " --development-region en" .. + " --target-device " .. targetDevice .. + " --minimum-deployment-target " .. minDeployment .. + " --platform " .. platformName + + print("actool command: " .. cmd) + + return cmd, plistPath +end + +-- Build ictool command for exporting icons (fallback for tvOS/macOS) local function buildIconToolCommand(ictool, iconFilePath, outputPath, platform, size, appearanceMode, scale) scale = scale or 1 local appearance = appearanceMode or "Light" - -- ictool command format (based on official usage) - -- ictool input-document --export-preview platform appearance width height scale output-png-path local cmd = quoteString(ictool) .. " " .. quoteString(iconFilePath) .. " --export-preview " .. platform .. " " .. appearance .. " " .. size .. " " .. size .. " " .. scale .. " " .. quoteString(outputPath) - - return cmd -end - --- Detect ictool command format and return command builder function -local function getIconToolCommandBuilder(ictool, debugBuildProcess) - -- Get help output to detect command format - local handle = io.popen(quoteString(ictool) .. " --help 2>&1") - local helpOutput = "" - if handle then - helpOutput = handle:read("*a") or "" - handle:close() - end - - local usesExportSubcommand = helpOutput:match("export") ~= nil - if debugBuildProcess and debugBuildProcess > 1 then - print("ictool format: " .. (usesExportSubcommand and "modern (export subcommand)" or "legacy")) - end - - -- Return function that builds commands based on detected format - return function(iconFilePath, outputPath, platform, size, appearanceMode) - scale = scale or 1 - local appearance = appearanceMode or "Light" - local cmd = quoteString(ictool) .. " " .. quoteString(iconFilePath) .. - " --export-preview " .. platform .. " " .. appearance .. - " " .. size .. " " .. size .. " " .. scale .. - " " .. quoteString(outputPath) - return cmd - end + return cmd end --- Platform-specific icon specifications +-- Platform-specific icon specifications (for ictool fallback) local function getIconSpecsForPlatform(platform) if platform == "ios" or platform == "iphone" then return { platform = "iOS", sizes = { - -- iPhone - {name = "Icon-App-20x20@2x", size = 40, idiom = "iphone", scale = "2x", sizeKey = "20x20"}, - {name = "Icon-App-20x20@3x", size = 60, idiom = "iphone", scale = "3x", sizeKey = "20x20"}, - {name = "Icon-App-29x29@2x", size = 58, idiom = "iphone", scale = "2x", sizeKey = "29x29"}, - {name = "Icon-App-29x29@3x", size = 87, idiom = "iphone", scale = "3x", sizeKey = "29x29"}, - {name = "Icon-App-40x40@2x", size = 80, idiom = "iphone", scale = "2x", sizeKey = "40x40"}, - {name = "Icon-App-40x40@3x", size = 120, idiom = "iphone", scale = "3x", sizeKey = "40x40"}, - {name = "Icon-App-60x60@2x", size = 120, idiom = "iphone", scale = "2x", sizeKey = "60x60"}, - {name = "Icon-App-60x60@3x", size = 180, idiom = "iphone", scale = "3x", sizeKey = "60x60"}, - -- iPad - {name = "Icon-App-20x20@1x", size = 20, idiom = "ipad", scale = "1x", sizeKey = "20x20"}, - {name = "Icon-App-20x20@2x", size = 40, idiom = "ipad", scale = "2x", sizeKey = "20x20"}, - {name = "Icon-App-29x29@1x", size = 29, idiom = "ipad", scale = "1x", sizeKey = "29x29"}, - {name = "Icon-App-29x29@2x", size = 58, idiom = "ipad", scale = "2x", sizeKey = "29x29"}, - {name = "Icon-App-40x40@1x", size = 40, idiom = "ipad", scale = "1x", sizeKey = "40x40"}, - {name = "Icon-App-40x40@2x", size = 80, idiom = "ipad", scale = "2x", sizeKey = "40x40"}, - {name = "Icon-App-76x76@1x", size = 76, idiom = "ipad", scale = "1x", sizeKey = "76x76"}, - {name = "Icon-App-76x76@2x", size = 152, idiom = "ipad", scale = "2x", sizeKey = "76x76"}, - {name = "Icon-App-83.5x83.5@2x", size = 167, idiom = "ipad", scale = "2x", sizeKey = "83.5x83.5"}, - -- App Store - {name = "Icon-App-1024x1024@1x", size = 1024, idiom = "ios-marketing", scale = "1x", sizeKey = "1024x1024"} + {name = "Icon", size = 1024, idiom = "universal", platform = "ios", sizeKey = "1024x1024", scale = nil} } } elseif platform == "tvos" or platform == "appletvos" then @@ -200,7 +193,7 @@ local function validateIconFile(iconFilePath) end -- Copy PNG files directly from .icon bundle to xcassets -local function copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, debugBuildProcess) +local function copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, debugBuildProcess, platform) local contentsImages = {} local copiedFiles = {} @@ -222,34 +215,251 @@ local function copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, end end - -- Try to match files to expected icon sizes - for _, spec in ipairs(iconSpecs.sizes) do - -- Look for matching files - local basePattern = spec.size .. "x" .. spec.size - local matchedFile = nil + -- For iOS universal format, look for 1024x1024 icons + if platform == "ios" or platform == "iphone" then + -- Look for light, dark, and tinted variants + local variants = { + {pattern = "1024.*[Ll]ight", appearance = nil, suffix = "Light"}, + {pattern = "1024.*[Dd]ark", appearance = "dark", suffix = "Dark"}, + {pattern = "1024.*[Tt]inted", appearance = "tinted", suffix = "Tinted"} + } - for _, file in ipairs(copiedFiles) do - if file:match(basePattern) or file:match(spec.name) then - matchedFile = file - break + for _, variant in ipairs(variants) do + local matchedFile = nil + for _, file in ipairs(copiedFiles) do + if file:match(variant.pattern) then + matchedFile = file + break + end + end + + if matchedFile then + local imageEntry = { + filename = matchedFile, + idiom = "universal", + platform = "ios", + size = "1024x1024" + } + + if variant.appearance then + imageEntry.appearances = {{ + appearance = "luminosity", + value = variant.appearance + }} + end + + table.insert(contentsImages, imageEntry) end end - - if matchedFile then - local imageEntry = { - filename = matchedFile, - idiom = spec.idiom, - scale = spec.scale, - size = spec.sizeKey - } - table.insert(contentsImages, imageEntry) + else + -- For other platforms, use the old matching logic + for _, spec in ipairs(iconSpecs.sizes) do + local basePattern = spec.size .. "x" .. spec.size + local matchedFile = nil + + for _, file in ipairs(copiedFiles) do + if file:match(basePattern) or file:match(spec.name) then + matchedFile = file + break + end + end + + if matchedFile then + local imageEntry = { + filename = matchedFile, + idiom = spec.idiom, + scale = spec.scale, + size = spec.sizeKey + } + table.insert(contentsImages, imageEntry) + end end end return contentsImages, #copiedFiles end --- Export icons from .icon file using ictool +-- Compile .icon file using actool (for iOS with Liquid Glass effects) +local function compileIconWithActool(actool, iconFilePath, tmpDir, platform, debugBuildProcess) + + print("Using actool to compile .icon file with Liquid Glass effects...") + + -- Create output directory for compiled assets + local outputPath = tmpDir .. "/CompiledAssets" + os.execute("mkdir -p " .. quoteString(outputPath)) + + -- Build and execute actool command + local actoolCmd, plistPath = buildActoolCommand(actool, iconFilePath, outputPath, platform, debugBuildProcess) + + if debugBuildProcess and debugBuildProcess > 1 then + print("Executing actool...") + end + + local result = os.execute(actoolCmd .. " 2>&1") + + -- Check if Assets.car was created + local assetsCarPath = outputPath .. "/Assets.car" + local testFile = io.open(assetsCarPath, "r") + if testFile then + testFile:close() + + + print("✓ Successfully compiled Assets.car with actool") + print(" Location: " .. assetsCarPath) + + -- Clean up the generated plist file + os.execute("rm -f " .. quoteString(plistPath)) + + return outputPath, assetsCarPath + else + return nil, "actool failed to compile .icon file. Check that the .icon file is valid." + end +end + +-- Export icons from .icon file using ictool (fallback for tvOS/macOS) +local function exportIconWithIctool(ictool, iconFilePath, tmpDir, platform, iconSpecs, debugBuildProcess) + if debugBuildProcess and debugBuildProcess > 0 then + print("Using ictool to export icon layers...") + end + + local tempXCAssets = tmpDir .. "/TempAssets.xcassets" + local tempAppIconSet = tempXCAssets .. "/AppIcon.appiconset" + + os.execute("mkdir -p " .. quoteString(tempAppIconSet)) + + -- Appearance modes to export + local appearances = { + {mode = "Light", suffix = "-Light", jsonAppearance = nil}, + {mode = "Dark", suffix = "-Dark", jsonAppearance = "dark"} + } + + -- Add tinted appearance for iOS (though we prefer actool for iOS) + if platform == "ios" or platform == "iphone" then + table.insert(appearances, {mode = "Tinted", suffix = "-Tinted", jsonAppearance = "tinted"}) + end + + local contentsImages = {} + local successCount = 0 + local failCount = 0 + + -- Export each icon size and appearance using ictool + for _, spec in ipairs(iconSpecs.sizes) do + for _, appearance in ipairs(appearances) do + local filename = spec.name .. appearance.suffix .. "-" .. spec.sizeKey .. ".png" + local outputPath = tempAppIconSet .. "/" .. filename + + -- Build ictool command + local ictoolCmd = buildIconToolCommand(ictool, iconFilePath, outputPath, iconSpecs.platform, spec.size, appearance.mode, 1) + + if debugBuildProcess and debugBuildProcess > 1 then + print(" Exporting: " .. filename .. " (" .. spec.size .. "x" .. spec.size .. ", " .. appearance.mode .. ")") + end + + os.execute(ictoolCmd .. " 2>&1") + + -- Check if file was created + local testFile = io.open(outputPath, "r") + if testFile then + testFile:close() + successCount = successCount + 1 + + -- Build Contents.json entry + local imageEntry = { + filename = filename, + idiom = spec.idiom, + size = spec.sizeKey + } + + -- Add platform for iOS universal format + if platform == "ios" or platform == "iphone" then + imageEntry.platform = "ios" + end + + -- Add scale for non-iOS platforms + if spec.scale then + imageEntry.scale = spec.scale + end + + -- Add appearance info for dark and tinted modes + if appearance.jsonAppearance then + imageEntry.appearances = {{ + appearance = "luminosity", + value = appearance.jsonAppearance + }} + end + + table.insert(contentsImages, imageEntry) + else + failCount = failCount + 1 + if debugBuildProcess and debugBuildProcess > 1 then + print(" WARNING: Failed to export " .. filename) + end + end + end + end + + if successCount == 0 then + return nil, "Failed to export any icons with ictool" + end + + -- Generate Contents.json + local contentsFile = io.open(tempAppIconSet .. "/Contents.json", "w") + if not contentsFile then + return nil, "Failed to create Contents.json" + end + + -- Write Contents.json + contentsFile:write('{\n "images" : [\n') + for i, img in ipairs(contentsImages) do + contentsFile:write(' {\n') + + if img.appearances then + contentsFile:write(' "appearances" : [\n') + contentsFile:write(' {\n') + contentsFile:write(' "appearance" : "luminosity",\n') + contentsFile:write(' "value" : "' .. img.appearances[1].value .. '"\n') + contentsFile:write(' }\n') + contentsFile:write(' ],\n') + end + + contentsFile:write(' "filename" : "' .. img.filename .. '",\n') + contentsFile:write(' "idiom" : "' .. img.idiom .. '"') + + if img.platform then + contentsFile:write(',\n "platform" : "' .. img.platform .. '"') + end + + if img.scale then + contentsFile:write(',\n "scale" : "' .. img.scale .. '"') + end + + contentsFile:write(',\n "size" : "' .. img.size .. '"') + contentsFile:write('\n }') + + if i < #contentsImages then + contentsFile:write(',') + end + contentsFile:write('\n') + end + contentsFile:write(' ],\n') + contentsFile:write(' "info" : {\n') + contentsFile:write(' "author" : "xcode",\n') + contentsFile:write(' "version" : 1\n') + contentsFile:write(' }\n') + contentsFile:write('}\n') + contentsFile:close() + + if debugBuildProcess and debugBuildProcess > 0 then + print("Successfully exported " .. successCount .. " icon variants") + if failCount > 0 then + print("WARNING: " .. failCount .. " icon exports failed") + end + end + + return tempXCAssets, nil +end + +-- Main function: Convert .icon file to xcassets or Assets.car function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDir, platform, debugBuildProcess) -- Validate .icon file and determine its type local isValid, iconType = validateIconFile(iconFilePath) @@ -260,7 +470,7 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi if debugBuildProcess and debugBuildProcess > 0 then print("========================================") - print("Converting .icon file to xcassets") + print("Converting .icon file") print("Source: " .. iconFilePath) print("Type: " .. iconType) print("Platform: " .. platform) @@ -274,7 +484,7 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi end -- Create temporary xcassets structure - local tempXCAssets = tmpDir .. "/GeneratedIconAssets.xcassets" + local tempXCAssets = tmpDir .. "/TempAssets.xcassets" local tempAppIconSet = tempXCAssets .. "/AppIcon.appiconset" os.execute("mkdir -p " .. quoteString(tempAppIconSet)) @@ -289,7 +499,7 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi print("Processing PNG bundle...") end - contentsImages, successCount = copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, debugBuildProcess) + contentsImages, successCount = copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, debugBuildProcess, platform) elseif iconType == "asset_catalog" then -- Asset catalog inside .icon - copy the whole structure @@ -300,7 +510,6 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi local copyCmd = "cp -R " .. quoteString(iconFilePath) .. "/* " .. quoteString(tempAppIconSet) .. "/" os.execute(copyCmd) - -- The Contents.json already exists, just return success if debugBuildProcess and debugBuildProcess > 0 then print("Copied asset catalog structure") end @@ -308,114 +517,61 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi return tempXCAssets, nil else -- iconType == "icon_composer" - -- Icon Composer project - use ictool to render - local ictool = findIconTool() - - if not ictool then - return nil, "ictool not found. Icon Composer must be installed with Xcode. Try: xcrun -f ictool" - end + -- Icon Composer project - use actool for iOS, ictool for others - if debugBuildProcess and debugBuildProcess > 0 then - print("Using ictool: " .. ictool) - end - - -- Get command builder - local buildCommand = getIconToolCommandBuilder(ictool, debugBuildProcess) - -- Get command builder - local buildCommand = getIconToolCommandBuilder(ictool, debugBuildProcess) - - -- Appearance modes to export - local appearances = { - {mode = "Light", suffix = "", jsonAppearance = nil}, - {mode = "Dark", suffix = "-dark", jsonAppearance = "dark"} - } - - local failCount = 0 - - -- Export each icon size and appearance using ictool - for _, spec in ipairs(iconSpecs.sizes) do - for _, appearance in ipairs(appearances) do - local filename = spec.name .. appearance.suffix .. ".png" - local outputPath = tempAppIconSet .. "/" .. filename - - -- Build ictool command using detected format - local ictoolCmd = buildCommand(iconFilePath, outputPath, iconSpecs.platform, spec.size, appearance.mode) + local outputPath, assetsCarPath = compileIconWithActool("xcrun actool", iconFilePath, tmpDir, platform, debugBuildProcess) - if debugBuildProcess and debugBuildProcess > 1 then - print(" Exporting: " .. filename .. " (" .. spec.size .. "x" .. spec.size .. ", " .. appearance.mode .. ")") - print(" Command: " .. ictoolCmd) - end - - local result = os.execute(ictoolCmd .. " 2>&1") - - -- Check if file was created - local testFile = io.open(outputPath, "r") - if testFile then - testFile:close() - successCount = successCount + 1 - - -- Build Contents.json entry - local imageEntry = { - filename = filename, - idiom = spec.idiom, - scale = spec.scale, - size = spec.sizeKey - } - - -- Add appearance info for dark mode - if appearance.jsonAppearance then - imageEntry.appearances = {{ - appearance = "luminosity", - value = appearance.jsonAppearance - }} - end - - table.insert(contentsImages, imageEntry) - else - failCount = failCount + 1 - if debugBuildProcess and debugBuildProcess > 1 then - print(" WARNING: Failed to export " .. filename) - end - end + if outputPath then + print("Generated compiled assets with Liquid Glass effects") + print("========================================") + return outputPath, assetsCarPath + else + -- Fall back to ictool if actool fails + if debugBuildProcess and debugBuildProcess > 0 then + print("WARNING: actool compilation failed, falling back to ictool") end end - if debugBuildProcess and debugBuildProcess > 0 then - if failCount > 0 then - print("WARNING: " .. failCount .. " icon exports failed") - end - end + end if successCount == 0 then return nil, "Failed to export any icons from " .. iconFilePath .. ". Check that the .icon file is valid." end - -- Generate Contents.json + -- Generate Contents.json for PNG bundle local contentsFile = io.open(tempAppIconSet .. "/Contents.json", "w") if not contentsFile then return nil, "Failed to create Contents.json" end - -- Write Contents.json manually (simple JSON generation) contentsFile:write('{\n "images" : [\n') for i, img in ipairs(contentsImages) do contentsFile:write(' {\n') - contentsFile:write(' "filename" : "' .. img.filename .. '",\n') - contentsFile:write(' "idiom" : "' .. img.idiom .. '",\n') - contentsFile:write(' "scale" : "' .. img.scale .. '",\n') - contentsFile:write(' "size" : "' .. img.size .. '"') if img.appearances then - contentsFile:write(',\n "appearances" : [\n') + contentsFile:write(' "appearances" : [\n') contentsFile:write(' {\n') contentsFile:write(' "appearance" : "luminosity",\n') contentsFile:write(' "value" : "' .. img.appearances[1].value .. '"\n') contentsFile:write(' }\n') - contentsFile:write(' ]') + contentsFile:write(' ],\n') + end + + contentsFile:write(' "filename" : "' .. img.filename .. '",\n') + contentsFile:write(' "idiom" : "' .. img.idiom .. '"') + + if img.platform then + contentsFile:write(',\n "platform" : "' .. img.platform .. '"') end + if img.scale then + contentsFile:write(',\n "scale" : "' .. img.scale .. '"') + end + + contentsFile:write(',\n "size" : "' .. img.size .. '"') contentsFile:write('\n }') + if i < #contentsImages then contentsFile:write(',') end @@ -430,10 +586,7 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi contentsFile:close() if debugBuildProcess and debugBuildProcess > 0 then - print("Successfully exported " .. successCount .. " icon variants") - if failCount > 0 then - print("WARNING: " .. failCount .. " icon exports failed") - end + print("Successfully processed " .. successCount .. " icon variants") print("Generated xcassets: " .. tempXCAssets) print("========================================") end @@ -443,10 +596,26 @@ end -- Export alternate icon from .icon file function CoronaIconComposerSupport.convertAlternateIconToXCAssets(iconFilePath, iconName, tmpDir, platform, debugBuildProcess) + -- For iOS, try actool first + if platform == "ios" or platform == "iphone" then + local actool = findActool() + + if actool then + print("Converting alternate icon: " .. iconName .. " with actool") + + local outputPath, assetsCarPath = compileIconWithActool(actool, iconFilePath, tmpDir, platform, debugBuildProcess) + + if outputPath then + return outputPath, assetsCarPath + end + end + end + + -- Fallback to ictool local ictool = findIconTool() if not ictool then - return nil, "ictool not found. Icon Composer must be installed with Xcode. Try: xcrun -f ictool" + return nil, "Neither actool nor ictool found. Xcode must be installed." end -- Verify .icon file exists @@ -461,124 +630,13 @@ function CoronaIconComposerSupport.convertAlternateIconToXCAssets(iconFilePath, print("Converting alternate icon: " .. iconName .. " from " .. iconFilePath) end - -- Get platform-specific icon sizes (only need specific sizes for alternate icons) + -- Get platform-specific icon sizes local iconSpecs = getIconSpecsForPlatform(platform) if not iconSpecs then return nil, "Unsupported platform: " .. tostring(platform) end - -- Create xcassets structure for this alternate icon - local tempXCAssets = tmpDir .. "/AlternateIcons.xcassets" - local tempAppIconSet = tempXCAssets .. "/" .. iconName .. ".appiconset" - - os.execute("mkdir -p " .. quoteString(tempAppIconSet)) - - -- Appearance modes - local appearances = { - {mode = "Light", suffix = "", jsonAppearance = nil}, - {mode = "Dark", suffix = "-dark", jsonAppearance = "dark"} - } - - local contentsImages = {} - local successCount = 0 - - -- Export each icon size - for _, spec in ipairs(iconSpecs.sizes) do - -- Alternate icons typically don't need all sizes, focus on app icons - -- Skip iPad and marketing sizes for alternate icons unless needed - local skipSize = false - if platform == "ios" or platform == "iphone" then - -- Only include iPhone app icons for alternates - if spec.idiom ~= "iphone" then - skipSize = true - end - end - - if not skipSize then - for _, appearance in ipairs(appearances) do - local filename = spec.name .. appearance.suffix .. ".png" - local outputPath = tempAppIconSet .. "/" .. filename - - -- Build ictool command - local ictoolCmd = buildIconToolCommand(ictool, iconFilePath, outputPath, iconSpecs.platform, spec.size, appearance.mode, 1) - - if debugBuildProcess and debugBuildProcess > 1 then - print(" Exporting alternate: " .. filename) - end - - os.execute(ictoolCmd .. " 2>&1") - - local testFile = io.open(outputPath, "r") - if testFile then - testFile:close() - successCount = successCount + 1 - - local imageEntry = { - filename = filename, - idiom = spec.idiom, - scale = spec.scale, - size = spec.sizeKey - } - - if appearance.jsonAppearance then - imageEntry.appearances = {{ - appearance = "luminosity", - value = appearance.jsonAppearance - }} - end - - table.insert(contentsImages, imageEntry) - end - end - end - end - - if successCount == 0 then - return nil, "Failed to export any alternate icons from " .. iconFilePath - end - - -- Generate Contents.json for alternate icon - local contentsFile = io.open(tempAppIconSet .. "/Contents.json", "w") - if not contentsFile then - return nil, "Failed to create Contents.json for alternate icon" - end - - contentsFile:write('{\n "images" : [\n') - for i, img in ipairs(contentsImages) do - contentsFile:write(' {\n') - contentsFile:write(' "filename" : "' .. img.filename .. '",\n') - contentsFile:write(' "idiom" : "' .. img.idiom .. '",\n') - contentsFile:write(' "scale" : "' .. img.scale .. '",\n') - contentsFile:write(' "size" : "' .. img.size .. '"') - - if img.appearances then - contentsFile:write(',\n "appearances" : [\n') - contentsFile:write(' {\n') - contentsFile:write(' "appearance" : "luminosity",\n') - contentsFile:write(' "value" : "' .. img.appearances[1].value .. '"\n') - contentsFile:write(' }\n') - contentsFile:write(' ]') - end - - contentsFile:write('\n }') - if i < #contentsImages then - contentsFile:write(',') - end - contentsFile:write('\n') - end - contentsFile:write(' ],\n') - contentsFile:write(' "info" : {\n') - contentsFile:write(' "author" : "xcode",\n') - contentsFile:write(' "version" : 1\n') - contentsFile:write(' }\n') - contentsFile:write('}\n') - contentsFile:close() - - if debugBuildProcess and debugBuildProcess > 0 then - print("Exported " .. successCount .. " variants for alternate icon: " .. iconName) - end - - return tempXCAssets, nil + return exportIconWithIctool(ictool, iconFilePath, tmpDir, platform, iconSpecs, debugBuildProcess) end -- Process all alternate icons from build.settings @@ -597,17 +655,11 @@ function CoronaIconComposerSupport.processAlternateIcons(alternateIconsConfig, s local tempXCAssets = tmpDir .. "/AlternateIcons.xcassets" local hasAlternateIcons = false - -- alternateIconsConfig can be: - -- 1. Array of icon names (existing behavior - looks for .xcassets) - -- 2. Table with iconFile paths (new .icon support) - for iconName, iconConfig in pairs(alternateIconsConfig) do - -- Check if this is a .icon file reference local iconFilePath = nil if type(iconConfig) == "string" then -- Old format: alternateIcons = { "Icon1", "Icon2" } - -- This means look in existing xcassets table.insert(alternateIconNames, iconConfig) if debugBuildProcess and debugBuildProcess > 0 then print("Alternate icon (xcassets): " .. iconConfig) @@ -652,68 +704,6 @@ function CoronaIconComposerSupport.processAlternateIcons(alternateIconsConfig, s return alternateIconNames, (hasAlternateIcons and tempXCAssets or nil) end --- Diagnostic function to help troubleshoot icon tool issues -function CoronaIconComposerSupport.diagnoseIconTool() - print("========================================") - print("Icon Composer Tool Diagnostics") - print("========================================") - - -- Check for ictool - local ictool = findIconTool() - if ictool then - print("✓ Found ictool: " .. ictool) - - -- Get version - local versionCmd = quoteString(ictool) .. " --version 2>&1" - print("\nVersion:") - os.execute(versionCmd) - - -- Show example usage - print("\nExample usage:") - print(" " .. ictool .. " input.icon --export-preview iOS Light 180 180 1 output.png") - - else - print("✗ ictool not found") - print("\nSearched in:") - print(" /Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") - print(" /Applications/Xcode_XX.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") - print("\nTo locate manually:") - print(" mdfind 'kMDItemFSName == ictool'") - print("\nIcon Composer.app should be inside Xcode.app bundle") - end - - print("========================================") -end --- Test if ictool is available and working -function CoronaIconComposerSupport.testIconTool() - local ictool = findIconTool() - - if not ictool then - print("ERROR: ictool not found") - print("Icon Composer must be inside Xcode.app bundle") - print("Path should be: /Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") - return false - end - - print("Found ictool at: " .. ictool) - - -- Test version command - local handle = io.popen(quoteString(ictool) .. " --version 2>&1") - local versionOutput = "" - if handle then - versionOutput = handle:read("*a") - handle:close() - end - - if versionOutput and versionOutput ~= "" then - print("ictool version: " .. versionOutput:gsub("\n", " ")) - print("ictool is working correctly") - return true - else - print("WARNING: ictool found but --version command failed") - return false - end -end return CoronaIconComposerSupport \ No newline at end of file diff --git a/platform/resources/iPhonePackageApp.lua b/platform/resources/iPhonePackageApp.lua index 67ac6396b..88f08e8d3 100644 --- a/platform/resources/iPhonePackageApp.lua +++ b/platform/resources/iPhonePackageApp.lua @@ -1686,31 +1686,30 @@ function iPhonePostPackage( params ) -- Check if using Icon Composer .icon file local iconFile = options.settings.iphone.iconFile local xcassetsPath = srcAssets .. "/Assets.xcassets" - local platformName = "ios" -- default local alternateIconsXCAssets = nil - - -- Determine platform - if options.targetPlatform == "tvos" or options.osPlatform == 3 then - platformName = "tvos" - elseif options.osPlatform == 2 then - platformName = "macos" - end + -- Process primary app icon if iconFile and iconFile:match("%.icon/?$") then local iconPath = srcAssets .. "/" .. iconFile if lfs.attributes(iconPath, "mode") == "directory" then - local tempXCAssets, errMsg = CoronaIconComposerSupport.convertIconFileToXCAssets( + local outputPath, assetsCarPath = CoronaIconComposerSupport.convertIconFileToXCAssets( iconPath, tmpDir, - platformName, + "ios", debugBuildProcess ) - if tempXCAssets then - xcassetsPath = tempXCAssets - print("Using Icon Composer generated assets from: " .. iconFile) + if outputPath then + print("Converted .icon file to xcassets at: " .. outputPath) + local copyCmd = "cp -R -f " .. outputPath .. "/* " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app") + + runScript(copyCmd) + + options.settings.iphone.plist.CFBundleIcons = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60"}, CFBundleIconName = "AppIcon" }} + options.settings.iphone.plist["CFBundleIcons~ipad"] = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60", "AppIcon76x76"}, CFBundleIconName = "AppIcon" }} + options.settings.iphone.plist.UIPrerenderedIcon = true else print("ERROR: Failed to convert .icon file: " .. tostring(errMsg)) return errMsg @@ -1718,8 +1717,26 @@ function iPhonePostPackage( params ) else print("WARNING: iconFile specified but not found: " .. iconPath) end + else + -- Compile primary app icon xcassets + local xcassetPlatformOptions = { + { "target-device", "iphone" }, + { "target-device", "ipad" }, + { "minimum-deployment-target", "8.0" }, + { "platform", options.signingIdentity and "iphoneos" or "iphonesimulator" }, + {"app-icon", "AppIcon"}, + } + + err = CoronaPListSupport.compileXcassets(options, tmpDir, srcAssets, xcassetPlatformOptions, options.settings.iphone) + if err then + return err + end end + + + + -- Process alternate icons (both .icon and existing xcassets) local alternateIcons = options.settings.iphone.alternateIcons if alternateIcons then @@ -1727,50 +1744,39 @@ function iPhonePostPackage( params ) alternateIcons, srcAssets, tmpDir, - platformName, + "ios", debugBuildProcess ) if alternateIconNames and #alternateIconNames > 0 then -- Update the settings with processed alternate icon names options.settings.iphone.alternateIcons = alternateIconNames + options.settings.iphone.xcassets = "TempAssets.xcassets" alternateIconsXCAssets = altXCAssets + local copyCmd = "cp -R -f " .. tempXCAssets .. " " .. quoteString(srcAssets .. "/TempAssets.xcassets") + runScript(copyCmd) end end - -- Compile primary app icon xcassets - local xcassetPlatformOptions = { - { "target-device", "iphone" }, - { "target-device", "ipad" }, - { "minimum-deployment-target", "8.0" }, - { "platform", options.signingIdentity and "iphoneos" or "iphonesimulator" }, - {"app-icon", "AppIcon"}, - } - - err = CoronaPListSupport.compileXcassets(options, tmpDir, xcassetsPath, xcassetPlatformOptions, options.settings.iphone) - if err then - return err - end - -- Compile alternate icons xcassets if we generated any from .icon files if alternateIconsXCAssets then if debugBuildProcess and debugBuildProcess > 0 then print("Compiling alternate icons xcassets...") end - err = CoronaPListSupport.compileXcassets(options, tmpDir, alternateIconsXCAssets, xcassetPlatformOptions, options.settings.iphone) + err = CoronaPListSupport.compileXcassets(options, tmpDir, srcAssets, xcassetPlatformOptions, options.settings.iphone) if err then return err end end -- Clean up temporary xcassets - if iconFile and iconFile:match("%.icon/?$") and xcassetsPath ~= (srcAssets .. "/Assets.xcassets") then - os.execute("rm -rf " .. quoteString(xcassetsPath)) - end - if alternateIconsXCAssets then - os.execute("rm -rf " .. quoteString(alternateIconsXCAssets)) - end + -- if iconFile and iconFile:match("%.icon/?$") and xcassetsPath ~= (srcAssets .. "/TempAssets.xcassets") then + -- os.execute("rm -rf " .. quoteString(xcassetsPath)) + -- end + -- if alternateIconsXCAssets then + -- os.execute("rm -rf " .. quoteString(alternateIconsXCAssets)) + -- end end setStatus("Packaging app") From da35716f9b5cacaa79dff7a0b153e41d198b4327 Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Thu, 23 Oct 2025 12:09:08 -0400 Subject: [PATCH 3/6] Clean Up --- .../resources/CoronaIconComposerSupport.lua | 655 +++++------------- platform/resources/iPhonePackageApp.lua | 74 +- 2 files changed, 218 insertions(+), 511 deletions(-) diff --git a/platform/resources/CoronaIconComposerSupport.lua b/platform/resources/CoronaIconComposerSupport.lua index 26373e02f..02fc0e072 100644 --- a/platform/resources/CoronaIconComposerSupport.lua +++ b/platform/resources/CoronaIconComposerSupport.lua @@ -2,7 +2,7 @@ -- -- CoronaIconComposerSupport.lua -- Handles Icon Composer .icon files for iOS, tvOS, and macOS builds --- iOS now uses actool to compile .icon files with Liquid Glass effects +-- Uses actool to compile .icon files with Liquid Glass effects -- ------------------------------------------------------------------------------ @@ -11,76 +11,81 @@ local json = require('json') local CoronaIconComposerSupport = {} --- Double quote a string escaping backslashes and any double quotes -local function quoteString( str ) +-- Utility: Quote string for shell +local function quoteString(str) str = str:gsub('\\', '\\\\') str = str:gsub('"', '\\"') return "\"" .. str .. "\"" end - --- Find ictool executable (Icon Composer's command-line tool) - for tvOS/macOS fallback +-- Find ictool executable (for legacy/fallback) local function findIconTool() local paths = { - -- Check common Xcode paths (ictool is inside Icon Composer.app) - "/Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool", - "/Applications/Xcode-beta.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool" + "/Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool" } - -- Check if user has multiple Xcodes (Xcode_15.app, Xcode_16.app, etc.) + -- Check multiple Xcode versions for i = 10, 30 do table.insert(paths, "/Applications/Xcode_" .. i .. ".app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool") end for _, path in ipairs(paths) do - local testCmd = "test -x " .. quoteString(path) - if os.execute(testCmd) == 0 then + if os.execute("test -x " .. quoteString(path)) == 0 then return path end end - -- Try to find using mdfind (Spotlight search) - local handle = io.popen("mdfind 'kMDItemFSName == ictool && kMDItemContentType == public.unix-executable' 2>/dev/null | head -1") + -- Try Spotlight search + local handle = io.popen("mdfind 'kMDItemFSName == ictool' 2>/dev/null | head -1") if handle then - local spotlightPath = handle:read("*l") + local path = handle:read("*l") handle:close() - if spotlightPath and spotlightPath ~= "" then - local testCmd = "test -x " .. quoteString(spotlightPath) - if os.execute(testCmd) == 0 then - return spotlightPath - end + if path and path ~= "" and os.execute("test -x " .. quoteString(path)) == 0 then + return path end end return nil end --- Build actool command for compiling .icon file -local function buildActoolCommand(actool, iconFilePath, outputPath, platform, debugBuildProcess) - local plistPath = outputPath .. "/assetcatalog_generated_info.plist" +-- Platform configuration +local function getPlatformConfig(platform) + local configs = { + ios = { + targetDevice = "iphone", + platformName = "iphoneos", + minDeployment = "11.0" + }, + tvos = { + targetDevice = "tv", + platformName = "appletvos", + minDeployment = "11.0" + }, + macos = { + targetDevice = "mac", + platformName = "macosx", + minDeployment = "10.15" + } + } - -- Determine platform-specific parameters - local targetDevice = "iphone" - local platformName = "iphoneos" - local minDeployment = "13.0" + -- Normalize platform names + if platform == "iphone" then platform = "ios" end + if platform == "appletvos" then platform = "tvos" end + if platform == "osx" then platform = "macos" end - if platform == "ios" or platform == "iphone" then - targetDevice = "iphone" - platformName = "iphoneos" - minDeployment = "13.0" - elseif platform == "tvos" or platform == "appletvos" then - targetDevice = "tv" - platformName = "appletvos" - minDeployment = "13.0" - elseif platform == "macos" or platform == "osx" then - targetDevice = "mac" - platformName = "macosx" - minDeployment = "10.15" + return configs[platform] +end + +-- Build actool command +local function buildActoolCommand(iconFilePath, outputPath, platform) + local config = getPlatformConfig(platform) + if not config then + return nil, "Unsupported platform: " .. tostring(platform) end - -- Build actool command - -- actool compiles asset catalogs (.xcassets or .icon) into Assets.car files - local cmd = "xcrun actool " .. quoteString(iconFilePath) .. + local plistPath = outputPath .. "/assetcatalog_info.plist" + + local cmd = "xcrun actool " .. quoteString(iconFilePath) .. " --compile " .. quoteString(outputPath) .. " --output-format human-readable-text" .. " --notices --warnings --errors" .. @@ -88,211 +93,58 @@ local function buildActoolCommand(actool, iconFilePath, outputPath, platform, de " --app-icon AppIcon" .. " --enable-on-demand-resources NO" .. " --development-region en" .. - " --target-device " .. targetDevice .. - " --minimum-deployment-target " .. minDeployment .. - " --platform " .. platformName - - print("actool command: " .. cmd) + " --target-device " .. config.targetDevice .. + " --minimum-deployment-target " .. config.minDeployment .. + " --platform " .. config.platformName return cmd, plistPath end --- Build ictool command for exporting icons (fallback for tvOS/macOS) -local function buildIconToolCommand(ictool, iconFilePath, outputPath, platform, size, appearanceMode, scale) - scale = scale or 1 - local appearance = appearanceMode or "Light" - - local cmd = quoteString(ictool) .. " " .. quoteString(iconFilePath) .. - " --export-preview " .. platform .. " " .. appearance .. - " " .. size .. " " .. size .. " " .. scale .. - " " .. quoteString(outputPath) - - return cmd -end - --- Platform-specific icon specifications (for ictool fallback) -local function getIconSpecsForPlatform(platform) - if platform == "ios" or platform == "iphone" then - return { - platform = "iOS", - sizes = { - {name = "Icon", size = 1024, idiom = "universal", platform = "ios", sizeKey = "1024x1024", scale = nil} - } - } - elseif platform == "tvos" or platform == "appletvos" then - return { - platform = "tvOS", - sizes = { - {name = "App-Icon-Small", size = 400, idiom = "tv", scale = "1x", sizeKey = "400x240"}, - {name = "App-Icon-Small@2x", size = 800, idiom = "tv", scale = "2x", sizeKey = "400x240"}, - {name = "App-Icon", size = 1280, idiom = "tv", scale = "1x", sizeKey = "1280x768"}, - {name = "App-Icon@2x", size = 2560, idiom = "tv", scale = "2x", sizeKey = "1280x768"}, - {name = "Top-Shelf-Image", size = 1920, idiom = "tv", scale = "1x", sizeKey = "1920x720"}, - {name = "Top-Shelf-Image@2x", size = 3840, idiom = "tv", scale = "2x", sizeKey = "1920x720"} - } - } - elseif platform == "macos" or platform == "osx" then - return { - platform = "macOS", - sizes = { - {name = "icon_16x16", size = 16, idiom = "mac", scale = "1x", sizeKey = "16x16"}, - {name = "icon_16x16@2x", size = 32, idiom = "mac", scale = "2x", sizeKey = "16x16"}, - {name = "icon_32x32", size = 32, idiom = "mac", scale = "1x", sizeKey = "32x32"}, - {name = "icon_32x32@2x", size = 64, idiom = "mac", scale = "2x", sizeKey = "32x32"}, - {name = "icon_128x128", size = 128, idiom = "mac", scale = "1x", sizeKey = "128x128"}, - {name = "icon_128x128@2x", size = 256, idiom = "mac", scale = "2x", sizeKey = "128x128"}, - {name = "icon_256x256", size = 256, idiom = "mac", scale = "1x", sizeKey = "256x256"}, - {name = "icon_256x256@2x", size = 512, idiom = "mac", scale = "2x", sizeKey = "256x256"}, - {name = "icon_512x512", size = 512, idiom = "mac", scale = "1x", sizeKey = "512x512"}, - {name = "icon_512x512@2x", size = 1024, idiom = "mac", scale = "2x", sizeKey = "512x512"} - } - } - end - - return nil -end - --- Check if .icon file is a valid bundle +-- Validate .icon file structure local function validateIconFile(iconFilePath) - -- Check if it's a directory local attr = lfs.attributes(iconFilePath) if not attr or attr.mode ~= "directory" then return false, "Not a directory: " .. iconFilePath end -- Check for icon.json (Icon Composer project) - local iconJson = iconFilePath .. "/icon.json" - local hasIconJson = os.execute("test -f " .. quoteString(iconJson)) == 0 - - if hasIconJson then + if os.execute("test -f " .. quoteString(iconFilePath .. "/icon.json")) == 0 then return true, "icon_composer" end - -- Check for Contents.json (Asset Catalog format inside .icon) - local contentsJson = iconFilePath .. "/Contents.json" - local hasContentsJson = os.execute("test -f " .. quoteString(contentsJson)) == 0 - - if hasContentsJson then + -- Check for Contents.json (Asset Catalog) + if os.execute("test -f " .. quoteString(iconFilePath .. "/Contents.json")) == 0 then return true, "asset_catalog" end - -- Check if it contains PNG files directly - local hasPngs = false + -- Check for PNG files for file in lfs.dir(iconFilePath) do if file:match("%.png$") then - hasPngs = true - break + return true, "png_bundle" end end - if hasPngs then - return true, "png_bundle" - end - - return false, "Invalid .icon file structure (no icon.json, Contents.json, or PNG files)" + return false, "Invalid .icon file (missing icon.json, Contents.json, or PNG files)" end --- Copy PNG files directly from .icon bundle to xcassets -local function copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, debugBuildProcess, platform) - local contentsImages = {} - local copiedFiles = {} - - -- Copy all PNG files from .icon directory - for file in lfs.dir(iconFilePath) do - if file:match("%.png$") then - local srcPath = iconFilePath .. "/" .. file - local dstPath = tempAppIconSet .. "/" .. file - - -- Copy the file - local copyCmd = "cp " .. quoteString(srcPath) .. " " .. quoteString(dstPath) - os.execute(copyCmd) - - if debugBuildProcess and debugBuildProcess > 1 then - print(" Copied: " .. file) - end - - table.insert(copiedFiles, file) - end - end - - -- For iOS universal format, look for 1024x1024 icons - if platform == "ios" or platform == "iphone" then - -- Look for light, dark, and tinted variants - local variants = { - {pattern = "1024.*[Ll]ight", appearance = nil, suffix = "Light"}, - {pattern = "1024.*[Dd]ark", appearance = "dark", suffix = "Dark"}, - {pattern = "1024.*[Tt]inted", appearance = "tinted", suffix = "Tinted"} - } - - for _, variant in ipairs(variants) do - local matchedFile = nil - for _, file in ipairs(copiedFiles) do - if file:match(variant.pattern) then - matchedFile = file - break - end - end - - if matchedFile then - local imageEntry = { - filename = matchedFile, - idiom = "universal", - platform = "ios", - size = "1024x1024" - } - - if variant.appearance then - imageEntry.appearances = {{ - appearance = "luminosity", - value = variant.appearance - }} - end - - table.insert(contentsImages, imageEntry) - end - end - else - -- For other platforms, use the old matching logic - for _, spec in ipairs(iconSpecs.sizes) do - local basePattern = spec.size .. "x" .. spec.size - local matchedFile = nil - - for _, file in ipairs(copiedFiles) do - if file:match(basePattern) or file:match(spec.name) then - matchedFile = file - break - end - end - - if matchedFile then - local imageEntry = { - filename = matchedFile, - idiom = spec.idiom, - scale = spec.scale, - size = spec.sizeKey - } - table.insert(contentsImages, imageEntry) - end - end - end - - return contentsImages, #copiedFiles -end - --- Compile .icon file using actool (for iOS with Liquid Glass effects) -local function compileIconWithActool(actool, iconFilePath, tmpDir, platform, debugBuildProcess) - +-- Compile .icon file with actool +local function compileIconWithActool(iconFilePath, tmpDir, platform, debugBuildProcess) + if debugBuildProcess and debugBuildProcess > 0 then print("Using actool to compile .icon file with Liquid Glass effects...") + end - -- Create output directory for compiled assets - local outputPath = tmpDir .. "/CompiledAssets" + -- Create output directory + local outputPath = tmpDir .. "/CompiledAssets_" .. os.time() os.execute("mkdir -p " .. quoteString(outputPath)) -- Build and execute actool command - local actoolCmd, plistPath = buildActoolCommand(actool, iconFilePath, outputPath, platform, debugBuildProcess) + local actoolCmd, plistPath = buildActoolCommand(iconFilePath, outputPath, platform) + if not actoolCmd then + return nil, plistPath -- plistPath contains error message + end if debugBuildProcess and debugBuildProcess > 1 then - print("Executing actool...") + print("actool command: " .. actoolCmd) end local result = os.execute(actoolCmd .. " 2>&1") @@ -300,26 +152,45 @@ local function compileIconWithActool(actool, iconFilePath, tmpDir, platform, deb -- Check if Assets.car was created local assetsCarPath = outputPath .. "/Assets.car" local testFile = io.open(assetsCarPath, "r") + if testFile then testFile:close() - - print("✓ Successfully compiled Assets.car with actool") + if debugBuildProcess and debugBuildProcess > 0 then + print("✓ Successfully compiled Assets.car") print(" Location: " .. assetsCarPath) + end - -- Clean up the generated plist file + -- Clean up plist os.execute("rm -f " .. quoteString(plistPath)) return outputPath, assetsCarPath else - return nil, "actool failed to compile .icon file. Check that the .icon file is valid." + return nil, "actool failed to compile .icon file" + end +end + +-- Copy asset catalog structure +local function copyAssetCatalog(iconFilePath, tmpDir, debugBuildProcess) + local tempXCAssets = tmpDir .. "/TempAssets.xcassets" + local tempAppIconSet = tempXCAssets .. "/AppIcon.appiconset" + + os.execute("mkdir -p " .. quoteString(tempAppIconSet)) + + local copyCmd = "cp -R " .. quoteString(iconFilePath) .. "/* " .. quoteString(tempAppIconSet) .. "/" + os.execute(copyCmd) + + if debugBuildProcess and debugBuildProcess > 0 then + print("Copied asset catalog structure") end + + return tempXCAssets, nil end --- Export icons from .icon file using ictool (fallback for tvOS/macOS) -local function exportIconWithIctool(ictool, iconFilePath, tmpDir, platform, iconSpecs, debugBuildProcess) +-- Process PNG bundle (simple PNG files) +local function processPngBundle(iconFilePath, tmpDir, platform, debugBuildProcess) if debugBuildProcess and debugBuildProcess > 0 then - print("Using ictool to export icon layers...") + print("Processing PNG bundle...") end local tempXCAssets = tmpDir .. "/TempAssets.xcassets" @@ -327,88 +198,61 @@ local function exportIconWithIctool(ictool, iconFilePath, tmpDir, platform, icon os.execute("mkdir -p " .. quoteString(tempAppIconSet)) - -- Appearance modes to export - local appearances = { - {mode = "Light", suffix = "-Light", jsonAppearance = nil}, - {mode = "Dark", suffix = "-Dark", jsonAppearance = "dark"} - } + -- Copy all PNG files + local copiedFiles = {} + for file in lfs.dir(iconFilePath) do + if file:match("%.png$") then + local srcPath = iconFilePath .. "/" .. file + local dstPath = tempAppIconSet .. "/" .. file + os.execute("cp " .. quoteString(srcPath) .. " " .. quoteString(dstPath)) + table.insert(copiedFiles, file) + end + end - -- Add tinted appearance for iOS (though we prefer actool for iOS) - if platform == "ios" or platform == "iphone" then - table.insert(appearances, {mode = "Tinted", suffix = "-Tinted", jsonAppearance = "tinted"}) + if #copiedFiles == 0 then + return nil, "No PNG files found in bundle" end + -- Generate Contents.json for iOS universal format local contentsImages = {} - local successCount = 0 - local failCount = 0 - - -- Export each icon size and appearance using ictool - for _, spec in ipairs(iconSpecs.sizes) do - for _, appearance in ipairs(appearances) do - local filename = spec.name .. appearance.suffix .. "-" .. spec.sizeKey .. ".png" - local outputPath = tempAppIconSet .. "/" .. filename - - -- Build ictool command - local ictoolCmd = buildIconToolCommand(ictool, iconFilePath, outputPath, iconSpecs.platform, spec.size, appearance.mode, 1) - - if debugBuildProcess and debugBuildProcess > 1 then - print(" Exporting: " .. filename .. " (" .. spec.size .. "x" .. spec.size .. ", " .. appearance.mode .. ")") - end - - os.execute(ictoolCmd .. " 2>&1") - - -- Check if file was created - local testFile = io.open(outputPath, "r") - if testFile then - testFile:close() - successCount = successCount + 1 - - -- Build Contents.json entry - local imageEntry = { - filename = filename, - idiom = spec.idiom, - size = spec.sizeKey - } - - -- Add platform for iOS universal format - if platform == "ios" or platform == "iphone" then - imageEntry.platform = "ios" - end - - -- Add scale for non-iOS platforms - if spec.scale then - imageEntry.scale = spec.scale - end - - -- Add appearance info for dark and tinted modes - if appearance.jsonAppearance then - imageEntry.appearances = {{ - appearance = "luminosity", - value = appearance.jsonAppearance - }} - end - - table.insert(contentsImages, imageEntry) - else - failCount = failCount + 1 - if debugBuildProcess and debugBuildProcess > 1 then - print(" WARNING: Failed to export " .. filename) + + if platform == "ios" or platform == "iphone" then + local variants = { + {pattern = "1024.*[Ll]ight", appearance = nil}, + {pattern = "1024.*[Dd]ark", appearance = "dark"}, + {pattern = "1024.*[Tt]inted", appearance = "tinted"} + } + + for _, variant in ipairs(variants) do + for _, file in ipairs(copiedFiles) do + if file:match(variant.pattern) then + local entry = { + filename = file, + idiom = "universal", + platform = "ios", + size = "1024x1024" + } + + if variant.appearance then + entry.appearances = {{ + appearance = "luminosity", + value = variant.appearance + }} + end + + table.insert(contentsImages, entry) + break end end end end - if successCount == 0 then - return nil, "Failed to export any icons with ictool" - end - - -- Generate Contents.json + -- Write Contents.json local contentsFile = io.open(tempAppIconSet .. "/Contents.json", "w") if not contentsFile then return nil, "Failed to create Contents.json" end - -- Write Contents.json contentsFile:write('{\n "images" : [\n') for i, img in ipairs(contentsImages) do contentsFile:write(' {\n') @@ -423,18 +267,10 @@ local function exportIconWithIctool(ictool, iconFilePath, tmpDir, platform, icon end contentsFile:write(' "filename" : "' .. img.filename .. '",\n') - contentsFile:write(' "idiom" : "' .. img.idiom .. '"') - - if img.platform then - contentsFile:write(',\n "platform" : "' .. img.platform .. '"') - end - - if img.scale then - contentsFile:write(',\n "scale" : "' .. img.scale .. '"') - end - - contentsFile:write(',\n "size" : "' .. img.size .. '"') - contentsFile:write('\n }') + contentsFile:write(' "idiom" : "' .. img.idiom .. '",\n') + contentsFile:write(' "platform" : "' .. img.platform .. '",\n') + contentsFile:write(' "size" : "' .. img.size .. '"\n') + contentsFile:write(' }') if i < #contentsImages then contentsFile:write(',') @@ -450,22 +286,18 @@ local function exportIconWithIctool(ictool, iconFilePath, tmpDir, platform, icon contentsFile:close() if debugBuildProcess and debugBuildProcess > 0 then - print("Successfully exported " .. successCount .. " icon variants") - if failCount > 0 then - print("WARNING: " .. failCount .. " icon exports failed") - end + print("Successfully processed " .. #copiedFiles .. " PNG files") end return tempXCAssets, nil end --- Main function: Convert .icon file to xcassets or Assets.car +-- Main: Convert .icon file to xcassets or Assets.car function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDir, platform, debugBuildProcess) - -- Validate .icon file and determine its type + -- Validate icon file local isValid, iconType = validateIconFile(iconFilePath) - if not isValid then - return nil, iconType -- iconType contains error message + return nil, iconType end if debugBuildProcess and debugBuildProcess > 0 then @@ -477,169 +309,28 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi print("========================================") end - -- Get platform-specific icon sizes - local iconSpecs = getIconSpecsForPlatform(platform) - if not iconSpecs then - return nil, "Unsupported platform for Icon Composer: " .. tostring(platform) - end - - -- Create temporary xcassets structure - local tempXCAssets = tmpDir .. "/TempAssets.xcassets" - local tempAppIconSet = tempXCAssets .. "/AppIcon.appiconset" - - os.execute("mkdir -p " .. quoteString(tempAppIconSet)) - - local contentsImages = {} - local successCount = 0 - - -- Handle different .icon file types - if iconType == "png_bundle" then - -- Simple PNG bundle - just copy files - if debugBuildProcess and debugBuildProcess > 0 then - print("Processing PNG bundle...") - end - - contentsImages, successCount = copyPngBundleToXCAssets(iconFilePath, tempAppIconSet, iconSpecs, debugBuildProcess, platform) - + -- Route to appropriate handler + if iconType == "icon_composer" then + return compileIconWithActool(iconFilePath, tmpDir, platform, debugBuildProcess) elseif iconType == "asset_catalog" then - -- Asset catalog inside .icon - copy the whole structure - if debugBuildProcess and debugBuildProcess > 0 then - print("Processing asset catalog...") - end - - local copyCmd = "cp -R " .. quoteString(iconFilePath) .. "/* " .. quoteString(tempAppIconSet) .. "/" - os.execute(copyCmd) - - if debugBuildProcess and debugBuildProcess > 0 then - print("Copied asset catalog structure") - end - - return tempXCAssets, nil - - else -- iconType == "icon_composer" - -- Icon Composer project - use actool for iOS, ictool for others - - local outputPath, assetsCarPath = compileIconWithActool("xcrun actool", iconFilePath, tmpDir, platform, debugBuildProcess) - - if outputPath then - print("Generated compiled assets with Liquid Glass effects") - print("========================================") - return outputPath, assetsCarPath - else - -- Fall back to ictool if actool fails - if debugBuildProcess and debugBuildProcess > 0 then - print("WARNING: actool compilation failed, falling back to ictool") - end - end - - - end - - if successCount == 0 then - return nil, "Failed to export any icons from " .. iconFilePath .. ". Check that the .icon file is valid." - end - - -- Generate Contents.json for PNG bundle - local contentsFile = io.open(tempAppIconSet .. "/Contents.json", "w") - if not contentsFile then - return nil, "Failed to create Contents.json" - end - - contentsFile:write('{\n "images" : [\n') - for i, img in ipairs(contentsImages) do - contentsFile:write(' {\n') - - if img.appearances then - contentsFile:write(' "appearances" : [\n') - contentsFile:write(' {\n') - contentsFile:write(' "appearance" : "luminosity",\n') - contentsFile:write(' "value" : "' .. img.appearances[1].value .. '"\n') - contentsFile:write(' }\n') - contentsFile:write(' ],\n') - end - - contentsFile:write(' "filename" : "' .. img.filename .. '",\n') - contentsFile:write(' "idiom" : "' .. img.idiom .. '"') - - if img.platform then - contentsFile:write(',\n "platform" : "' .. img.platform .. '"') - end - - if img.scale then - contentsFile:write(',\n "scale" : "' .. img.scale .. '"') - end - - contentsFile:write(',\n "size" : "' .. img.size .. '"') - contentsFile:write('\n }') - - if i < #contentsImages then - contentsFile:write(',') - end - contentsFile:write('\n') - end - contentsFile:write(' ],\n') - contentsFile:write(' "info" : {\n') - contentsFile:write(' "author" : "xcode",\n') - contentsFile:write(' "version" : 1\n') - contentsFile:write(' }\n') - contentsFile:write('}\n') - contentsFile:close() - - if debugBuildProcess and debugBuildProcess > 0 then - print("Successfully processed " .. successCount .. " icon variants") - print("Generated xcassets: " .. tempXCAssets) - print("========================================") + return copyAssetCatalog(iconFilePath, tmpDir, debugBuildProcess) + elseif iconType == "png_bundle" then + return processPngBundle(iconFilePath, tmpDir, platform, debugBuildProcess) end - return tempXCAssets, nil + return nil, "Unknown icon type: " .. iconType end --- Export alternate icon from .icon file +-- Convert alternate icon function CoronaIconComposerSupport.convertAlternateIconToXCAssets(iconFilePath, iconName, tmpDir, platform, debugBuildProcess) - -- For iOS, try actool first - if platform == "ios" or platform == "iphone" then - local actool = findActool() - - if actool then - print("Converting alternate icon: " .. iconName .. " with actool") - - local outputPath, assetsCarPath = compileIconWithActool(actool, iconFilePath, tmpDir, platform, debugBuildProcess) - - if outputPath then - return outputPath, assetsCarPath - end - end - end - - -- Fallback to ictool - local ictool = findIconTool() - - if not ictool then - return nil, "Neither actool nor ictool found. Xcode must be installed." - end - - -- Verify .icon file exists - local iconJson = iconFilePath .. "/icon.json" - local hasIconJson = os.execute("test -f " .. quoteString(iconJson)) == 0 - - if not hasIconJson then - return nil, "Not a valid Icon Composer .icon file: " .. iconFilePath - end - if debugBuildProcess and debugBuildProcess > 0 then - print("Converting alternate icon: " .. iconName .. " from " .. iconFilePath) - end - - -- Get platform-specific icon sizes - local iconSpecs = getIconSpecsForPlatform(platform) - if not iconSpecs then - return nil, "Unsupported platform: " .. tostring(platform) + print("Converting alternate icon: " .. iconName) end - return exportIconWithIctool(ictool, iconFilePath, tmpDir, platform, iconSpecs, debugBuildProcess) + return CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDir, platform, debugBuildProcess) end --- Process all alternate icons from build.settings +-- Process all alternate icons function CoronaIconComposerSupport.processAlternateIcons(alternateIconsConfig, srcAssets, tmpDir, platform, debugBuildProcess) if not alternateIconsConfig or type(alternateIconsConfig) ~= "table" then return nil, nil @@ -652,24 +343,18 @@ function CoronaIconComposerSupport.processAlternateIcons(alternateIconsConfig, s end local alternateIconNames = {} - local tempXCAssets = tmpDir .. "/AlternateIcons.xcassets" - local hasAlternateIcons = false + local alternateIconAssets = {} for iconName, iconConfig in pairs(alternateIconsConfig) do - local iconFilePath = nil - if type(iconConfig) == "string" then - -- Old format: alternateIcons = { "Icon1", "Icon2" } + -- Legacy format: just icon name table.insert(alternateIconNames, iconConfig) - if debugBuildProcess and debugBuildProcess > 0 then - print("Alternate icon (xcassets): " .. iconConfig) - end elseif type(iconConfig) == "table" and iconConfig.iconFile then - -- New format: alternateIcons = { DarkIcon = { iconFile = "DarkIcon.icon" } } - iconFilePath = srcAssets .. "/" .. iconConfig.iconFile + -- New format: .icon file reference + local iconFilePath = srcAssets .. "/" .. iconConfig.iconFile - if iconFilePath:match("%.icon/?$") and lfs.attributes(iconFilePath, "mode") == "directory" then - local altXCAssets, errMsg = CoronaIconComposerSupport.convertAlternateIconToXCAssets( + if lfs.attributes(iconFilePath, "mode") == "directory" then + local outputPath, assetsCarPath = CoronaIconComposerSupport.convertAlternateIconToXCAssets( iconFilePath, iconName, tmpDir, @@ -677,33 +362,31 @@ function CoronaIconComposerSupport.processAlternateIcons(alternateIconsConfig, s debugBuildProcess ) - if altXCAssets then - hasAlternateIcons = true + if outputPath then table.insert(alternateIconNames, iconName) + alternateIconAssets[iconName] = { + outputPath = outputPath, + assetsCarPath = assetsCarPath + } + if debugBuildProcess and debugBuildProcess > 0 then - print("Converted alternate icon: " .. iconName .. " from " .. iconConfig.iconFile) + print("✓ Converted: " .. iconName) end else - print("WARNING: Failed to convert alternate icon: " .. tostring(errMsg)) + print("WARNING: Failed to convert " .. iconName .. ": " .. tostring(assetsCarPath)) end else - print("WARNING: Alternate icon file not found: " .. iconFilePath) + print("WARNING: Icon file not found: " .. iconFilePath) end end end - if #alternateIconNames == 0 then - return nil, nil - end - if debugBuildProcess and debugBuildProcess > 0 then print("Total alternate icons: " .. #alternateIconNames) print("========================================") end - return alternateIconNames, (hasAlternateIcons and tempXCAssets or nil) + return alternateIconNames, alternateIconAssets end - - return CoronaIconComposerSupport \ No newline at end of file diff --git a/platform/resources/iPhonePackageApp.lua b/platform/resources/iPhonePackageApp.lua index 88f08e8d3..bc16f7b15 100644 --- a/platform/resources/iPhonePackageApp.lua +++ b/platform/resources/iPhonePackageApp.lua @@ -1749,34 +1749,58 @@ function iPhonePostPackage( params ) ) if alternateIconNames and #alternateIconNames > 0 then - -- Update the settings with processed alternate icon names - options.settings.iphone.alternateIcons = alternateIconNames - options.settings.iphone.xcassets = "TempAssets.xcassets" - alternateIconsXCAssets = altXCAssets - local copyCmd = "cp -R -f " .. tempXCAssets .. " " .. quoteString(srcAssets .. "/TempAssets.xcassets") - runScript(copyCmd) - end - end - - -- Compile alternate icons xcassets if we generated any from .icon files - if alternateIconsXCAssets then - if debugBuildProcess and debugBuildProcess > 0 then - print("Compiling alternate icons xcassets...") - end - - err = CoronaPListSupport.compileXcassets(options, tmpDir, srcAssets, xcassetPlatformOptions, options.settings.iphone) - if err then - return err + local copyCmd = "cp -R -f " .. outputPath .. "/* " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app") + runScript(copyCmd) + + if not options.settings.iphone.plist.CFBundleIcons then + options.settings.iphone.plist.CFBundleIcons = {} + end + + -- add alternate icons to CFBundleIcons + if not options.settings.iphone.plist.CFBundleIcons then + options.settings.iphone.plist.CFBundleIcons = {} + end + + -- Add alternate icons to CFBundleIcons + if not options.settings.iphone.plist.CFBundleIcons.CFBundleAlternateIcons then + options.settings.iphone.plist.CFBundleIcons.CFBundleAlternateIcons = {} + end + + -- Build alternate icons structure + for _, iconName in ipairs(alternateIconNames) do + options.settings.iphone.plist.CFBundleIcons.CFBundleAlternateIcons[iconName] = { + CFBundleIconFiles = { + iconName + }, + UIPrerenderedIcon = false + } + + if debugBuildProcess > 0 then + print("Added alternate icon to plist: " .. iconName) + end + end + + -- Also add for iPad + if not options.settings.iphone.plist["CFBundleIcons~ipad"] then + options.settings.iphone.plist["CFBundleIcons~ipad"] = {} + end + + if not options.settings.iphone.plist["CFBundleIcons~ipad"].CFBundleAlternateIcons then + options.settings.iphone.plist["CFBundleIcons~ipad"].CFBundleAlternateIcons = {} + end + + -- Add same alternate icons for iPad + for _, iconName in ipairs(alternateIconNames) do + options.settings.iphone.plist["CFBundleIcons~ipad"].CFBundleAlternateIcons[iconName] = { + CFBundleIconFiles = { + iconName + }, + UIPrerenderedIcon = false + } + end end end - -- Clean up temporary xcassets - -- if iconFile and iconFile:match("%.icon/?$") and xcassetsPath ~= (srcAssets .. "/TempAssets.xcassets") then - -- os.execute("rm -rf " .. quoteString(xcassetsPath)) - -- end - -- if alternateIconsXCAssets then - -- os.execute("rm -rf " .. quoteString(alternateIconsXCAssets)) - -- end end setStatus("Packaging app") From 2573144608210159ef744156c0d802c2ad8e7e63 Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Thu, 23 Oct 2025 13:25:41 -0400 Subject: [PATCH 4/6] Save Point --- .../resources/CoronaIconComposerSupport.lua | 8 +++-- platform/resources/iPhonePackageApp.lua | 34 ++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/platform/resources/CoronaIconComposerSupport.lua b/platform/resources/CoronaIconComposerSupport.lua index 02fc0e072..48e081f30 100644 --- a/platform/resources/CoronaIconComposerSupport.lua +++ b/platform/resources/CoronaIconComposerSupport.lua @@ -84,13 +84,14 @@ local function buildActoolCommand(iconFilePath, outputPath, platform) end local plistPath = outputPath .. "/assetcatalog_info.plist" - + local iconBaseName = iconFilePath:match("([^/]+)%.icon/?$") or "AppIcon" + local cmd = "xcrun actool " .. quoteString(iconFilePath) .. " --compile " .. quoteString(outputPath) .. " --output-format human-readable-text" .. " --notices --warnings --errors" .. " --output-partial-info-plist " .. quoteString(plistPath) .. - " --app-icon AppIcon" .. + " --app-icon " .. iconBaseName .. " --enable-on-demand-resources NO" .. " --development-region en" .. " --target-device " .. config.targetDevice .. @@ -150,6 +151,7 @@ local function compileIconWithActool(iconFilePath, tmpDir, platform, debugBuildP local result = os.execute(actoolCmd .. " 2>&1") -- Check if Assets.car was created + local iconBaseName = iconFilePath:match("([^/]+)%.icon/?$") or "AppIcon" local assetsCarPath = outputPath .. "/Assets.car" local testFile = io.open(assetsCarPath, "r") @@ -311,6 +313,8 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi -- Route to appropriate handler if iconType == "icon_composer" then + print("Compiling .icon file with actool...") + print(iconFilePath) return compileIconWithActool(iconFilePath, tmpDir, platform, debugBuildProcess) elseif iconType == "asset_catalog" then return copyAssetCatalog(iconFilePath, tmpDir, debugBuildProcess) diff --git a/platform/resources/iPhonePackageApp.lua b/platform/resources/iPhonePackageApp.lua index bc16f7b15..cdf8654e4 100644 --- a/platform/resources/iPhonePackageApp.lua +++ b/platform/resources/iPhonePackageApp.lua @@ -1702,11 +1702,12 @@ function iPhonePostPackage( params ) ) if outputPath then - print("Converted .icon file to xcassets at: " .. outputPath) local copyCmd = "cp -R -f " .. outputPath .. "/* " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app") - runScript(copyCmd) + local renameCmd = "mv -f " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app" .. "/Assets.car") .. " " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app" .. "/MainAppAsset.car") + runScript(renameCmd) + options.settings.iphone.plist.CFBundleIcons = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60"}, CFBundleIconName = "AppIcon" }} options.settings.iphone.plist["CFBundleIcons~ipad"] = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60", "AppIcon76x76"}, CFBundleIconName = "AppIcon" }} options.settings.iphone.plist.UIPrerenderedIcon = true @@ -1747,16 +1748,25 @@ function iPhonePostPackage( params ) "ios", debugBuildProcess ) - - if alternateIconNames and #alternateIconNames > 0 then - local copyCmd = "cp -R -f " .. outputPath .. "/* " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app") - runScript(copyCmd) - if not options.settings.iphone.plist.CFBundleIcons then - options.settings.iphone.plist.CFBundleIcons = {} + -- copy alternate icon xcassets into app bundle + + for iconName, data in pairs(altXCAssets) do + if data and data.outputPath then + local srcPath = data.outputPath + + -- safely handle spaces and copy all contents + local copyCmd = 'cp -R -f ' .. quoteString(srcPath) .. '/* ' .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app") + print("Copying alternate icon xcassets for " .. iconName .. " from: " .. srcPath) + runScript(copyCmd) + local renameCmd = "mv -f " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app" .. "/Assets.car") .. " " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app" .. "/"..iconName.."AltAsset.car") + runScript(renameCmd) + else + print("WARNING: missing outputPath for alternate icon " .. tostring(iconName)) end - - -- add alternate icons to CFBundleIcons + end + if alternateIconNames and #alternateIconNames > 0 then + if not options.settings.iphone.plist.CFBundleIcons then options.settings.iphone.plist.CFBundleIcons = {} end @@ -1770,7 +1780,7 @@ function iPhonePostPackage( params ) for _, iconName in ipairs(alternateIconNames) do options.settings.iphone.plist.CFBundleIcons.CFBundleAlternateIcons[iconName] = { CFBundleIconFiles = { - iconName + iconName.."60x60" }, UIPrerenderedIcon = false } @@ -1793,7 +1803,7 @@ function iPhonePostPackage( params ) for _, iconName in ipairs(alternateIconNames) do options.settings.iphone.plist["CFBundleIcons~ipad"].CFBundleAlternateIcons[iconName] = { CFBundleIconFiles = { - iconName + iconName.."60x60", iconName.."76x76" }, UIPrerenderedIcon = false } From f52b0099e2fbe48614e460fb36d1e613d5fa8135 Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Thu, 23 Oct 2025 14:16:04 -0400 Subject: [PATCH 5/6] Fixes --- platform/resources/iPhonePackageApp.lua | 85 +------------------------ 1 file changed, 3 insertions(+), 82 deletions(-) diff --git a/platform/resources/iPhonePackageApp.lua b/platform/resources/iPhonePackageApp.lua index cdf8654e4..b38da4c34 100644 --- a/platform/resources/iPhonePackageApp.lua +++ b/platform/resources/iPhonePackageApp.lua @@ -1688,7 +1688,7 @@ function iPhonePostPackage( params ) local xcassetsPath = srcAssets .. "/Assets.xcassets" local alternateIconsXCAssets = nil - + -- Process primary app icon if iconFile and iconFile:match("%.icon/?$") then local iconPath = srcAssets .. "/" .. iconFile @@ -1705,11 +1705,8 @@ function iPhonePostPackage( params ) local copyCmd = "cp -R -f " .. outputPath .. "/* " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app") runScript(copyCmd) - local renameCmd = "mv -f " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app" .. "/Assets.car") .. " " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app" .. "/MainAppAsset.car") - runScript(renameCmd) - - options.settings.iphone.plist.CFBundleIcons = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60"}, CFBundleIconName = "AppIcon" }} - options.settings.iphone.plist["CFBundleIcons~ipad"] = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60", "AppIcon76x76"}, CFBundleIconName = "AppIcon" }} + options.settings.iphone.plist.CFBundleIcons = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60"}, CFBundleIconName = "AppIcon", CFBundleIconLiquidGlass = true }} + options.settings.iphone.plist["CFBundleIcons~ipad"] = { CFBundlePrimaryIcon = { CFBundleIconFiles = {"AppIcon60x60", "AppIcon76x76"}, CFBundleIconName = "AppIcon", CFBundleIconLiquidGlass = true }} options.settings.iphone.plist.UIPrerenderedIcon = true else print("ERROR: Failed to convert .icon file: " .. tostring(errMsg)) @@ -1735,82 +1732,6 @@ function iPhonePostPackage( params ) end - - - - -- Process alternate icons (both .icon and existing xcassets) - local alternateIcons = options.settings.iphone.alternateIcons - if alternateIcons then - local alternateIconNames, altXCAssets = CoronaIconComposerSupport.processAlternateIcons( - alternateIcons, - srcAssets, - tmpDir, - "ios", - debugBuildProcess - ) - - -- copy alternate icon xcassets into app bundle - - for iconName, data in pairs(altXCAssets) do - if data and data.outputPath then - local srcPath = data.outputPath - - -- safely handle spaces and copy all contents - local copyCmd = 'cp -R -f ' .. quoteString(srcPath) .. '/* ' .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app") - print("Copying alternate icon xcassets for " .. iconName .. " from: " .. srcPath) - runScript(copyCmd) - local renameCmd = "mv -f " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app" .. "/Assets.car") .. " " .. quoteString(options.dstDir .. '/' .. options.dstFile .. ".app" .. "/"..iconName.."AltAsset.car") - runScript(renameCmd) - else - print("WARNING: missing outputPath for alternate icon " .. tostring(iconName)) - end - end - if alternateIconNames and #alternateIconNames > 0 then - - if not options.settings.iphone.plist.CFBundleIcons then - options.settings.iphone.plist.CFBundleIcons = {} - end - - -- Add alternate icons to CFBundleIcons - if not options.settings.iphone.plist.CFBundleIcons.CFBundleAlternateIcons then - options.settings.iphone.plist.CFBundleIcons.CFBundleAlternateIcons = {} - end - - -- Build alternate icons structure - for _, iconName in ipairs(alternateIconNames) do - options.settings.iphone.plist.CFBundleIcons.CFBundleAlternateIcons[iconName] = { - CFBundleIconFiles = { - iconName.."60x60" - }, - UIPrerenderedIcon = false - } - - if debugBuildProcess > 0 then - print("Added alternate icon to plist: " .. iconName) - end - end - - -- Also add for iPad - if not options.settings.iphone.plist["CFBundleIcons~ipad"] then - options.settings.iphone.plist["CFBundleIcons~ipad"] = {} - end - - if not options.settings.iphone.plist["CFBundleIcons~ipad"].CFBundleAlternateIcons then - options.settings.iphone.plist["CFBundleIcons~ipad"].CFBundleAlternateIcons = {} - end - - -- Add same alternate icons for iPad - for _, iconName in ipairs(alternateIconNames) do - options.settings.iphone.plist["CFBundleIcons~ipad"].CFBundleAlternateIcons[iconName] = { - CFBundleIconFiles = { - iconName.."60x60", iconName.."76x76" - }, - UIPrerenderedIcon = false - } - end - end - end - end setStatus("Packaging app") From a874d0c1550140075851fccd760f5e222404d714 Mon Sep 17 00:00:00 2001 From: Scott Harrison Date: Thu, 23 Oct 2025 14:16:50 -0400 Subject: [PATCH 6/6] Clean Up --- platform/resources/CoronaIconComposerSupport.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/platform/resources/CoronaIconComposerSupport.lua b/platform/resources/CoronaIconComposerSupport.lua index 48e081f30..7bcbc363d 100644 --- a/platform/resources/CoronaIconComposerSupport.lua +++ b/platform/resources/CoronaIconComposerSupport.lua @@ -313,8 +313,6 @@ function CoronaIconComposerSupport.convertIconFileToXCAssets(iconFilePath, tmpDi -- Route to appropriate handler if iconType == "icon_composer" then - print("Compiling .icon file with actool...") - print(iconFilePath) return compileIconWithActool(iconFilePath, tmpDir, platform, debugBuildProcess) elseif iconType == "asset_catalog" then return copyAssetCatalog(iconFilePath, tmpDir, debugBuildProcess)