diff --git a/LICENSE.txt b/LICENSE.txt index 0e259d4..3f56a63 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,121 +1,18 @@ -Creative Commons Legal Code +Copyright 2020 LoRd_MuldeR -CC0 1.0 Universal +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/res/launcher.ico b/res/launcher.ico new file mode 100644 index 0000000..8d2c957 Binary files /dev/null and b/res/launcher.ico differ diff --git a/res/resources.rc b/res/resources.rc new file mode 100644 index 0000000..c55d1d3 --- /dev/null +++ b/res/resources.rc @@ -0,0 +1,20 @@ +/************************************************************/ +/* Launch5j, by LoRd_MuldeR */ +/* Java JAR wrapper for creating Windows native executables */ +/* https://github.com/lordmulder/ */ +/* */ +/* This work has been released under the MIT license. */ +/* Please see LICENSE.TXT for details! */ +/* */ +/* ACKNOWLEDGEMENT */ +/* This project is partly inspired by the Launch4j project: */ +/* https://sourceforge.net/p/launch4j/ */ +/************************************************************/ + +#include "../src/resource.h" + +// ICON +ID_APP_ICON ICON "launcher.ico" + +// BITMAP +ID_SPLASH_BITMAP BITMAP "splash_screen.bmp" diff --git a/res/splash_screen.bmp b/res/splash_screen.bmp new file mode 100644 index 0000000..51301f9 Binary files /dev/null and b/res/splash_screen.bmp differ diff --git a/src/main.c b/src/main.c index 814dbf5..f61d04a 100644 --- a/src/main.c +++ b/src/main.c @@ -1,15 +1,36 @@ +/************************************************************/ +/* Launch5j, by LoRd_MuldeR */ +/* Java JAR wrapper for creating Windows native executables */ +/* https://github.com/lordmulder/ */ +/* */ +/* This work has been released under the MIT license. */ +/* Please see LICENSE.TXT for details! */ +/* */ +/* ACKNOWLEDGEMENT */ +/* This project is partly inspired by the Launch4j project: */ +/* https://sourceforge.net/p/launch4j/ */ +/************************************************************/ + +#define WIN32_LEAN_AND_MEAN 1 + +#include #include #include #include #include #include #include +#include "resource.h" -#define WIN32_LEAN_AND_MEAN 1 -#include - +// Const +static const DWORD SPLASH_SCREEN_TIMEOUT = 30U; static const wchar_t *const JRE_RELATIVE_PATH = L"runtime\\bin\\javaw.exe"; +// Options +#define ENABLE_SPLASH 1 +#define WAIT_FOR_INPUT_IDLE 1 +//#define JAR_FILE_WRAPPED 1 + /* ======================================================================== */ /* String routines */ /* ======================================================================== */ @@ -156,6 +177,7 @@ static const wchar_t *get_executable_directory(const wchar_t *const executable_p static const wchar_t *get_jarfile_path(const wchar_t *const executable_path, const wchar_t *const executable_directory) { +#ifndef JAR_FILE_WRAPPED const wchar_t *jarfile_path = NULL; const wchar_t *const path_prefix = remove_file_extension(executable_path); @@ -175,6 +197,9 @@ static const wchar_t *get_jarfile_path(const wchar_t *const executable_path, con free((void*)path_prefix); return jarfile_path; +#else + return wcsdup(executable_path); /*JAR file is wrapped*/ +#endif } /* ======================================================================== */ @@ -201,6 +226,49 @@ static const BOOL set_current_directory(const wchar_t *const path) } } +/* ======================================================================== */ +/* Splash screen */ +/* ======================================================================== */ + +static BOOL create_splash_screen(const HWND hwnd, const HANDLE splash_image) +{ + if (hwnd && splash_image) + { + RECT rect; + SendMessageW(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) splash_image); + GetWindowRect(hwnd, &rect); + const int x = (GetSystemMetrics(SM_CXSCREEN) - (rect.right - rect.left)) / 2; + const int y = (GetSystemMetrics(SM_CYSCREEN) - (rect.bottom - rect.top)) / 2; + SetWindowPos(hwnd, HWND_TOP, x, y, 0, 0, SWP_NOSIZE); + ShowWindow(hwnd, SW_SHOW); + return UpdateWindow(hwnd); + } + return FALSE; +} + +static BOOL process_window_messages(const HWND hwnd) +{ + BOOL result = FALSE; + if (hwnd != NULL) + { + MSG msg = {}; + for (DWORD k = 0U; k < MAXWORD; ++k) + { + if (PeekMessageW(&msg, hwnd, 0U, 0U, PM_REMOVE)) + { + result = TRUE; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + else + { + break; /*no more messages!*/ + } + } + } + return result; +} + /* ======================================================================== */ /* Message box */ /* ======================================================================== */ @@ -236,32 +304,79 @@ while(0) } \ while(0) +/* ======================================================================== */ +/* Utilities */ +/* ======================================================================== */ + +static void close_handle(HANDLE *const handle) +{ + if(*handle) + { + CloseHandle(*handle); + *handle = NULL; + } +} + +static void delete_object(HGDIOBJ *const handle) +{ + if(*handle) + { + DeleteObject(*handle); + *handle = NULL; + } +} + +static void destroy_window(HWND *const hwnd) +{ + if(*hwnd) + { + DestroyWindow(*hwnd); + *hwnd = NULL; + } +} + /* ======================================================================== */ /* MAIN */ /* ======================================================================== */ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { - int exit_code = -1; + int result = -1; const wchar_t *executable_path = NULL, *executable_directory = NULL, *jarfile_path = NULL, *java_runtime_path = NULL, *command_line = NULL; - STARTUPINFOW startup_info; + HGDIOBJ splash_image = NULL; + DWORD exit_code = 0U; PROCESS_INFORMATION process_info; + STARTUPINFOW startup_info; // Initialize SecureZeroMemory(&startup_info, sizeof(STARTUPINFOW)); SecureZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + // Create the window + HWND hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, L"STATIC", L"", WS_POPUP | SS_BITMAP, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); + + // Show the splash screen +#ifdef ENABLE_SPLASH + if(splash_image = LoadImage(hInstance, MAKEINTRESOURCE(ID_SPLASH_BITMAP), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE)) + { + if(create_splash_screen(hwnd, splash_image)) + { + process_window_messages(hwnd); + } + } +#endif + // Find executable path if(!(executable_path = get_executable_path())) { - show_message(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"System Error", L"The path of the executable could not be determined!"); + show_message(hwnd, MB_ICONERROR | MB_TOPMOST, L"System Error", L"The path of the executable could not be determined!"); goto cleanup; } // Find executable directory if(!(executable_directory = get_executable_directory(executable_path))) { - show_message(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"System Error", L"The executable directory could not be determined!"); + show_message(hwnd, MB_ICONERROR | MB_TOPMOST, L"System Error", L"The executable directory could not be determined!"); goto cleanup; } @@ -274,28 +389,30 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine // Find the JAR file path if(!(jarfile_path = get_jarfile_path(executable_path, executable_directory))) { - show_message(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"System Error", L"The path of the JAR file could not be determined!"); + show_message(hwnd, MB_ICONERROR | MB_TOPMOST, L"System Error", L"The path of the JAR file could not be determined!"); goto cleanup; } - // Find the Java runtime path + // Find the Java runtime executable path if(!(java_runtime_path = awprintf(L"%ls\\%ls", executable_directory, JRE_RELATIVE_PATH))) { - show_message(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"System Error", L"The path of the Java runtime could not be determined!"); + show_message(hwnd, MB_ICONERROR | MB_TOPMOST, L"System Error", L"The path of the Java runtime could not be determined!"); goto cleanup; } - // Does JAR file exist? + // Does the JAR file exist? +#ifndef JAR_FILE_WRAPPED if(!file_exists(jarfile_path)) { - show_message_format(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"JAR not found", L"The required JAR file could not be found:\n\n%ls\n\n\nRe-installing the application may fix the problem!", jarfile_path); + show_message_format(hwnd, MB_ICONERROR | MB_TOPMOST, L"JAR not found", L"The required JAR file could not be found:\n\n%ls\n\n\nRe-installing the application may fix the problem!", jarfile_path); goto cleanup; } +#endif - // Does the Java runtime exist? + // Does the Java runtime executable exist? if(!file_exists(java_runtime_path)) { - show_message_format(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"Java not found", L"The required Java runtime could not be found:\n\n%ls\n\n\nRe-installing the application may fix the problem!", java_runtime_path); + show_message_format(hwnd, MB_ICONERROR | MB_TOPMOST, L"Java not found", L"The required Java runtime could not be found:\n\n%ls\n\n\nRe-installing the application may fix the problem!", java_runtime_path); goto cleanup; } @@ -303,37 +420,67 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine command_line = NOT_EMPTY(pCmdLine) ? awprintf(L"\"%ls\" -jar \"%ls\" %ls", java_runtime_path, jarfile_path, pCmdLine) : awprintf(L"\"%ls\" -jar \"%ls\"", java_runtime_path, jarfile_path); if(!command_line) { - show_message(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"System Error", L"The Java command-line could not be generated!"); + show_message(hwnd, MB_ICONERROR | MB_TOPMOST, L"System Error", L"The Java command-line could not be generated!"); goto cleanup; } + // Process pending window messages +#ifdef ENABLE_SPLASH + process_window_messages(hwnd); +#endif + // Now actually start the process! if(!CreateProcessW(NULL, (LPWSTR)command_line, NULL, NULL, FALSE, 0U, NULL, executable_directory, &startup_info, &process_info)) { const wchar_t *const error_text = describe_system_error(GetLastError()); if(error_text) { - show_message_format(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"System Error", L"Failed to create the Java process:\n\n%ls\n\n\n%ls", command_line, error_text); + show_message_format(hwnd, MB_ICONERROR | MB_TOPMOST, L"System Error", L"Failed to create the Java process:\n\n%ls\n\n\n%ls", command_line, error_text); free((void*)error_text); } else { - show_message_format(NULL, MB_ICONERROR | MB_SYSTEMMODAL, L"System Error", L"Failed to create the Java process:\n\n%ls", command_line); + show_message_format(hwnd, MB_ICONERROR | MB_TOPMOST, L"System Error", L"Failed to create the Java process:\n\n%ls", command_line); } goto cleanup; } + // Process pending window messages +#ifdef ENABLE_SPLASH + process_window_messages(hwnd); +#ifdef WAIT_FOR_INPUT_IDLE + for (DWORD t = 0U; t < SPLASH_SCREEN_TIMEOUT; ++t) + { + if (WaitForInputIdle(process_info.hProcess, 1000U) != WAIT_TIMEOUT) + { + break; /*child-process is ready!*/ + } + if (WaitForSingleObject(process_info.hProcess, 1U) != WAIT_TIMEOUT) + { + break; /*child process terminated!*/ + } + process_window_messages(hwnd); + } +#endif + destroy_window(&hwnd); +#endif + + // Wait for process to exit + WaitForSingleObject(process_info.hProcess, INFINITE); + + // Get the exit code + if(GetExitCodeProcess(process_info.hProcess, &exit_code)) + { + result = (int) exit_code; + } + cleanup: - if(process_info.hThread) - { - CloseHandle(process_info.hThread); - } + close_handle(&process_info.hThread); + close_handle(&process_info.hProcess); - if(process_info.hProcess) - { - CloseHandle(process_info.hProcess); - } + destroy_window(&hwnd); + delete_object(splash_image); free((void*)command_line); free((void*)java_runtime_path); @@ -341,5 +488,5 @@ cleanup: free((void*)executable_directory); free((void*)executable_path); - return exit_code; + return result; } diff --git a/src/resource.h b/src/resource.h new file mode 100644 index 0000000..e6eec6c --- /dev/null +++ b/src/resource.h @@ -0,0 +1,18 @@ +/************************************************************/ +/* Launch5j, by LoRd_MuldeR */ +/* Java JAR wrapper for creating Windows native executables */ +/* https://github.com/lordmulder/ */ +/* */ +/* This work has been released under the MIT license. */ +/* Please see LICENSE.TXT for details! */ +/* */ +/* ACKNOWLEDGEMENT */ +/* This project is partly inspired by the Launch4j project: */ +/* https://sourceforge.net/p/launch4j/ */ +/************************************************************/ + +// ICON +#define ID_APP_ICON 1 + +// BITMAP +#define ID_SPLASH_BITMAP 1