From 3b9ba6d65e9054c89ff607127cf05ead267f2057 Mon Sep 17 00:00:00 2001 From: Joe Page Date: Tue, 11 Aug 2026 10:15:06 -0400 Subject: [PATCH] =?UTF-8?q?-=20fix=20mac=20task=20icon=20creation:=20=20?= =?UTF-8?q?=20-=20The=20diff=20touches=20**only**=20`MacBundler.java`=20(4?= =?UTF-8?q?3+/16=E2=88=92,=20one=20file).=20=20=20-=20Windows=20has=20a=20?= =?UTF-8?q?completely=20separate=20path:=20`InstallWindows.java:193`=20rea?= =?UTF-8?q?ds=20`icon.png`=20from=20the=20bundle=20dir=20and=20encodes=20a?= =?UTF-8?q?=20`.ico`=20via=20image4j's=20`ICOEncoder`.=20Linux=20just=20po?= =?UTF-8?q?ints=20`Icon=3D{{APP=5FICON}}`=20in=20the=20`.desktop`=20file?= =?UTF-8?q?=20at=20`icon.png`.=20Neither=20goes=20near=20`MacBundler`.=20?= =?UTF-8?q?=20=20-=20Both=20new=20file=20operations=20(`createThumbnails`,?= =?UTF-8?q?=20`cleanThumbnails`)=20work=20strictly=20inside=20the=20mac=20?= =?UTF-8?q?bundle's=20`Contents/`,=20which=20no=20other=20platform=20reads?= =?UTF-8?q?.=20The=20thumbnails=20are=20files=20`MacBundler`=20itself=20ju?= =?UTF-8?q?st=20created,=20and=20`iconFile.delete()`=20was=20already=20the?= =?UTF-8?q?re.=20=20=20-=20The=20change=20uses=20only=20`Thumbnails`=20(Th?= =?UTF-8?q?umbnailator)=20and=20`IcnsType`/`IcnsBuilder`=20=E2=80=94=20pur?= =?UTF-8?q?e=20Java,=20no=20`iconutil`=20or=20other=20macOS=20tooling.=20S?= =?UTF-8?q?o=20mac=20bundles=20still=20build=20correctly=20from=20Linux/Wi?= =?UTF-8?q?ndows=20CI.=20(`iconutil`=20appeared=20only=20in=20my=20ADM=20s?= =?UTF-8?q?cript=20and=20my=20verification,=20never=20in=20the=20jdeploy?= =?UTF-8?q?=20patch.)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appbundler/mac/MacBundler.java | 59 ++++++++++++++----- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/shared/src/main/java/com/joshondesign/appbundler/mac/MacBundler.java b/shared/src/main/java/com/joshondesign/appbundler/mac/MacBundler.java index 81fef4ad..896554a6 100644 --- a/shared/src/main/java/com/joshondesign/appbundler/mac/MacBundler.java +++ b/shared/src/main/java/com/joshondesign/appbundler/mac/MacBundler.java @@ -575,11 +575,34 @@ private static void createThumbnail(File f, File contentsDir, int size) throws I .toFile(new File(contentsDir, "icon-"+size+".png")); } - private static void createThumbnails(File f, File contentsDir) throws IOException { - for (int size : new int[]{16, 32, 64, 128, 256, 512, 1024}) { - createThumbnail(f, contentsDir, size); + /** icns slice types each rendered size fills; the @2x types let one file serve two entries */ + private static final int[] ICON_SIZES = {16, 32, 64, 128, 256, 512, 1024}; + private static final IcnsType[][] ICON_TYPES = { + {IcnsType.ICNS_16x16_JPEG_PNG_IMAGE}, + {IcnsType.ICNS_32x32_JPEG_PNG_IMAGE, IcnsType.ICNS_16x16_2X_JPEG_PNG_IMAGE}, + // no icp6: macOS reads that type as 48x48, and its icon set has no 64pt logical size + {IcnsType.ICNS_32x32_2X_JPEG_PNG_IMAGE}, + {IcnsType.ICNS_128x128_JPEG_PNG_IMAGE}, + {IcnsType.ICNS_256x256_JPEG_PNG_IMAGE, IcnsType.ICNS_128x128_2X_JPEG_PNG_IMAGE}, + {IcnsType.ICNS_512x512_JPEG_PNG_IMAGE, IcnsType.ICNS_256x256_2X_JPEG_PNG_IMAGE}, + {IcnsType.ICNS_1024x1024_2X_JPEG_PNG_IMAGE}, + }; + + private static void createThumbnails(File f, File contentsDir, int maxSize) throws IOException { + for (int size : ICON_SIZES) { + if (size <= maxSize) createThumbnail(f, contentsDir, size); } } + + /** rendered size the given slice type holds, or 0 if it isn't one we generate */ + private static int getIconSize(String osType) { + for (int i = 0; i < ICON_SIZES.length; i++) { + for (IcnsType type : ICON_TYPES[i]) { + if (type.getOsType().equals(osType)) return ICON_SIZES[i]; + } + } + return 0; + } private static FileInputStream getThumbnail(File contentsDir, int size) throws IOException { return new FileInputStream(new File(contentsDir, "icon-"+size+".png")); @@ -737,28 +760,32 @@ private static void processIcon(AppDescription app, File contentsDir, String ext } - //createThumbnails(iconFile, contentsDir); + // macOS 26 draws a single-slice icns shrunk onto a grey plate, so write the whole size family + int maxSize = getIconSize(osType); + if (maxSize > 0) createThumbnails(iconFile, contentsDir, maxSize); try (IcnsBuilder builder = IcnsBuilder.getInstance()) { - - //builder.add("icp4", getThumbnail(contentsDir, 16)); - //builder.add(IcnsType.ICNS_16x16_2X_JPEG_PNG_IMAGE, getThumbnail(contentsDir, 32)); - //builder.add("icp5", getThumbnail(contentsDir, 32)); - //builder.add("icp6", getThumbnail(contentsDir, 64)); - //builder.add("ic07", getThumbnail(contentsDir, 128)); - //builder.add(IcnsType.ICNS_128x128_2X_JPEG_PNG_IMAGE, getThumbnail(contentsDir, 256)); - //builder.add("ic08", getThumbnail(contentsDir, 256)); - builder.add(osType, new FileInputStream(iconFile)); - //builder.add("ic10", getThumbnail(contentsDir, 1024)); + if (maxSize == 0) { + builder.add(osType, new FileInputStream(iconFile)); + } else { + // only down to the source size - upscaling would tag a blurry slice as native + for (int i = 0; i < ICON_SIZES.length && ICON_SIZES[i] <= maxSize; i++) { + for (IcnsType type : ICON_TYPES[i]) { + try (InputStream in = getThumbnail(contentsDir, ICON_SIZES[i])) { + builder.add(type.getOsType(), in); + } + } + } + } File icnsFile = ext != null ? new File(contentsDir, "Resources/icon."+ext+".icns") : new File(contentsDir, "Resources/icon.icns"); try (FileOutputStream out = new FileOutputStream(icnsFile)) { builder.build().writeTo(out); } - + } iconFile.delete(); - //cleanThumbnails(contentsDir); + cleanThumbnails(contentsDir); }