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, ¶ms);
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