From 272b7df8dd100fc42b24b3cc056f29cabda01687 Mon Sep 17 00:00:00 2001 From: Michael Binz Date: Wed, 18 Mar 2020 17:06:40 +0100 Subject: [PATCH 1/8] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 00d4654..2940e43 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,15 @@ Yet another Java launcher generator for Windows. Unique features are: * Works with Java 10+. * Automatically creates the necessary icon resources from a single .png image. -Many other fine launchers exist. But after a long search we did not find one that was able to work with Java 11 and application images generated by JLink. jlgen is in use today as part of our Windows toolchain for generating Java-JLink-based native Windows installers for a set of commercial applications. And no customer knows it's Java :) +Many other fine launchers exist. But after a long search we did not find one that was able to work with Java 11 and application images generated by JLink. jlgen is in use today as part of our Windows build chain for generating Java-JLink-based native Windows installers for a set of commercial applications. And no customer knows it's Java :) ## How to generate a launcher Get the latest version of the jlgen executable from the project's [release page](https://github.com/michab66/jlaunch/releases). -`jlgen.exe` is a command line application that supports different commands. The command *`MakeLauncher`* represents the central functionality of the software. It generates a native Windows launcher for a JLink-generated Java application image. +`jlgen.exe` is a command line application that supports different commands. The command *`MakeLauncher`* represents the central functionality of the software. It generates a native Windows launcher for a JLink-generated Java application image. The command line is + + `jlgen MakeLauncher `. The required parameters are: * The name of the target file, e.g. 'Farboo.exe'. If the .exe extension is not specified then it is added. @@ -23,7 +25,7 @@ The required parameters are: A complete sample call may look like `jlgen MakeLauncher C:\cygwin64\tmp\Farboo.exe ..\mmt-icon-1024.png app.mmt de.michab.app.mmt.Mmt`. - + ## I have the launcher executable, what now? Note that the generated launcher--in our example 'Farboo.exe'--has to be placed in the existing jlink image directory hierarchy at the same position where the file `jvm.dll` is located. This is currently `{jlink-app-root}/bin/server` but this may change in coming versions of the Jdk. (It is a deliberate decision not to look-up the jvm.dll dynamically to prevent to start the target application using a wrong Jdk/Jre that non-deterministically happened to be found.) From 522dbe1fc69079a95cd7ce381248408ee1ddc70b Mon Sep 17 00:00:00 2001 From: Michael Binz Date: Wed, 18 Mar 2020 17:09:29 +0100 Subject: [PATCH 2/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2940e43..2234553 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ A complete sample call may look like `jlgen MakeLauncher C:\cygwin64\tmp\Farboo.exe ..\mmt-icon-1024.png app.mmt de.michab.app.mmt.Mmt`. ## I have the launcher executable, what now? -Note that the generated launcher--in our example 'Farboo.exe'--has to be placed in the existing jlink image directory hierarchy at the same position where the file `jvm.dll` is located. This is currently `{jlink-app-root}/bin/server` but this may change in coming versions of the Jdk. (It is a deliberate decision not to look-up the jvm.dll dynamically to prevent to start the target application using a wrong Jdk/Jre that non-deterministically happened to be found.) +Note that the generated launcher--in our example 'Farboo.exe'--has to be placed in the existing jlink image directory hierarchy at the same position where the file `jvm.dll` is located. This is currently `{jlink-app-root}/bin/server` but this may change in coming versions of the Jdk. In Windows Explorer the launcher is displayed with the application icon that was passed to `jlgen.exe` above. On a double click your application opens, joy starts :) From 195b550717e867ea54b2e1af58ed12bf5a046c79 Mon Sep 17 00:00:00 2001 From: Michael Binz Date: Wed, 18 Mar 2020 17:11:33 +0100 Subject: [PATCH 3/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2234553..f1dbb99 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ A complete sample call may look like ## I have the launcher executable, what now? Note that the generated launcher--in our example 'Farboo.exe'--has to be placed in the existing jlink image directory hierarchy at the same position where the file `jvm.dll` is located. This is currently `{jlink-app-root}/bin/server` but this may change in coming versions of the Jdk. -In Windows Explorer the launcher is displayed with the application icon that was passed to `jlgen.exe` above. On a double click your application opens, joy starts :) +In Windows Explorer the launcher is displayed with the application icon that was passed to `jlgen.exe` above. On a double click your application opens and joy starts :^) The next step for professional application packing is to create a native Windows installer based on the JLink file system including the `jlgen`-generated launcher that has to be configured as the start application. We use the [WiX toolset](https://wixtoolset.org/) for this purpose, but this is a different story. From 80095b77c543d6cc8967cd7d0966e0af5b7da1dd Mon Sep 17 00:00:00 2001 From: Michael Binz Date: Tue, 24 Mar 2020 09:17:12 +0100 Subject: [PATCH 4/8] Added .ico file generation. --- jlgen/RtIcon.cpp | 2 +- jlgen/RtIcon.h | 7 ++- jlgen/jlgen.cpp | 23 ++++++--- jlgen/mod_icons.cpp | 120 ++++++++++++++++++++++++++++++++------------ jlgen/mod_icons.hpp | 18 +++++-- test/6858395_0.png | Bin 0 -> 46879 bytes 6 files changed, 123 insertions(+), 47 deletions(-) create mode 100644 test/6858395_0.png diff --git a/jlgen/RtIcon.cpp b/jlgen/RtIcon.cpp index d200375..246a251 100644 --- a/jlgen/RtIcon.cpp +++ b/jlgen/RtIcon.cpp @@ -43,4 +43,4 @@ void RtIcon::update(HANDLE resourceHolder, int resourceId) } } // namespace windows -} // namespace micbinz +} // namespace mob diff --git a/jlgen/RtIcon.h b/jlgen/RtIcon.h index 4e89820..200f7c6 100644 --- a/jlgen/RtIcon.h +++ b/jlgen/RtIcon.h @@ -32,6 +32,11 @@ namespace windows void update(HANDLE resourceHolder, int resourceId); + const std::vector& raw_png() + { + return data_; + } + GRPICONDIRENTRY GetDirectoryEntry() { return directoryEntry_; @@ -39,4 +44,4 @@ namespace windows }; } // namespace windows -} // namespace micbinz +} // namespace mob diff --git a/jlgen/jlgen.cpp b/jlgen/jlgen.cpp index 3af8d89..25d1456 100644 --- a/jlgen/jlgen.cpp +++ b/jlgen/jlgen.cpp @@ -169,12 +169,19 @@ namespace jlgen return EXIT_SUCCESS; } - int WriteImageSet( - const string& file) + int CreateWindowsIcon( + const string& pngFile) { - smack::util::icons::WriteImageSet( - file, - IMAGE_SIZES ); + path icnFile = pngFile; + icnFile.replace_extension(".ico"); + + cerr << "Writing icon file: " << icnFile << endl; + + smack::util::icons::CreateWindowsIcon( + pngFile, + IMAGE_SIZES, + icnFile); + return EXIT_SUCCESS; } @@ -199,10 +206,10 @@ namespace jlgen "startClass" }), Commands::make( - "WriteImageSet", - WriteImageSet, + "CreateWindowsIcon", + CreateWindowsIcon, { - "imageFilename" + "pngFilename", }) ); diff --git a/jlgen/mod_icons.cpp b/jlgen/mod_icons.cpp index c00d2e5..5e22024 100644 --- a/jlgen/mod_icons.cpp +++ b/jlgen/mod_icons.cpp @@ -11,6 +11,7 @@ #include #include + // See https://docs.microsoft.com/en-us/windows/uwp/design/globalizing/use-utf8-code-page #undef UNICODE @@ -207,39 +208,6 @@ namespace smack { namespace util { namespace icons { -/** - * Use the passed image to create a set of scaled, square images in the - * dimensions 16, 32, 64, 128, 256. It is recommended to pass a square - * image though all image sizes will do. - */ -void WriteImageSet( - const path& sourceFile, - const std::initializer_list sizes) -{ - InitGdiPlus init; - - if (!exists(sourceFile)) - throw std::invalid_argument("File not found."); - - std::wstring wideName = - sourceFile.c_str(); - - Gdiplus::Bitmap bitmap{ wideName.c_str() }; - - CLSID fileClsid = - GetClsid(bitmap); - if (IsEqualCLSID(fileClsid, CLSID_NULL)) - throw std::invalid_argument("Unknown file type."); - - string baseName = - GetPath(sourceFile.generic_string()); - string suffix = - GetSuffix(sourceFile.generic_string()); - - for (auto c : sizes) - WriteImageFile(baseName, suffix, c, fileClsid, bitmap); -} - /** * */ @@ -282,6 +250,92 @@ void CreateIcons( } } +/** + * See header. + */ +void CreateWindowsIcon( + const path& sourcePng, + const std::initializer_list sizes, + const path& targetIco) +{ + std::vector> outHolder; + smack::util::icons::CreateIcons( + outHolder, + sizes, + sourcePng); + + std::vector fileContent; + + // Add the prefix icondir structure. + ICONDIR icondir{ + 0, + 1, + static_cast(outHolder.size()) }; + rawAppend( + fileContent, + &icondir, + sizeof(icondir) - sizeof(ICONDIRENTRY)); + + // Add the directory entry structures. + size_t currentOffset{ + fileContent.size() + (outHolder.size() * sizeof(ICONDIRENTRY) ) + }; + + for (const std::unique_ptr& c : outHolder) + { + auto grpDirEntry = + c->GetDirectoryEntry(); + + ICONDIRENTRY dirEntry; + dirEntry.bWidth = + grpDirEntry.bWidth; + dirEntry.bHeight = + grpDirEntry.bHeight; + dirEntry.bColorCount = + grpDirEntry.bColorCount; + dirEntry.bReserved = + grpDirEntry.bReserved; + dirEntry.wPlanes = + grpDirEntry.wPlanes; + dirEntry.wBitCount = + grpDirEntry.wBitCount; + dirEntry.dwBytesInRes = + grpDirEntry.dwBytesInRes; + + dirEntry.dwImageOffset = + static_cast(currentOffset); + currentOffset += c->raw_png().size(); + + rawAppend( + fileContent, + &dirEntry); + } + + // Add the actual entries. + for (const std::unique_ptr& c : outHolder) + { + auto pngData = + c->raw_png(); + rawAppend( + fileContent, + pngData.data(), + pngData.size() + ); + } + + // Write to file. + std::ofstream fout( + targetIco, + std::ios::binary); + void* data = + fileContent.data(); + fout.write( + static_cast(data), + fileContent.size()); + // Trigger write error here, not in destructor. + fout.close(); +} + } } } diff --git a/jlgen/mod_icons.hpp b/jlgen/mod_icons.hpp index a4fca48..1a664fc 100644 --- a/jlgen/mod_icons.hpp +++ b/jlgen/mod_icons.hpp @@ -22,16 +22,26 @@ using std::string; using std::experimental::filesystem::path; /** - * Use the passed image to create a set of scaled, square images in the + * Create a Windows .icn file from an input .png file. + * Use the passed sizes to create a set of scaled, square images in the * supported dimensions. It is recommended to pass a square image though * all image sizes will do. + * + * @param sourcePng The source .png file. This must exist. + * @param sizes A list of sizes. Used to create square images. + * @param targetIco The target file. If this exists it gets overwritten. */ -void WriteImageSet( - const path& sourceFile, - const std::initializer_list sizes); +void CreateWindowsIcon( + const path& sourcePng, + const std::initializer_list sizes, + const path& targetIco); /** * Create a set of scaled icons and place them into the passed container. + * + * @param outHolder The target container. + * @param sizes A list of sites. + * @param sourcePng The input .png file. */ void CreateIcons( std::vector>& outHolder, diff --git a/test/6858395_0.png b/test/6858395_0.png new file mode 100644 index 0000000000000000000000000000000000000000..b79411b0aabd70132420ea7f52b1301be1199190 GIT binary patch literal 46879 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

0yM9YF(9oCSJV}e-*N==+@vOyLzO$~bPGYiC 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 literal 0 HcmV?d00001 From 2cf74d2b6e838891e831613df6054706dd3b97a1 Mon Sep 17 00:00:00 2001 From: Michael Binz Date: Tue, 24 Mar 2020 09:34:06 +0100 Subject: [PATCH 5/8] Update README.md --- README.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f1dbb99..418b14b 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,33 @@ Yet another Java launcher generator for Windows. Unique features are: * Knows about (jigsaw) modules. * Works with Java 10+. * Automatically creates the necessary icon resources from a single .png image. +* Offers a command line option to generate a Windows `.ico`-file from a given `.png`-file. Many other fine launchers exist. But after a long search we did not find one that was able to work with Java 11 and application images generated by JLink. jlgen is in use today as part of our Windows build chain for generating Java-JLink-based native Windows installers for a set of commercial applications. And no customer knows it's Java :) -## How to generate a launcher +In addition we had a hard time to generate Windows `.ico`-files. The existing tools didn't work or were clumsy to use, so we added this to `jlgen`. -Get the latest version of the jlgen executable from the project's [release page](https://github.com/michab66/jlaunch/releases). +# Usage -`jlgen.exe` is a command line application that supports different commands. The command *`MakeLauncher`* represents the central functionality of the software. It generates a native Windows launcher for a JLink-generated Java application image. The command line is +Firstly, get the latest version of the jlgen executable from the project's [release page](https://github.com/michab66/jlaunch/releases). + +`jlgen.exe` is a command line application that supports different commands. Execute `jlgen` to get a list of the supported commands. + +## How to generate a Windows `.ico`-file -- `CreateWindowsIcon` + +The command *`CreateWindowsIcon`* lets you create a Windows `.ico`-file from a given input `.png`-file. Internally the input `.png` gets scaled to square images of the pixel sizes 16x16, 32x32, 64x64, 128x128 and 256x256. The resulting `.ico`-file is generated in the directory of the input file, with an `.ico` suffix. The command line is: + + `jlgen CreateWindowsIcon `. + +A sample command line is + + `jlgen CreateWindowsIcon felix.png`. + +This results in the creation of the file `felix.ico` in the same directory as `felix.png`. + +## How to generate a launcher -- `MakeLauncher` + +The command *`MakeLauncher`* represents the central functionality of the software. It generates a native Windows launcher for a JLink-generated Java application image. The command line is `jlgen MakeLauncher `. @@ -26,7 +45,7 @@ A complete sample call may look like `jlgen MakeLauncher C:\cygwin64\tmp\Farboo.exe ..\mmt-icon-1024.png app.mmt de.michab.app.mmt.Mmt`. -## I have the launcher executable, what now? +# I have the launcher executable and the icon files, what now? Note that the generated launcher--in our example 'Farboo.exe'--has to be placed in the existing jlink image directory hierarchy at the same position where the file `jvm.dll` is located. This is currently `{jlink-app-root}/bin/server` but this may change in coming versions of the Jdk. In Windows Explorer the launcher is displayed with the application icon that was passed to `jlgen.exe` above. On a double click your application opens and joy starts :^) From ea98ad2869a8da484222f3d18482c807c8b85a08 Mon Sep 17 00:00:00 2001 From: Michael Binz Date: Tue, 24 Mar 2020 13:04:15 +0100 Subject: [PATCH 6/8] Added Mac .icns support. --- jlgen/jlgen.cpp | 24 +++++++++ jlgen/mod_icons.cpp | 117 ++++++++++++++++++++++++++++++++++++++++++++ jlgen/mod_icons.hpp | 17 ++++++- 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/jlgen/jlgen.cpp b/jlgen/jlgen.cpp index 25d1456..a30df43 100644 --- a/jlgen/jlgen.cpp +++ b/jlgen/jlgen.cpp @@ -185,6 +185,24 @@ namespace jlgen return EXIT_SUCCESS; } + int CreateAppleIcon( + const string& pngFile) + { + path icnFile = pngFile; + icnFile.replace_extension(".icns"); + + cerr << "Writing icon file: " << icnFile << endl; + + // TODO(michab66) The Apple iconviewer expects 16,32,128,256,512. + // But the file with our current setting seems to work. + smack::util::icons::CreateAppleIcon( + pngFile, + IMAGE_SIZES, + icnFile); + + return EXIT_SUCCESS; + } + int execute(const std::vector& argv) { using smack::util::Commands; @@ -208,6 +226,12 @@ namespace jlgen Commands::make( "CreateWindowsIcon", CreateWindowsIcon, + { + "pngFilename", + }), + Commands::make( + "CreateAppleIcon", + CreateAppleIcon, { "pngFilename", }) diff --git a/jlgen/mod_icons.cpp b/jlgen/mod_icons.cpp index 5e22024..99c0038 100644 --- a/jlgen/mod_icons.cpp +++ b/jlgen/mod_icons.cpp @@ -11,6 +11,7 @@ #include #include +#include // See https://docs.microsoft.com/en-us/windows/uwp/design/globalizing/use-utf8-code-page #undef UNICODE @@ -336,6 +337,122 @@ void CreateWindowsIcon( fout.close(); } +/** + * See header. + * + * The implementation uses info from + * https://en.wikipedia.org/wiki/Apple_Icon_Image_format + */ +void CreateAppleIcon( + const path& sourcePng, + const std::initializer_list sizes, + const path& targetIco) +{ + std::vector> outHolder; + smack::util::icons::CreateIcons( + outHolder, + sizes, + sourcePng); + + std::vector fileContent; + + // Write the lead-in magic literal. + rawAppend( + fileContent, + "icns", + 4 ); + + // Compute the size of the file we generate. + { + uint32_t fileSize = static_cast( + // Sizeof magic literal. + fileContent.size() + + sizeof(fileSize) + + // 8 = sizeof( IconType ) + sizeof( LengthOfData ). + (outHolder.size() * 8) ); + for (const std::unique_ptr& c : outHolder) + fileSize += static_cast(c->raw_png().size()); + fileContent.reserve(fileSize); + + // MSVC intrinsic. + fileSize = _byteswap_ulong(fileSize); + + rawAppend( + fileContent, + &fileSize + ); + } + + for (const std::unique_ptr& c : outHolder) + { + auto grpDirEntry = + c->GetDirectoryEntry(); + + const char* OSType; + switch (grpDirEntry.bWidth) + { + case 16: + OSType = "icp4"; + break; + case 32: + OSType = "icp5"; + break; + case 64: + OSType = "icp6"; + break; + case 128: + OSType = "ic07"; + break; + // Zero means actually 256. + case 0: + OSType = "ic08"; + break; + default: + throw std::invalid_argument("Unexpected size."); + } + + // Icon type. + rawAppend( + fileContent, + OSType, + 4 ); + + auto data = + c->raw_png(); + uint32_t size = + static_cast(data.size()); + // Size is 'including type and length'. + size += + (sizeof(size) + 4); + size = + _byteswap_ulong(size); + + // Length of data, big endian. + rawAppend( + fileContent, + &size + ); + // Icon data. + rawAppend( + fileContent, + data.data(), + data.size() + ); + } + + // Write to file. + std::ofstream fout( + targetIco, + std::ios::binary); + void* data = + fileContent.data(); + fout.write( + static_cast(data), + fileContent.size()); + // Trigger write error here, not in destructor. + fout.close(); +} + } } } diff --git a/jlgen/mod_icons.hpp b/jlgen/mod_icons.hpp index 1a664fc..d7da385 100644 --- a/jlgen/mod_icons.hpp +++ b/jlgen/mod_icons.hpp @@ -22,7 +22,7 @@ using std::string; using std::experimental::filesystem::path; /** - * Create a Windows .icn file from an input .png file. + * Create a Windows .ico file from an input .png file. * Use the passed sizes to create a set of scaled, square images in the * supported dimensions. It is recommended to pass a square image though * all image sizes will do. @@ -36,6 +36,21 @@ void CreateWindowsIcon( const std::initializer_list sizes, const path& targetIco); +/** + * Create an MacOS .icns file from an input .png file. + * Use the passed sizes to create a set of scaled, square images in the + * supported dimensions. It is recommended to pass a square image though + * all image sizes will do. + * + * @param sourcePng The source .png file. This must exist. + * @param sizes A list of sizes. Used to create square images. + * @param targetIco The target file. If this exists it gets overwritten. + */ +void CreateAppleIcon( + const path& sourcePng, + const std::initializer_list sizes, + const path& targetIco); + /** * Create a set of scaled icons and place them into the passed container. * From cf644d173d31dfd8d56865224057664d15fa66cf Mon Sep 17 00:00:00 2001 From: Michael Binz Date: Tue, 24 Mar 2020 13:09:11 +0100 Subject: [PATCH 7/8] Added docs. --- jlgen/jlgen.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/jlgen/jlgen.cpp b/jlgen/jlgen.cpp index a30df43..e162570 100644 --- a/jlgen/jlgen.cpp +++ b/jlgen/jlgen.cpp @@ -169,6 +169,12 @@ namespace jlgen return EXIT_SUCCESS; } + /** + * Create an .ico icon file for Windows. Create target file at position + * of input file with extension .ico. + * + * @param pngFile Input png. + */ int CreateWindowsIcon( const string& pngFile) { @@ -185,6 +191,12 @@ namespace jlgen return EXIT_SUCCESS; } + /** + * Create an .icns icon for Mac. Create target file at position + * of input file with extension .icns. + * + * @param pngFile Input png. + */ int CreateAppleIcon( const string& pngFile) { @@ -194,7 +206,7 @@ namespace jlgen cerr << "Writing icon file: " << icnFile << endl; // TODO(michab66) The Apple iconviewer expects 16,32,128,256,512. - // But the file with our current setting seems to work. + // But the file with our current setting seems to work. smack::util::icons::CreateAppleIcon( pngFile, IMAGE_SIZES, From ad709cb4c7f4574f79c9edc1811ae2f4376b9e49 Mon Sep 17 00:00:00 2001 From: Michael Binz Date: Tue, 24 Mar 2020 13:13:18 +0100 Subject: [PATCH 8/8] Update README.md --- README.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 418b14b..5af1096 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,22 @@ Firstly, get the latest version of the jlgen executable from the project's [rele `jlgen.exe` is a command line application that supports different commands. Execute `jlgen` to get a list of the supported commands. +## How to generate a launcher -- `MakeLauncher` + +The command *`MakeLauncher`* represents the central functionality of the software. It generates a native Windows launcher for a JLink-generated Java application image. The command line is + + `jlgen MakeLauncher `. + +The required parameters are: +* The name of the target file, e.g. 'Farboo.exe'. If the .exe extension is not specified then it is added. +* The name of an icon file that is used to generate the required icon resources in the executable, e.g. 'farboo.png'. Note that a *.png* file is required, no hassle with generating an .ico file. It is recommended to offer a square, high resolution image, though all sizes and resolutions will do. This image gets scaled and resized for the resolutions 16, 32, 64, 128, 256 pixels. +* The name of the target module. That is, the name of the module that holds the main Java application. For example `app.mmt`. +* Finally, the name of the Java class representing the entry point to the application. This class has to offer the Java-application's `public static void main( String[] argv )` operation. An example is `de.michab.app.mmt.Mmt`. + +A complete sample call may look like + + `jlgen MakeLauncher C:\cygwin64\tmp\Farboo.exe ..\mmt-icon-1024.png app.mmt de.michab.app.mmt.Mmt`. + ## How to generate a Windows `.ico`-file -- `CreateWindowsIcon` The command *`CreateWindowsIcon`* lets you create a Windows `.ico`-file from a given input `.png`-file. Internally the input `.png` gets scaled to square images of the pixel sizes 16x16, 32x32, 64x64, 128x128 and 256x256. The resulting `.ico`-file is generated in the directory of the input file, with an `.ico` suffix. The command line is: @@ -29,21 +45,17 @@ A sample command line is This results in the creation of the file `felix.ico` in the same directory as `felix.png`. -## How to generate a launcher -- `MakeLauncher` +## How to generate a MacOS `.icns`-file -- `CreateAppleIcon` -The command *`MakeLauncher`* represents the central functionality of the software. It generates a native Windows launcher for a JLink-generated Java application image. The command line is +The command *`CreateAppleIcon`* lets you create a MacOS `.icns`-file from a given input `.png`-file. Internally the input `.png` gets scaled to square images of the pixel sizes 16x16, 32x32, 64x64, 128x128 and 256x256. The resulting `.icns`-file is generated in the directory of the input file, with an `.icns` suffix. The command line is: - `jlgen MakeLauncher `. + `jlgen CreateAppleIcon `. -The required parameters are: -* The name of the target file, e.g. 'Farboo.exe'. If the .exe extension is not specified then it is added. -* The name of an icon file that is used to generate the required icon resources in the executable, e.g. 'farboo.png'. Note that a *.png* file is required, no hassle with generating an .ico file. It is recommended to offer a square, high resolution image, though all sizes and resolutions will do. This image gets scaled and resized for the resolutions 16, 32, 64, 128, 256 pixels. -* The name of the target module. That is, the name of the module that holds the main Java application. For example `app.mmt`. -* Finally, the name of the Java class representing the entry point to the application. This class has to offer the Java-application's `public static void main( String[] argv )` operation. An example is `de.michab.app.mmt.Mmt`. +A sample command line is -A complete sample call may look like + `jlgen CreateAppleIcon felix.png`. - `jlgen MakeLauncher C:\cygwin64\tmp\Farboo.exe ..\mmt-icon-1024.png app.mmt de.michab.app.mmt.Mmt`. +This results in the creation of the file `felix.icns` in the same directory as `felix.png`. # I have the launcher executable and the icon files, what now? Note that the generated launcher--in our example 'Farboo.exe'--has to be placed in the existing jlink image directory hierarchy at the same position where the file `jvm.dll` is located. This is currently `{jlink-app-root}/bin/server` but this may change in coming versions of the Jdk.