diff --git a/README.md b/README.md index 4beab08..85a588e 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ All of the above Launch5j variants are available as `i586` (32-Bit) and `x86-64` Launch5j comes with a *default* executable icon and a *default* splash screen bitmap. These just server as an example and you probably want to replace them with your own *application-specific* graphics. -It is ***not*** necessary to re-build the executable files for that purpose. Instead, you can simply use a resource editor, such as [**XN Resource Editor**](https://stefansundin.github.io/xn_resource_editor/) or [**Resource Hacker™**](http://www.angusj.com/resourcehacker/), to *modify* the pre-compiled executable files as needed: +It is ***not*** necessary to re-build the executable files for that purpose. Instead, you can simply use a resource editor, such as [**XN Resource Editor**](https://web.archive.org/web/20100419201225/http://www.wilsonc.demon.co.uk/d10resourceeditor.htm) ([mirror](https://stefansundin.github.io/xn_resource_editor/)) or [**Resource Hacker™**](http://www.angusj.com/resourcehacker/), to *modify* the pre-compiled executable files as needed: ![reshack](etc/reshacker-example.png) @@ -90,17 +90,30 @@ Some options can be configured via the launcher executable's [STRINGTABLE](https Specifies the ***maximum*** supported JRE version, in the **`w.x.y.z`** format (e.g. `12.0.0.0`). This values is *exclusive*, i.e. only JRE versions *older* than the specified JRE version will be accepted. If not specified, then there is **no** upper limit on the supported JRE version. - - *Hint:* Old-style `1.x.y.z` Java versions are automatically translated to the `x.y.z.0` format! - + + *Hint:* Old-style `1.x.y.z` Java versions are automatically translated to the `x.y.z.0` format! + (This option only applies to the `registry` variant of Launch5j) * **`ID_STR_BITNESS` (#6)** Specifies the required ***bitness*** of the JRE. This can be either **`32`** (x86, aka i586) or **`64`** (x86-64). If not specified, 32-Bit *and* 64-Bit JREs are accepted, with a preference to 64-Bit. - + (This option only applies to the `registry` variant of Launch5j) +* **`ID_STR_BITNESS` (#6)** + Specifies the required ***bitness*** of the JRE. This can be either **`32`** (x86, aka i586) or **`64`** (x86-64). + If not specified, 32-Bit *and* 64-Bit JREs are accepted, with a preference to 64-Bit. + + (This option only applies to the `registry` variant of Launch5j) + +* **`ID_STR_MUTEXID` (#7)** + Specifies the application ID to be used when creating the [*single-instance*](http://www.bcbjournal.org/articles/vol3/9911/Single-instance_applications.htm) mutex. + The ID **must** be at least 5 characters in length and **should** be a *unique* string for each application! + If not specified, then **no** mutex will be created and thus *multiple* instances will be allowed. + + *Hint:* If the specified application ID *starts* with an **`@`** character, then Launch5j will **not** show a message box when the application is already running; the **`@`** character is *not* considered a part of the actual ID. + *Note:* We use the convention that the default resource string value `"?"` is used to represent an “undefined” value, because resource strings cannot be empty. You can replace the default value as needed! # Build instructions diff --git a/build.cmd b/build.cmd index cf0030d..93bec23 100644 --- a/build.cmd +++ b/build.cmd @@ -27,7 +27,7 @@ for %%m in (32,64) do ( echo. ) -echo ALL IS COMPLETED. +echo ALL IS DONE. :build_completed pause diff --git a/res/common.rc b/res/common.rc index ac79083..7f9b9cf 100644 --- a/res/common.rc +++ b/res/common.rc @@ -40,6 +40,7 @@ BEGIN ID_STR_HEADING L"?" /*custom application title*/ ID_STR_JVMARGS L"?" /*additional JVM args*/ ID_STR_CMDARGS L"?" /*additional command-line args*/ + ID_STR_MUTEXID L"?" /*single instance mutex ID*/ END ///////////////////////////////////////////////////////////////////////////// diff --git a/src/head.c b/src/head.c index 38c37d5..112982c 100644 --- a/src/head.c +++ b/src/head.c @@ -48,6 +48,7 @@ // Const static const wchar_t *const JRE_RELATIVE_PATH = L"runtime\\bin\\javaw.exe"; static const wchar_t *const JRE_DOWNLOAD_LINK = L"https://adoptopenjdk.net/"; +static const size_t MIN_MUTEXID_LENGTH = 5U; static const DWORD SPLASH_SCREEN_TIMEOUT = 30000U; /* ======================================================================== */ @@ -918,6 +919,47 @@ static void show_jre_download_notice(const HWND hwnd, const wchar_t *const title } } +/* ======================================================================== */ +/* Single instance */ +/* ======================================================================== */ + +static ULONGLONG hash_code(const BYTE *const message, const size_t message_len) +{ + ULONGLONG hash = 0xCBF29CE484222325ull; + for (size_t iter = 0U; iter < message_len; ++iter) + { + hash ^= message[iter]; + hash *= 0x00000100000001B3ull; + } + return hash; +} + +static BOOL initialize_mutex(HANDLE *const handle, const wchar_t *const mutex_name) +{ + static const char *const BUILD_TIME = __DATE__ " " __TIME__; + + const ULONGLONG hashcode_0 = hash_code((const BYTE*)BUILD_TIME, sizeof(wchar_t) * strlen(BUILD_TIME)); + const ULONGLONG hashcode_1 = hash_code((const BYTE*)mutex_name, sizeof(wchar_t) * wcslen(mutex_name)); + + const wchar_t *const mutex_uuid = awprintf(L"l5j.%016llX%016llX", hashcode_0, hashcode_1); + if (!mutex_uuid) + { + return TRUE; /*better safe than sorry*/ + } + + BOOL result = TRUE; + if ((*handle = CreateMutexW(NULL, TRUE, mutex_uuid)) != NULL) + { + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + result = FALSE; + } + } + + free((void*)mutex_uuid); + return result; +} + /* ======================================================================== */ /* Utilities */ /* ======================================================================== */ @@ -959,7 +1001,8 @@ static wchar_t *const DEFAULT_HEADING = L"Launch5j"; int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { int result = -1; - const wchar_t *app_heading = NULL, *executable_path = NULL, *executable_directory = NULL, *jarfile_path = NULL, *java_runtime_path = NULL, *jvm_extra_args = NULL, *cmd_extra_args = NULL, *command_line = NULL; + const wchar_t *app_heading = NULL, *mutex_name = NULL, *executable_path = NULL, *executable_directory = NULL, *jarfile_path = NULL, *java_runtime_path = NULL, *jvm_extra_args = NULL, *cmd_extra_args = NULL, *command_line = NULL; + HANDLE mutex_handle = NULL; DWORD java_required_bitness = 0U; ULONGLONG java_required_ver_min = 0ULL, java_required_ver_max = 0ULL; HGDIOBJ splash_image = NULL; @@ -976,6 +1019,21 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine // Create the window HWND hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, L"STATIC", APP_HEADING, WS_POPUP | SS_BITMAP, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); + // Single instance + mutex_name = load_string(hInstance, ID_STR_MUTEXID); + if (AVAILABLE(mutex_name) && (wcslen(mutex_name) >= MIN_MUTEXID_LENGTH + ((mutex_name[0U] == L'@') ? 0U : 1U))) + { + if(!initialize_mutex(&mutex_handle, (mutex_name[0U] == L'@') ? mutex_name + 1U : mutex_name)) + { + if(mutex_name[0U] != L'@') + { + show_message(hwnd, MB_ICONWARNING | MB_TOPMOST, APP_HEADING, L"The application is already running.\n\n" + L"If you see this message even though the application does not appear to be running, try restarting your computer!"); + } + goto cleanup; + } + } + // Show the splash screen #if ENABLE_SPLASH if (splash_image = LoadImage(hInstance, MAKEINTRESOURCE(ID_BITMAP_SPLASH), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE)) @@ -1113,6 +1171,7 @@ cleanup: close_handle(&process_info.hThread); close_handle(&process_info.hProcess); + close_handle(&mutex_handle); destroy_window(&hwnd); delete_object(&splash_image); @@ -1124,6 +1183,7 @@ cleanup: free((void*)jarfile_path); free((void*)executable_directory); free((void*)executable_path); + free((void*)mutex_name); free((void*)app_heading); return result; diff --git a/src/resource.h b/src/resource.h index 5a8a4e6..0539073 100644 --- a/src/resource.h +++ b/src/resource.h @@ -24,3 +24,4 @@ #define ID_STR_JAVAMIN 4 #define ID_STR_JAVAMAX 5 #define ID_STR_BITNESS 6 +#define ID_STR_MUTEXID 7