IDEA-149864 Switch boot JDK on Windows shouldn't appear since unavailable
[idea/community.git] / native / WinLauncher / WinLauncher / WinLauncher.cpp
1 /*
2 * Copyright 2000-2013 JetBrains s.r.o.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "stdafx.h"
18 #include "WinLauncher.h"
19
20 typedef JNIIMPORT jint(JNICALL *JNI_createJavaVM)(JavaVM **pvm, JNIEnv **env, void *args);
21
22 HINSTANCE hInst; // Current instance.
23 char jvmPath[_MAX_PATH] = "";
24 JavaVMOption* vmOptions = NULL;
25 int vmOptionCount = 0;
26 bool bServerJVM = false;
27 HMODULE hJVM = NULL;
28 JNI_createJavaVM pCreateJavaVM = NULL;
29 JavaVM* jvm = NULL;
30 JNIEnv* env = NULL;
31 volatile bool terminating = false;
32 bool nativesplash = false;
33
34 HANDLE hFileMapping;
35 HANDLE hEvent;
36 HANDLE hSingleInstanceWatcherThread;
37 const int FILE_MAPPING_SIZE = 16000;
38
39 #ifdef _M_X64
40 bool need64BitJRE = true;
41 #define BITS_STR "64-bit"
42 #else
43 bool need64BitJRE = false;
44 #define BITS_STR "32-bit"
45 #endif
46
47 void TrimLine(char* line);
48
49 std::string EncodeWideACP(const std::wstring &str)
50 {
51   int cbANSI = WideCharToMultiByte(CP_ACP, 0, str.c_str(), str.size(), NULL, 0, NULL, NULL);
52   char* ansiBuf = new char[cbANSI];
53   WideCharToMultiByte(CP_ACP, 0, str.c_str(), str.size(), ansiBuf, cbANSI, NULL, NULL);
54   std::string result(ansiBuf, cbANSI);
55   delete[] ansiBuf;
56   return result;
57 }
58
59 std::string LoadStdString(int id)
60 {
61   wchar_t *buf = NULL;
62   int len = LoadStringW(hInst, id, reinterpret_cast<LPWSTR>(&buf), 0);
63   return len ? EncodeWideACP(std::wstring(buf, len)) : "";
64 }
65
66 bool FileExists(const std::string& path)
67 {
68   return GetFileAttributesA(path.c_str()) != INVALID_FILE_ATTRIBUTES;
69 }
70
71 bool IsValidJRE(const char* path)
72 {
73   std::string dllPath(path);
74   if (dllPath[dllPath.size() - 1] != '\\')
75   {
76     dllPath += "\\";
77   }
78   return FileExists(dllPath + "bin\\server\\jvm.dll") || FileExists(dllPath + "bin\\client\\jvm.dll");
79 }
80
81 bool Is64BitJRE(const char* path)
82 {
83   std::string cfgPath(path);
84   cfgPath += "\\lib\\amd64\\jvm.cfg";
85   return FileExists(cfgPath);
86 }
87
88 bool FindValidJVM(const char* path)
89 {
90   if (IsValidJRE(path))
91   {
92     strcpy_s(jvmPath, _MAX_PATH - 1, path);
93     return true;
94   }
95   char jrePath[_MAX_PATH];
96   strcpy_s(jrePath, path);
97   if (jrePath[strlen(jrePath) - 1] != '\\')
98   {
99     strcat_s(jrePath, "\\");
100   }
101   strcat_s(jrePath, _MAX_PATH - 1, "jre");
102   if (IsValidJRE(jrePath))
103   {
104     strcpy_s(jvmPath, jrePath);
105     return true;
106   }
107   return false;
108 }
109
110 std::string GetAdjacentDir(const char* suffix)
111 {
112   char libDir[_MAX_PATH];
113   GetModuleFileNameA(NULL, libDir, _MAX_PATH - 1);
114   char* lastSlash = strrchr(libDir, '\\');
115   if (!lastSlash) return "";
116   *lastSlash = '\0';
117   lastSlash = strrchr(libDir, '\\');
118   if (!lastSlash) return "";
119   strcpy(lastSlash + 1, suffix);
120   strcat_s(libDir, "\\");
121   return std::string(libDir);
122 }
123
124 bool FindJVMInEnvVar(const char* envVarName, bool& result)
125 {
126   char envVarValue[_MAX_PATH];
127   if (GetEnvironmentVariableA(envVarName, envVarValue, _MAX_PATH - 1))
128   {
129     if (FindValidJVM(envVarValue))
130     {
131       if (Is64BitJRE(jvmPath) != need64BitJRE) return false;
132       result = true;
133     }
134     else
135     {
136       char buf[_MAX_PATH];
137       sprintf_s(buf, "The environment variable %s (with the value of %s) does not point to a valid JVM installation.",
138         envVarName, envVarValue);
139       std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
140       MessageBoxA(NULL, buf, error.c_str(), MB_OK);
141       result = false;
142     }
143     return true;
144   }
145   return false;
146 }
147
148 bool FindJVMInSettings() {
149   TCHAR buffer[_MAX_PATH];
150   TCHAR copy[_MAX_PATH];
151
152   GetModuleFileName(NULL, buffer, _MAX_PATH);
153   std::wstring module(buffer);
154
155   if (LoadString(hInst, IDS_VM_OPTIONS_PATH, buffer, _MAX_PATH)) {
156     ExpandEnvironmentStrings(buffer, copy, _MAX_PATH - 1);
157     std::wstring path(copy);
158     path += L"\\config" + module.substr(module.find_last_of('\\')) + L".jdk";
159     FILE *f = _tfopen(path.c_str(), _T("rt"));
160     if (!f) return false;
161
162     char line[_MAX_PATH];
163     if (!fgets(line, _MAX_PATH, f)) {
164       fclose(f);
165       return false;
166     }
167
168     TrimLine(line);
169     fclose(f);
170
171     return FindValidJVM(line);
172   }
173   return false;
174 }
175
176 bool FindJVMInRegistryKey(const char* key, bool wow64_32)
177 {
178   HKEY hKey;
179   int flags = KEY_READ;
180   if (wow64_32) flags |= KEY_WOW64_32KEY;
181   if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey) != ERROR_SUCCESS) return false;
182   char javaHome[_MAX_PATH];
183   DWORD javaHomeSize = _MAX_PATH - 1;
184   bool success = false;
185   if (RegQueryValueExA(hKey, "JavaHome", NULL, NULL, (LPBYTE)javaHome, &javaHomeSize) == ERROR_SUCCESS)
186   {
187     success = FindValidJVM(javaHome);
188   }
189   RegCloseKey(hKey);
190   return success;
191 }
192
193 bool FindJVMInRegistryWithVersion(const char* version, bool wow64_32)
194 {
195   const char* keyName = LoadStdString(IDS_JDK_ONLY) == std::string("true")
196     ? "Java Development Kit"
197     : "Java Runtime Environment";
198
199   char buf[_MAX_PATH];
200   sprintf_s(buf, "Software\\JavaSoft\\%s\\%s", keyName, version);
201   return FindJVMInRegistryKey(buf, wow64_32);
202 }
203
204 bool FindJVMInRegistry()
205 {
206 #ifndef _M_X64
207   if (FindJVMInRegistryWithVersion("1.8", true))
208     return true;
209   if (FindJVMInRegistryWithVersion("1.7", true))
210     return true;
211   if (FindJVMInRegistryWithVersion("1.6", true))
212     return true;
213 #endif
214
215   if (FindJVMInRegistryWithVersion("1.8", false))
216     return true;
217   if (FindJVMInRegistryWithVersion("1.7", false))
218     return true;
219   if (FindJVMInRegistryWithVersion("1.6", false))
220     return true;
221   return false;
222 }
223
224 // The following code is taken from http://msdn.microsoft.com/en-us/library/ms684139(v=vs.85).aspx
225 // and provides a backwards compatible way to check if this application is a 32-bit process running
226 // on a 64-bit OS
227 typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
228
229 LPFN_ISWOW64PROCESS fnIsWow64Process;
230
231 BOOL IsWow64()
232 {
233   BOOL bIsWow64 = FALSE;
234
235   //IsWow64Process is not available on all supported versions of Windows.
236   //Use GetModuleHandle to get a handle to the DLL that contains the function
237   //and GetProcAddress to get a pointer to the function if available.
238
239   fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
240       GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
241
242   if (NULL != fnIsWow64Process)
243   {
244     fnIsWow64Process(GetCurrentProcess(), &bIsWow64);
245   }
246   return bIsWow64;
247 }
248
249 bool LocateJVM()
250 {
251   bool result;
252   if (FindJVMInEnvVar(LoadStdString(IDS_JDK_ENV_VAR).c_str(), result))
253   {
254     return result;
255   }
256
257   if (FindJVMInSettings()) return true;
258
259   std::vector<std::string> jrePaths;
260   if(need64BitJRE) jrePaths.push_back(GetAdjacentDir("jre64"));
261   jrePaths.push_back(GetAdjacentDir("jre"));
262   for(std::vector<std::string>::iterator it = jrePaths.begin(); it != jrePaths.end(); ++it) {
263     if (FindValidJVM((*it).c_str()) && Is64BitJRE(jvmPath) == need64BitJRE)
264     {
265       return true;
266     }
267   }
268
269   if (FindJVMInEnvVar("JAVA_HOME", result))
270   {
271     return result;
272   }
273
274   if (FindJVMInRegistry())
275   {
276     return true;
277   }
278
279   std::string jvmError;
280   jvmError = "No JVM installation found. Please install a " BITS_STR " JDK.\n"
281     "If you already have a JDK installed, define a JAVA_HOME variable in\n"
282     "Computer > System Properties > System Settings > Environment Variables.";
283
284   if (IsWow64())
285   {
286     // If WoW64, this means we are running a 32-bit program on 64-bit Windows. This may explain
287     // why we couldn't locate the JVM.
288     jvmError += "\n\nNOTE: We have detected that you are running a 64-bit version of the "
289         "Windows operating system but are running the 32-bit executable. This "
290         "can prevent you from finding a 64-bit installation of Java. Consider running "
291         "the 64-bit version instead, if this is the problem you're encountering.";
292   }
293
294   std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
295   MessageBoxA(NULL, jvmError.c_str(),  error.c_str(), MB_OK);
296   return false;
297 }
298
299 void TrimLine(char* line)
300 {
301   char *p = line + strlen(line) - 1;
302   if (p >= line && *p == '\n')
303   {
304     *p-- = '\0';
305   }
306   while (p >= line && (*p == ' ' || *p == '\t'))
307   {
308     *p-- = '\0';
309   }
310 }
311
312 bool LoadVMOptionsFile(const TCHAR* path, std::vector<std::string>& vmOptionLines)
313 {
314   FILE *f = _tfopen(path, _T("rt"));
315   if (!f) return false;
316
317   char line[_MAX_PATH];
318   while (fgets(line, _MAX_PATH, f))
319   {
320     TrimLine(line);
321     if (line[0] == '#') continue;
322     if (strcmp(line, "-server") == 0)
323     {
324       bServerJVM = true;
325     }
326     else if (strlen(line) > 0)
327     {
328       vmOptionLines.push_back(line);
329     }
330   }
331   fclose(f);
332
333   return true;
334 }
335
336 std::string FindToolsJar()
337 {
338   std::string toolsJarPath = jvmPath;
339   size_t lastSlash = toolsJarPath.rfind('\\');
340   if (lastSlash != std::string::npos)
341   {
342     toolsJarPath = toolsJarPath.substr(0, lastSlash + 1) + "lib\\tools.jar";
343     if (FileExists(toolsJarPath))
344     {
345       return toolsJarPath;
346     }
347   }
348   return "";
349 }
350
351 std::string CollectLibJars(const std::string& jarList)
352 {
353   std::string libDir = GetAdjacentDir("lib");
354   if (libDir.size() == 0 || !FileExists(libDir))
355   {
356     return "";
357   }
358
359   std::string result;
360   int pos = 0;
361   while (pos < jarList.size())
362   {
363     int delimiterPos = jarList.find(';', pos);
364     if (delimiterPos == std::string::npos)
365     {
366       delimiterPos = jarList.size();
367     }
368     if (result.size() > 0)
369     {
370       result += ";";
371     }
372     result += libDir;
373     result += jarList.substr(pos, delimiterPos - pos);
374     pos = delimiterPos + 1;
375   }
376   return result;
377 }
378
379 std::string BuildClassPath()
380 {
381   std::string classpathLibs = LoadStdString(IDS_CLASSPATH_LIBS);
382   std::string result = CollectLibJars(classpathLibs);
383
384   std::string toolsJar = FindToolsJar();
385   if (toolsJar.size() > 0)
386   {
387     result += ";";
388     result += toolsJar;
389   }
390
391   return result;
392 }
393
394 bool AddClassPathOptions(std::vector<std::string>& vmOptionLines)
395 {
396   std::string classPath = BuildClassPath();
397   if (classPath.size() == 0) return false;
398   vmOptionLines.push_back(std::string("-Djava.class.path=") + classPath);
399
400   std::string bootClassPathLibs = LoadStdString(IDS_BOOTCLASSPATH_LIBS);
401   std::string bootClassPath = CollectLibJars(bootClassPathLibs);
402   if (bootClassPath.size() > 0)
403   {
404     vmOptionLines.push_back(std::string("-Xbootclasspath/a:") + bootClassPath);
405   }
406
407   return true;
408 }
409
410 //return VMOptions from one of the files in the following order:
411 //$<IDE-NAME>_VM_OPTIONS
412 //$CONFIG_DIRECTORY/<ide-name>[64][.exe].vmoptions
413 //bin/<ide-name>[64][.exe].vmoptions
414 bool FindValidVMOptions(std::vector<std::wstring> files, std::wstring& used, std::vector<std::string>& vmOptionLines)
415 {
416   if (files.size() != 0)
417   {
418     for (int i = 0; i < files.size(); i++)
419     {
420       if (GetFileAttributes(files[i].c_str()) != INVALID_FILE_ATTRIBUTES)
421       {
422         if (LoadVMOptionsFile(files[i].c_str(), vmOptionLines))
423         {
424           used += (used.size() ? L"," : L"") + files[i];
425           return true;
426         }
427       }
428     }
429   }
430   return false;
431 }
432
433 void AddPredefinedVMOptions(std::vector<std::string>& vmOptionLines)
434 {
435   std::string vmOptions = LoadStdString(IDS_VM_OPTIONS);
436   while (vmOptions.size() > 0)
437   {
438     int pos = vmOptions.find(' ');
439     if (pos == std::string::npos) pos = vmOptions.size();
440     vmOptionLines.push_back(vmOptions.substr(0, pos));
441     while (pos < vmOptions.size() && vmOptions[pos] == ' ') pos++;
442     vmOptions = vmOptions.substr(pos);
443   }
444
445   char propertiesFile[_MAX_PATH];
446   if (GetEnvironmentVariableA(LoadStdString(IDS_PROPS_ENV_VAR).c_str(), propertiesFile, _MAX_PATH))
447   {
448     vmOptionLines.push_back(std::string("-Didea.properties.file=") + propertiesFile);
449   }
450 }
451
452 bool LoadVMOptions()
453 {
454   TCHAR buffer[_MAX_PATH];
455   TCHAR copy[_MAX_PATH];
456
457   std::vector<std::wstring> files;
458
459   GetModuleFileName(NULL, buffer, _MAX_PATH);
460   std::wstring module(buffer);
461
462   if (LoadString(hInst, IDS_VM_OPTIONS_ENV_VAR, buffer, _MAX_PATH))
463   {
464     if (GetEnvironmentVariableW(buffer, copy, _MAX_PATH)) {
465       ExpandEnvironmentStrings(copy, buffer, _MAX_PATH);
466       files.push_back(std::wstring(buffer));
467     }
468   }
469
470   if (LoadString(hInst, IDS_VM_OPTIONS_PATH, buffer, _MAX_PATH))
471   {
472     ExpandEnvironmentStrings(buffer, copy, _MAX_PATH - 1);
473     std::wstring selector(copy);
474     files.push_back(selector + module.substr(module.find_last_of('\\')) + L".vmoptions");
475   }
476
477   files.push_back(module + L".vmoptions");
478   std::wstring used;
479   std::vector<std::string> vmOptionLines;
480
481   if (!FindValidVMOptions(files, used, vmOptionLines))
482   {
483     std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
484     MessageBoxA(NULL, "Cannot find VM options file", error.c_str(), MB_OK);
485     return false;
486   }
487
488   vmOptionLines.push_back(std::string("-Djb.vmOptionsFile=") + EncodeWideACP(used));
489
490   if (!AddClassPathOptions(vmOptionLines)) return false;
491   AddPredefinedVMOptions(vmOptionLines);
492
493   vmOptionCount = vmOptionLines.size();
494   vmOptions = (JavaVMOption*)malloc(vmOptionCount * sizeof(JavaVMOption));
495   for (int i = 0; i < vmOptionLines.size(); i++)
496   {
497     vmOptions[i].optionString = _strdup(vmOptionLines[i].c_str());
498     vmOptions[i].extraInfo = 0;
499   }
500
501   return true;
502 }
503
504 bool LoadJVMLibrary()
505 {
506   std::string dllName(jvmPath);
507   std::string binDir = dllName + "\\bin";
508   std::string serverDllName = binDir + "\\server\\jvm.dll";
509   std::string clientDllName = binDir + "\\client\\jvm.dll";
510   if ((bServerJVM && FileExists(serverDllName)) || !FileExists(clientDllName))
511   {
512     dllName = serverDllName;
513   }
514   else
515   {
516     dllName = clientDllName;
517   }
518
519   // ensure we can find msvcr100.dll which is located in jre/bin directory; jvm.dll depends on it.
520   SetCurrentDirectoryA(binDir.c_str());
521   hJVM = LoadLibraryA(dllName.c_str());
522   if (hJVM)
523   {
524     pCreateJavaVM = (JNI_createJavaVM) GetProcAddress(hJVM, "JNI_CreateJavaVM");
525   }
526   if (!pCreateJavaVM)
527   {
528     std::string jvmError = "Failed to load JVM DLL ";
529     jvmError += dllName.c_str();
530     jvmError += "\n"
531         "If you already have a " BITS_STR " JDK installed, define a JAVA_HOME variable in "
532         "Computer > System Properties > System Settings > Environment Variables.";
533     std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
534     MessageBoxA(NULL, jvmError.c_str(), error.c_str(), MB_OK);
535     return false;
536   }
537   return true;
538 }
539
540 bool CreateJVM()
541 {
542   JavaVMInitArgs initArgs;
543   initArgs.version = JNI_VERSION_1_2;
544   initArgs.options = vmOptions;
545   initArgs.nOptions = vmOptionCount;
546   initArgs.ignoreUnrecognized = JNI_FALSE;
547
548   int result = pCreateJavaVM(&jvm, &env, &initArgs);
549
550   for (int i = 0; i < vmOptionCount; i++)
551   {
552     free(vmOptions[i].optionString);
553   }
554   free(vmOptions);
555   vmOptions = NULL;
556
557   if (result != JNI_OK)
558   {
559     std::stringstream buf;
560
561     buf << "Failed to create JVM: error code " << result << ".\n";
562     buf << "JVM Path: " << jvmPath << "\n";
563     buf << "If you already have a " BITS_STR " JDK installed, define a JAVA_HOME variable in \n";
564     buf << "Computer > System Properties > System Settings > Environment Variables.";
565     std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
566     MessageBoxA(NULL, buf.str().c_str(), error.c_str(), MB_OK);
567   }
568
569   return result == JNI_OK;
570 }
571
572 jobjectArray PrepareCommandLine()
573 {
574   int numArgs;
575   LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &numArgs);
576   jclass stringClass = env->FindClass("java/lang/String");
577   jobjectArray args = env->NewObjectArray(numArgs - (nativesplash ? 2 : 1), stringClass, NULL);
578   for (int i = 1, k = 0; i < numArgs; i++)
579   {
580     const wchar_t* arg = argv[i];
581     if (_wcsicmp(arg, _T("/nativesplash")) == 0) continue;
582     env->SetObjectArrayElement(args, k++, env->NewString((const jchar *)arg, wcslen(argv[i])));
583   }
584   return args;
585 }
586
587 bool RunMainClass()
588 {
589   std::string mainClassName = LoadStdString(IDS_MAIN_CLASS);
590   jclass mainClass = env->FindClass(mainClassName.c_str());
591   if (!mainClass)
592   {
593     char buf[_MAX_PATH + 256];
594     sprintf_s(buf, "Could not find main class %s", mainClassName.c_str());
595     std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
596     MessageBoxA(NULL, buf, error.c_str(), MB_OK);
597     return false;
598   }
599
600   jmethodID mainMethod = env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;)V");
601   if (!mainMethod)
602   {
603     std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
604     MessageBoxA(NULL, "Could not find main method", error.c_str(), MB_OK);
605     return false;
606   }
607
608   jobjectArray args = PrepareCommandLine();
609   env->CallStaticVoidMethod(mainClass, mainMethod, args);
610   jthrowable exc = env->ExceptionOccurred();
611   if (exc)
612   {
613     std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
614     MessageBoxA(NULL, "Error invoking main method", error.c_str(), MB_OK);
615   }
616
617   return true;
618 }
619
620 void CallCommandLineProcessor(const std::wstring& curDir, const std::wstring& args)
621 {
622   JNIEnv *env;
623   JavaVMAttachArgs attachArgs;
624   attachArgs.version = JNI_VERSION_1_2;
625   attachArgs.name = "WinLauncher external command processing thread";
626   attachArgs.group = NULL;
627   jvm->AttachCurrentThread((void**)&env, &attachArgs);
628
629   std::string processorClassName = LoadStdString(IDS_COMMAND_LINE_PROCESSOR_CLASS);
630   jclass processorClass = env->FindClass(processorClassName.c_str());
631   if (processorClass)
632   {
633     jmethodID processMethodID = env->GetStaticMethodID(processorClass, "processWindowsLauncherCommandLine", "(Ljava/lang/String;Ljava/lang/String;)V");
634     if (processMethodID)
635     {
636       jstring jCurDir = env->NewString((const jchar *)curDir.c_str(), curDir.size());
637       jstring jArgs = env->NewString((const jchar *)args.c_str(), args.size());
638       env->CallStaticVoidMethod(processorClass, processMethodID, jCurDir, jArgs);
639       jthrowable exc = env->ExceptionOccurred();
640       if (exc)
641       {
642         MessageBox(NULL, _T("Error sending command line to existing instance"), _T("Error"), MB_OK);
643       }
644     }
645   }
646
647   jvm->DetachCurrentThread();
648 }
649
650 DWORD WINAPI SingleInstanceThread(LPVOID args)
651 {
652   while (true)
653   {
654     WaitForSingleObject(hEvent, INFINITE);
655     if (terminating) break;
656
657     wchar_t *view = static_cast<wchar_t *>(MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0));
658     if (!view) continue;
659     std::wstring command(view);
660     int pos = command.find('\n');
661     if (pos >= 0)
662     {
663       std::wstring curDir = command.substr(0, pos);
664       std::wstring args = command.substr(pos + 1);
665
666       CallCommandLineProcessor(curDir, args);
667     }
668
669     UnmapViewOfFile(view);
670   }
671   return 0;
672 }
673
674 void SendCommandLineToFirstInstance()
675 {
676   wchar_t curDir[_MAX_PATH];
677   GetCurrentDirectoryW(_MAX_PATH - 1, curDir);
678   std::wstring command(curDir);
679   command += _T("\n");
680   command += GetCommandLineW();
681
682   void *view = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
683   if (view)
684   {
685     memcpy(view, command.c_str(), (command.size() + 1) * sizeof(wchar_t));
686     UnmapViewOfFile(view);
687   }
688   SetEvent(hEvent);
689 }
690
691 bool CheckSingleInstance()
692 {
693   char moduleFileName[_MAX_PATH];
694   GetModuleFileNameA(NULL, moduleFileName, _MAX_PATH - 1);
695   for (char *p = moduleFileName; *p; p++)
696   {
697     if (*p == ':' || *p == '\\') *p = '_';
698   }
699   std::string mappingName = std::string("IntelliJLauncherMapping.") + moduleFileName;
700   std::string eventName = std::string("IntelliJLauncherEvent.") + moduleFileName;
701
702   hEvent = CreateEventA(NULL, FALSE, FALSE, eventName.c_str());
703
704   hFileMapping = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, mappingName.c_str());
705   if (!hFileMapping)
706   {
707     hFileMapping = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, FILE_MAPPING_SIZE,
708       mappingName.c_str());
709     return true;
710   }
711   else
712   {
713     SendCommandLineToFirstInstance();
714     CloseHandle(hFileMapping);
715     CloseHandle(hEvent);
716     return false;
717   }
718 }
719
720 void DrawSplashImage(HWND hWnd)
721 {
722   HBITMAP hSplashBitmap = (HBITMAP)GetWindowLongPtr(hWnd, GWLP_USERDATA);
723   PAINTSTRUCT ps;
724   HDC hDC = BeginPaint(hWnd, &ps);
725   HDC hMemDC = CreateCompatibleDC(hDC);
726   HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hSplashBitmap);
727   BITMAP splashBitmap;
728   GetObject(hSplashBitmap, sizeof(splashBitmap), &splashBitmap);
729   BitBlt(hDC, 0, 0, splashBitmap.bmWidth, splashBitmap.bmHeight, hMemDC, 0, 0, SRCCOPY);
730   SelectObject(hMemDC, hOldBmp);
731   DeleteDC(hMemDC);
732   EndPaint(hWnd, &ps);
733 }
734
735 LRESULT CALLBACK SplashScreenWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
736 {
737   switch (uMsg)
738   {
739   case WM_PAINT:
740     DrawSplashImage(hWnd);
741     break;
742   }
743   return DefWindowProc(hWnd, uMsg, wParam, lParam);
744 }
745
746 const TCHAR splashClassName[] = _T("IntelliJLauncherSplash");
747
748 void RegisterSplashScreenWndClass()
749 {
750   WNDCLASSEX wcx;
751   wcx.cbSize = sizeof(wcx);
752   wcx.style = 0;
753   wcx.lpfnWndProc = SplashScreenWndProc;
754   wcx.cbClsExtra = 0;
755   wcx.cbWndExtra = 0;
756   wcx.hInstance = hInst;
757   wcx.hIcon = 0;
758   wcx.hCursor = LoadCursor(NULL, IDC_WAIT);
759   wcx.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
760   wcx.lpszMenuName = 0;
761   wcx.lpszClassName = splashClassName;
762   wcx.hIconSm = 0;
763
764   RegisterClassEx(&wcx);
765 }
766
767 HWND ShowSplashScreenWindow(HBITMAP hSplashBitmap)
768 {
769   RECT workArea;
770   SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
771   BITMAP splashBitmap;
772   GetObject(hSplashBitmap, sizeof(splashBitmap), &splashBitmap);
773   int x = workArea.left + ((workArea.right - workArea.left) - splashBitmap.bmWidth) / 2;
774   int y = workArea.top + ((workArea.bottom - workArea.top) - splashBitmap.bmHeight) / 2;
775
776   HWND splashWindow = CreateWindowEx(WS_EX_TOOLWINDOW, splashClassName, splashClassName, WS_POPUP,
777     x, y, splashBitmap.bmWidth, splashBitmap.bmHeight, NULL, NULL, NULL, NULL);
778   SetWindowLongPtr(splashWindow, GWLP_USERDATA, (LONG_PTR)hSplashBitmap);
779   ShowWindow(splashWindow, SW_SHOW);
780   UpdateWindow(splashWindow);
781   return splashWindow;
782 }
783
784 DWORD parentProcId;
785 HANDLE parentProcHandle;
786
787 BOOL IsParentProcessRunning(HANDLE hProcess)
788 {
789   if (hProcess == NULL) return FALSE;
790   DWORD ret = WaitForSingleObject(hProcess, 0);
791   return ret == WAIT_TIMEOUT;
792 }
793
794 BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
795 {
796   DWORD procId = 0;
797   GetWindowThreadProcessId(hWnd, &procId);
798   if (parentProcId == procId)
799   {
800     WINDOWINFO wi;
801     wi.cbSize = sizeof(WINDOWINFO);
802     GetWindowInfo(hWnd, &wi);
803     if ((wi.dwStyle & WS_VISIBLE) != 0)
804     {
805       HWND *phNewWindow = (HWND *)lParam;
806       *phNewWindow = hWnd;
807       return FALSE;
808     }
809   }
810   return TRUE;
811 }
812
813 DWORD WINAPI SplashScreen(HBITMAP hSplashBitmap)
814 {
815   RegisterSplashScreenWndClass();
816   HWND splashWindow = ShowSplashScreenWindow(hSplashBitmap);
817   MSG msg;
818   while (true)
819   {
820     while (PeekMessage(&msg, splashWindow, 0, 0, PM_REMOVE))
821     {
822       TranslateMessage(&msg);
823       DispatchMessage(&msg);
824     }
825     Sleep(50);
826     HWND hNewWindow = NULL;
827     EnumWindows(EnumWindowsProc, (LPARAM)&hNewWindow);
828     if (hNewWindow)
829     {
830       BringWindowToTop(hNewWindow);
831       Sleep(100);
832       DeleteObject(hSplashBitmap);
833       DestroyWindow(splashWindow);
834       break;
835     }
836     if (!IsParentProcessRunning(parentProcHandle)) break;
837   }
838   return 0;
839 }
840
841 void StartSplashProcess()
842 {
843   TCHAR ownPath[_MAX_PATH];
844   TCHAR params[_MAX_PATH];
845
846   PROCESS_INFORMATION splashProcessInformation;
847   STARTUPINFO startupInfo;
848   memset(&splashProcessInformation, 0, sizeof(splashProcessInformation));
849   memset(&startupInfo, 0, sizeof(startupInfo));
850   startupInfo.cb = sizeof(startupInfo);
851   startupInfo.dwFlags = STARTF_USESHOWWINDOW;
852   startupInfo.wShowWindow = SW_SHOW;
853
854   GetModuleFileName(NULL, ownPath, (sizeof(ownPath)));
855   _snwprintf(params, _MAX_PATH, _T("SPLASH %d"), GetCurrentProcessId());
856   if (CreateProcess(ownPath, params, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &splashProcessInformation))
857   {
858     CloseHandle(splashProcessInformation.hProcess);
859     CloseHandle(splashProcessInformation.hThread);
860   }
861 }
862
863 int APIENTRY _tWinMain(HINSTANCE hInstance,
864                        HINSTANCE hPrevInstance,
865                        LPTSTR    lpCmdLine,
866                        int       nCmdShow)
867 {
868   UNREFERENCED_PARAMETER(hPrevInstance);
869
870   hInst = hInstance;
871
872   if (__argc == 2 && _wcsicmp(__wargv[0], _T("SPLASH")) == 0)
873   {
874     HBITMAP hSplashBitmap = static_cast<HBITMAP>(LoadImage(hInst, MAKEINTRESOURCE(IDB_SPLASH), IMAGE_BITMAP, 0, 0, 0));
875     if (hSplashBitmap)
876     {
877       parentProcId = _wtoi(__wargv[1]);
878       parentProcHandle = OpenProcess(SYNCHRONIZE, FALSE, parentProcId);
879       if (IsParentProcessRunning(parentProcHandle)) SplashScreen(hSplashBitmap);
880     }
881     CloseHandle(parentProcHandle);
882     return 0;
883   }
884
885   if (!CheckSingleInstance()) return 1;
886
887   if (nativesplash = wcsstr(lpCmdLine, _T("/nativesplash")) != NULL) StartSplashProcess();
888
889   if (!LocateJVM()) return 1;
890   if (!LoadVMOptions()) return 1;
891   if (!LoadJVMLibrary()) return 1;
892   if (!CreateJVM()) return 1;
893
894   hSingleInstanceWatcherThread = CreateThread(NULL, 0, SingleInstanceThread, NULL, 0, NULL);
895
896   if (!RunMainClass()) return 1;
897
898   jvm->DestroyJavaVM();
899
900   terminating = true;
901   SetEvent(hEvent);
902   WaitForSingleObject(hSingleInstanceWatcherThread, INFINITE);
903   CloseHandle(hEvent);
904   CloseHandle(hFileMapping);
905
906   return 0;
907 }