1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Windows main function of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** BSD License Usage
18 ** Alternatively, you may use this file under the terms of the BSD license
19 ** as follows:
20 **
21 ** "Redistribution and use in source and binary forms, with or without
22 ** modification, are permitted provided that the following conditions are
23 ** met:
24 **   * Redistributions of source code must retain the above copyright
25 **     notice, this list of conditions and the following disclaimer.
26 **   * Redistributions in binary form must reproduce the above copyright
27 **     notice, this list of conditions and the following disclaimer in
28 **     the documentation and/or other materials provided with the
29 **     distribution.
30 **   * Neither the name of The Qt Company Ltd nor the names of its
31 **     contributors may be used to endorse or promote products derived
32 **     from this software without specific prior written permission.
33 **
34 **
35 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46 **
47 ** $QT_END_LICENSE$
48 **
49 ****************************************************************************/
50 
51 /*
52   This file contains the code in the qtmain library for WinRT.
53   qtmain contains the WinRT startup code and is required for
54   linking to the Qt DLL.
55 
56   When a Windows application starts, the WinMain function is
57   invoked. This WinMain creates the WinRT application
58   container, which in turn calls the application's main()
59   entry point within the newly created GUI thread.
60 */
61 
62 extern "C" {
63     int main(int, char **);
64 }
65 
66 #include <qbytearray.h>
67 #include <qstring.h>
68 #include <qdir.h>
69 #include <qstandardpaths.h>
70 #include <qfunctions_winrt.h>
71 #include <qcoreapplication.h>
72 #include <qmutex.h>
73 
74 #include <wrl.h>
75 #include <Windows.ApplicationModel.core.h>
76 #include <windows.ui.xaml.h>
77 #include <windows.ui.xaml.controls.h>
78 
79 using namespace ABI::Windows::ApplicationModel;
80 using namespace ABI::Windows::ApplicationModel::Activation;
81 using namespace ABI::Windows::ApplicationModel::Core;
82 using namespace ABI::Windows::Foundation;
83 using namespace ABI::Windows::UI;
84 using namespace Microsoft::WRL;
85 using namespace Microsoft::WRL::Wrappers;
86 
87 #define qHString(x) Wrappers::HString::MakeReference(x).Get()
88 #define CoreApplicationClass RuntimeClass_Windows_ApplicationModel_Core_CoreApplication
89 typedef ITypedEventHandler<CoreApplicationView *, Activation::IActivatedEventArgs *> ActivatedHandler;
90 
91 const quint32 resizeMessageType = QtInfoMsg + 1;
92 
93 const PCWSTR shmemName = L"qdebug-shmem";
94 const PCWSTR eventName = L"qdebug-event";
95 const PCWSTR ackEventName = L"qdebug-event-ack";
96 
97 static QtMessageHandler defaultMessageHandler;
devMessageHandler(QtMsgType type,const QMessageLogContext & context,const QString & message)98 static void devMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
99 {
100     static HANDLE shmem = 0;
101     static HANDLE event = 0;
102     static HANDLE ackEvent = 0;
103 
104     static QMutex messageMutex;
105     QMutexLocker locker(&messageMutex);
106 
107     static quint64 mappingSize = 4096;
108     const quint32 copiedMessageLength = message.length() + 1;
109     // Message format is message type + message. We need the message's length + 4 bytes for the type
110     const quint64 copiedMessageSize = copiedMessageLength * sizeof(wchar_t) + sizeof(quint32);
111     if (copiedMessageSize > mappingSize) {
112         if (!shmem)
113             shmem = CreateFileMappingFromApp(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, mappingSize, shmemName);
114         Q_ASSERT_X(shmem, Q_FUNC_INFO, "Could not create file mapping");
115 
116         quint32 *data = reinterpret_cast<quint32 *>(MapViewOfFileFromApp(shmem, FILE_MAP_WRITE, 0, mappingSize));
117         Q_ASSERT_X(data, Q_FUNC_INFO, "Could not map size file");
118 
119         mappingSize = copiedMessageSize;
120 
121         memcpy(data, (void *)&resizeMessageType, sizeof(quint32));
122         memcpy(data + 1, (void *)&mappingSize, sizeof(quint64));
123         UnmapViewOfFile(data);
124         SetEvent(event);
125         WaitForSingleObjectEx(ackEvent, INFINITE, false);
126         if (shmem) {
127             if (!CloseHandle(shmem))
128                 Q_ASSERT_X(false, Q_FUNC_INFO, "Could not close shared file handle");
129             shmem = 0;
130         }
131     }
132 
133     if (!shmem)
134         shmem = CreateFileMappingFromApp(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, mappingSize, shmemName);
135     if (!event)
136         event = CreateEventEx(NULL, eventName, 0, EVENT_ALL_ACCESS);
137     if (!ackEvent)
138         ackEvent = CreateEventEx(NULL, ackEventName, 0, EVENT_ALL_ACCESS);
139 
140     Q_ASSERT_X(shmem, Q_FUNC_INFO, "Could not create file mapping");
141     Q_ASSERT_X(event, Q_FUNC_INFO, "Could not create debug event");
142 
143     void *data = MapViewOfFileFromApp(shmem, FILE_MAP_WRITE, 0, mappingSize);
144     Q_ASSERT_X(data, Q_FUNC_INFO, "Could not map file");
145 
146     memset(data, quint32(type), sizeof(quint32));
147     memcpy_s(static_cast<quint32 *>(data) + 1, mappingSize - sizeof(quint32),
148              message.data(), copiedMessageLength * sizeof(wchar_t));
149     UnmapViewOfFile(data);
150     SetEvent(event);
151     WaitForSingleObjectEx(ackEvent, INFINITE, false);
152     locker.unlock();
153     defaultMessageHandler(type, context, message);
154 }
155 
156 class QActivationEvent : public QEvent
157 {
158 public:
QActivationEvent(IInspectable * args)159     explicit QActivationEvent(IInspectable *args)
160         : QEvent(QEvent::WinEventAct)
161     {
162         setAccepted(false);
163         args->AddRef();
164         d = reinterpret_cast<QEventPrivate *>(args);
165     }
166 
~QActivationEvent()167     ~QActivationEvent() {
168         IUnknown *args = reinterpret_cast<IUnknown *>(d);
169         args->Release();
170         d = nullptr;
171     }
172 };
173 
174 class AppContainer : public RuntimeClass<Xaml::IApplicationOverrides>
175 {
176 public:
AppContainer()177     AppContainer()
178     {
179         ComPtr<Xaml::IApplicationFactory> applicationFactory;
180         HRESULT hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Xaml_Application).Get(),
181                                             IID_PPV_ARGS(&applicationFactory));
182         Q_ASSERT_SUCCEEDED(hr);
183 
184         hr = applicationFactory->CreateInstance(this, &base, &core);
185         RETURN_VOID_IF_FAILED("Failed to create application container instance");
186 
187         pidFile = INVALID_HANDLE_VALUE;
188     }
189 
~AppContainer()190     ~AppContainer()
191     {
192     }
193 
exec()194     int exec()
195     {
196         mainThread = CreateThread(NULL, 0, [](void *param) -> DWORD {
197             AppContainer *app = reinterpret_cast<AppContainer *>(param);
198             int argc = app->args.count() - 1;
199             char **argv = app->args.data();
200             const int res = main(argc, argv);
201             if (app->pidFile != INVALID_HANDLE_VALUE) {
202                 const QByteArray resString = QByteArray::number(res);
203                 WriteFile(app->pidFile, reinterpret_cast<LPCVOID>(resString.constData()),
204                           resString.size(), NULL, NULL);
205                 FlushFileBuffers(app->pidFile);
206                 CloseHandle(app->pidFile);
207             }
208             app->core->Exit();
209             return res;
210         }, this, CREATE_SUSPENDED, nullptr);
211         Q_ASSERT_X(mainThread, Q_FUNC_INFO, "Could not create Qt main thread");
212 
213         HRESULT hr;
214         ComPtr<Xaml::IApplicationStatics> appStatics;
215         hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Xaml_Application).Get(),
216                                     IID_PPV_ARGS(&appStatics));
217         Q_ASSERT_SUCCEEDED(hr);
218         hr = appStatics->Start(Callback<Xaml::IApplicationInitializationCallback>([](Xaml::IApplicationInitializationCallbackParams *) {
219             return S_OK;
220         }).Get());
221         Q_ASSERT_SUCCEEDED(hr);
222 
223         WaitForSingleObjectEx(mainThread, INFINITE, FALSE);
224         DWORD exitCode;
225         GetExitCodeThread(mainThread, &exitCode);
226         return exitCode;
227     }
228 
229 private:
activatedLaunch(IInspectable * activateArgs)230     HRESULT activatedLaunch(IInspectable *activateArgs) {
231         // Check if an application instance is already running
232         // This is mostly needed for Windows Phone and file pickers
233         QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
234         if (dispatcher) {
235             QCoreApplication::postEvent(dispatcher, new QActivationEvent(activateArgs));
236             return S_OK;
237         }
238 
239         QCoreApplication *app = QCoreApplication::instance();
240 
241         // Check whether the app already runs
242         if (!app) {
243             // I*EventArgs have no launch arguments, hence we
244             // need to prepend the application binary manually
245             wchar_t fn[513];
246             DWORD res = GetModuleFileName(0, fn, 512);
247 
248             if (SUCCEEDED(res))
249                 args.prepend(QString::fromWCharArray(fn, res).toUtf8().data());
250 
251             ResumeThread(mainThread);
252 
253             // We give main() a max of 100ms to create an application object.
254             // No eventhandling needs to happen at that point, all we want is
255             // append our activation event
256             int iterations = 0;
257             while (true) {
258                 app = QCoreApplication::instance();
259                 if (app || iterations++ > 10)
260                     break;
261                 Sleep(10);
262             }
263         }
264 
265         if (app)
266             QCoreApplication::postEvent(app, new QActivationEvent(activateArgs));
267         return S_OK;
268     }
269 
OnActivated(IActivatedEventArgs * args)270     HRESULT __stdcall OnActivated(IActivatedEventArgs *args) override
271     {
272         return activatedLaunch(args);
273     }
274 
OnLaunched(ILaunchActivatedEventArgs * launchArgs)275     HRESULT __stdcall OnLaunched(ILaunchActivatedEventArgs *launchArgs) override
276     {
277         ComPtr<IPrelaunchActivatedEventArgs> preArgs;
278         HRESULT hr = launchArgs->QueryInterface(preArgs.GetAddressOf());
279         if (SUCCEEDED(hr)) {
280             boolean prelaunched;
281             preArgs->get_PrelaunchActivated(&prelaunched);
282             if (prelaunched)
283                 return S_OK;
284         }
285 
286         commandLine = QString::fromWCharArray(GetCommandLine()).toUtf8();
287 
288         HString launchCommandLine;
289         launchArgs->get_Arguments(launchCommandLine.GetAddressOf());
290         if (launchCommandLine.IsValid()) {
291             quint32 launchCommandLineLength;
292             const wchar_t *launchCommandLineBuffer = launchCommandLine.GetRawBuffer(&launchCommandLineLength);
293             if (!commandLine.isEmpty() && launchCommandLineLength)
294                 commandLine += ' ';
295             if (launchCommandLineLength)
296                 commandLine += QString::fromWCharArray(launchCommandLineBuffer, launchCommandLineLength).toUtf8();
297         }
298         if (!commandLine.isEmpty())
299             args.append(commandLine.data());
300 
301         bool quote = false;
302         bool escape = false;
303         for (int i = 0; i < commandLine.size(); ++i) {
304             switch (commandLine.at(i)) {
305             case '\\':
306                 escape = true;
307                 break;
308             case '"':
309                 if (escape) {
310                     escape = false;
311                     break;
312                 }
313                 quote = !quote;
314                 commandLine[i] = '\0';
315                 break;
316             case ' ':
317                 if (quote)
318                     break;
319                 commandLine[i] = '\0';
320                 if (!args.isEmpty() && args.last() && args.last()[0] != '\0')
321                     args.append(commandLine.data() + i + 1);
322                 // fall through
323             default:
324                 if (!args.isEmpty() && args.last() && args.last()[0] == '\0')
325                     args.last() = commandLine.data() + i;
326                 escape = false; // only quotes are escaped
327                 break;
328             }
329         }
330 
331         if (args.count() >= 2 && args.at(1) && strncmp(args.at(1), "-ServerName:", 12) == 0)
332             args.remove(1);
333 
334         bool develMode = false;
335         bool debugWait = false;
336         for (int i = args.count() - 1; i >= 0; --i) {
337             if (!args.at(i))
338                 continue;
339 
340             const char *arg = args.at(i);
341             if (strcmp(arg, "-qdevel") == 0) {
342                 develMode = true;
343                 args.remove(i);
344             } else if (strcmp(arg, "-qdebug") == 0) {
345                 debugWait = true;
346                 args.remove(i);
347             }
348         }
349         args.append(nullptr);
350 
351         if (develMode) {
352             // Write a PID file to help runner
353             const QString pidFileName = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))
354                     .absoluteFilePath(QString::asprintf("%u.pid", uint(GetCurrentProcessId())));
355             CREATEFILE2_EXTENDED_PARAMETERS params = {
356                 sizeof(CREATEFILE2_EXTENDED_PARAMETERS),
357                 FILE_ATTRIBUTE_NORMAL
358             };
359             pidFile = CreateFile2(reinterpret_cast<LPCWSTR>(pidFileName.utf16()),
360                         GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, &params);
361             // Install the develMode message handler
362             defaultMessageHandler = qInstallMessageHandler(devMessageHandler);
363         }
364         // Wait for debugger before continuing
365         if (debugWait) {
366             while (!IsDebuggerPresent())
367                 WaitForSingleObjectEx(GetCurrentThread(), 1, true);
368         }
369 
370         ResumeThread(mainThread);
371         return S_OK;
372     }
373 
OnFileActivated(IFileActivatedEventArgs * args)374     HRESULT __stdcall OnFileActivated(IFileActivatedEventArgs *args) override
375     {
376         return activatedLaunch(args);
377     }
378 
OnSearchActivated(ISearchActivatedEventArgs * args)379     HRESULT __stdcall OnSearchActivated(ISearchActivatedEventArgs *args) override
380     {
381         Q_UNUSED(args);
382         return S_OK;
383     }
384 
OnShareTargetActivated(IShareTargetActivatedEventArgs * args)385     HRESULT __stdcall OnShareTargetActivated(IShareTargetActivatedEventArgs *args) override
386     {
387         return activatedLaunch(args);
388     }
389 
OnFileOpenPickerActivated(IFileOpenPickerActivatedEventArgs * args)390     HRESULT __stdcall OnFileOpenPickerActivated(IFileOpenPickerActivatedEventArgs *args) override
391     {
392         Q_UNUSED(args);
393         return S_OK;
394     }
395 
OnFileSavePickerActivated(IFileSavePickerActivatedEventArgs * args)396     HRESULT __stdcall OnFileSavePickerActivated(IFileSavePickerActivatedEventArgs *args) override
397     {
398         Q_UNUSED(args);
399         return S_OK;
400     }
401 
OnCachedFileUpdaterActivated(ICachedFileUpdaterActivatedEventArgs * args)402     HRESULT __stdcall OnCachedFileUpdaterActivated(ICachedFileUpdaterActivatedEventArgs *args) override
403     {
404         Q_UNUSED(args);
405         return S_OK;
406     }
407 
OnWindowCreated(Xaml::IWindowCreatedEventArgs * args)408     HRESULT __stdcall OnWindowCreated(Xaml::IWindowCreatedEventArgs *args) override
409     {
410         Q_UNUSED(args);
411         return S_OK;
412     }
413 
414     ComPtr<Xaml::IApplicationOverrides> base;
415     ComPtr<Xaml::IApplication> core;
416     QByteArray commandLine;
417     QVarLengthArray<char *> args;
418     HANDLE mainThread{0};
419     HANDLE pidFile;
420 };
421 
422 // Main entry point for Appx containers
WinMain(HINSTANCE,HINSTANCE,LPSTR,int)423 int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
424 {
425     if (FAILED(RoInitialize(RO_INIT_MULTITHREADED)))
426         return 1;
427 
428     ComPtr<AppContainer> app = Make<AppContainer>();
429     return app->exec();
430 }
431