JUCE
JUCE copied to clipboard
Windows vs Mac command line handling differs
Building an app for Mac & Windows on JUCE 5.4.4. We subclass JUCEApplication and override initialise() and anotherInstanceStarted().
moreThanOnceInstanceAllowedis returningfalse
Let's say we launch the app myapp here are some args
Observed on Mac:
initialisecalled with 1 string:here are some argsanotherInstanceStarted4 times, once for each arg:here,are,some,args
Observed on Windows:
initialisecalled with 1 string:here are some argsanotherInstanceStartedis not called at launch
Of course we can work around this, but would be nice to have a single code path to deal with, since we also want to take arguments from subsequent launches
This is due to the way that command line arguments are handled on Unix vs Windows. You should be able to use the JUCEApplicationBase::getCommandLineParameters() or JUCEApplicationBase::getCommandLineParameterArray() methods inside initialise() to get a platform-independent String or StringArray containing the args.
Hi @ed95, thanks for the info, but that is not exactly the issue I'm describing. It is rather whether or not anotherInstanceStarted is called on initial launch of the app.
Yeah this is problematic as it seems a new juce app started from a URL scheme cannot piggyback into the same app running, and will always start a new instance. This makes it impossible to implement deeplinking between the app and an externally launched URL.
Will this be addressed once and for all @ed95 ?
In case someone else needs the solution, the Juce Team doesn't seem to care much.
diff --git a/juce/modules/juce_events/juce_events.cpp b/juce/modules/juce_events/juce_events.cpp
index f604f9033b804acf4d41cce70a640fbab9e0b158..3b4036042e2a0d8e59f7a7306b57466c9cc5db5b 100644
--- a/juce/modules/juce_events/juce_events.cpp
+++ b/juce/modules/juce_events/juce_events.cpp
@@ -51,6 +51,10 @@
#elif JUCE_LINUX || JUCE_BSD
#include <unistd.h>
+
+#elif JUCE_WINDOWS
+ #include <Psapi.h>
+
#endif
//==============================================================================
diff --git a/juce/modules/juce_events/native/juce_win32_Messaging.cpp b/juce/modules/juce_events/native/juce_win32_Messaging.cpp
index adfaf3b8c8e678cd952bdb882fc01336ede3a34e..f6695ee6a54fbbc92f9c192b9193561e0c8bd3e0 100644
--- a/juce/modules/juce_events/native/juce_win32_Messaging.cpp
+++ b/juce/modules/juce_events/native/juce_win32_Messaging.cpp
@@ -47,10 +47,15 @@ public:
{
messageWindow = std::make_unique<HiddenMessageWindow> (messageWindowName, (WNDPROC) messageWndProc);
juce_messageWindowHandle = messageWindow->getHWND();
+
+ handleAnotherInstanceStarted();
}
~InternalMessageQueue()
{
+ if (uniqueMutex)
+ CloseHandle (uniqueMutex);
+
juce_messageWindowHandle = nullptr;
clearSingletonInstance();
}
@@ -204,7 +209,29 @@ private:
static void handleBroadcastMessage (const COPYDATASTRUCT* data)
{
- if (data != nullptr && data->dwData == broadcastMessageMagicNumber)
+ if (data == nullptr)
+ return;
+
+ if (data->dwData == anotherInstanceStartedMagicNumber)
+ {
+ String commandLine (CharPointer_UTF32 ((const CharPointer_UTF32::CharType*) data->lpData),
+ data->cbData / sizeof (CharPointer_UTF32::CharType));
+
+ auto commandLineArgs = StringArray::fromTokens (commandLine, true);
+ if (! commandLineArgs.isEmpty())
+ commandLineArgs.remove (0);
+
+ struct AnotherInstanceStartedMessage : public CallbackMessage
+ {
+ AnotherInstanceStartedMessage (String message) : message (std::move(message)) {}
+ void messageCallback() override { JUCEApplicationBase::getInstance()->anotherInstanceStarted (message); }
+
+ String message;
+ };
+
+ (new AnotherInstanceStartedMessage (commandLineArgs.joinIntoString (" ")))->post();
+ }
+ else if (data->dwData == broadcastMessageMagicNumber)
{
struct BroadcastMessage : public CallbackMessage
{
@@ -220,6 +247,104 @@ private:
}
}
+ static HWND getProcessMainWindow (DWORD processId)
+ {
+ HWND hwnd = NULL;
+
+ do
+ {
+ hwnd = FindWindowEx (NULL, hwnd, NULL, NULL);
+
+ DWORD dwPID = 0;
+ GetWindowThreadProcessId (hwnd, &dwPID);
+ if (dwPID == processId && GetWindow (hwnd, GW_OWNER) == (HWND) 0 && ! IsWindowVisible (hwnd))
+ return hwnd;
+ }
+ while (hwnd != NULL);
+ }
+
+ static HWND findFirstApplicationInstanceWindow (const String& applicationName)
+ {
+ constexpr int maxNumberOfProcesses = 8192;
+
+ DWORD numberOfProcessesNeeded;
+ HeapBlock<DWORD> processIds (maxNumberOfProcesses, true);
+
+ if (EnumProcesses (processIds, maxNumberOfProcesses * sizeof(DWORD), &numberOfProcessesNeeded))
+ {
+ const auto numberOfProcesses = static_cast<int> (numberOfProcessesNeeded / sizeof(DWORD));
+
+ WCHAR pathBuffer[MAX_PATH];
+
+ for (int processIndex = 0; processIndex < numberOfProcesses; ++processIndex)
+ {
+ const auto processId = processIds[processIndex];
+ if (processId == 0)
+ continue;
+
+ HMODULE moduleHandle;
+ DWORD sizeNeeded;
+
+ HANDLE processHandle = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
+ if (processHandle == NULL)
+ continue;
+
+ if (EnumProcessModules (processHandle, &moduleHandle, sizeof(moduleHandle), &sizeNeeded))
+ {
+ juce::zeromem (pathBuffer, MAX_PATH * sizeof(WCHAR));
+
+ auto moduleResult = GetModuleFileNameEx (processHandle, moduleHandle, pathBuffer, sizeof(pathBuffer) / sizeof(WCHAR));
+ if (moduleResult == 0)
+ {
+ CloseHandle (processHandle);
+ continue;
+ }
+
+ const auto processPath = File (String (pathBuffer));
+ if (processPath.getFileNameWithoutExtension().compareIgnoreCase (applicationName) == 0)
+ {
+ HWND hwnd = getProcessMainWindow (processId);
+ if (hwnd != NULL)
+ {
+ CloseHandle (processHandle);
+ return hwnd;
+ }
+ }
+ }
+
+ CloseHandle (processHandle);
+ }
+ }
+
+ return NULL;
+ }
+
+ void handleAnotherInstanceStarted()
+ {
+ TCHAR szFileName[MAX_PATH] = { 0 };
+ GetModuleFileName (NULL, szFileName, MAX_PATH);
+
+ auto applicationName = File (String (szFileName)).getFileNameWithoutExtension();
+
+ String uniqueMutexName;
+ uniqueMutexName << "__unique_lock_" << applicationName;
+
+ uniqueMutex = CreateMutex (NULL, FALSE, uniqueMutexName.toWideCharPointer());
+ if (uniqueMutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS)
+ {
+ const auto commandLineArguments = String (GetCommandLine());
+
+ COPYDATASTRUCT dataToSend = { 0 };
+ dataToSend.dwData = anotherInstanceStartedMagicNumber;
+ dataToSend.cbData = (DWORD) (((size_t) commandLineArguments.length() + 1) * sizeof (CharPointer_UTF32::CharType));
+ dataToSend.lpData = (void*) commandLineArguments.toUTF32().getAddress();
+
+ HWND firstApplicationInstanceWindow = findFirstApplicationInstanceWindow (applicationName);
+ if (firstApplicationInstanceWindow != NULL)
+ SendMessage (firstApplicationInstanceWindow, WM_COPYDATA, 0, (LPARAM) &dataToSend);
+ }
+ }
+
void dispatchMessages()
{
ReferenceCountedArray<MessageManager::MessageBase> messagesToDispatch;
@@ -244,6 +369,7 @@ private:
//==============================================================================
static constexpr unsigned int customMessageID = WM_USER + 123;
static constexpr unsigned int broadcastMessageMagicNumber = 0xc403;
+ static constexpr unsigned int anotherInstanceStartedMagicNumber = 0xf403;
static const TCHAR messageWindowName[];
std::unique_ptr<HiddenMessageWindow> messageWindow;
@@ -251,6 +377,8 @@ private:
CriticalSection lock;
ReferenceCountedArray<MessageManager::MessageBase> messageQueue;
+ HANDLE uniqueMutex;
+
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalMessageQueue)
};