1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "appxlocalengine.h"
41 #include "appxengine_p.h"
42
43 #include "utils.h"
44
45 #include <QtCore/QDateTime>
46 #include <QtCore/QDir>
47 #include <QtCore/QDirIterator>
48 #include <QtCore/QFile>
49 #include <QtCore/QFileInfo>
50 #include <QtCore/QLoggingCategory>
51 #include <QtCore/QStandardPaths>
52 #include <QtCore/QOperatingSystemVersion>
53
54 #include <ShlObj.h>
55 #include <Shlwapi.h>
56 #include <wsdevlicensing.h>
57 #include <AppxPackaging.h>
58 #include <wrl.h>
59 #include <windows.applicationmodel.h>
60 #include <windows.management.deployment.h>
61
62 using namespace Microsoft::WRL;
63 using namespace Microsoft::WRL::Wrappers;
64 using namespace ABI::Windows::Foundation;
65 using namespace ABI::Windows::Management::Deployment;
66 using namespace ABI::Windows::ApplicationModel;
67 using namespace ABI::Windows::System;
68
69 typedef IAsyncOperationWithProgressCompletedHandler<DeploymentResult *, DeploymentProgress> DeploymentResultHandler;
70 typedef IAsyncOperationWithProgress<DeploymentResult *, DeploymentProgress> DeploymentOperation;
71
72 QT_USE_NAMESPACE
73
74 // Set a break handler for gracefully breaking long-running ops
75 static bool g_ctrlReceived = false;
76 static bool g_handleCtrl = false;
ctrlHandler(DWORD type)77 static BOOL WINAPI ctrlHandler(DWORD type)
78 {
79 switch (type) {
80 case CTRL_C_EVENT:
81 case CTRL_CLOSE_EVENT:
82 case CTRL_LOGOFF_EVENT:
83 g_ctrlReceived = g_handleCtrl;
84 return g_handleCtrl;
85 case CTRL_BREAK_EVENT:
86 case CTRL_SHUTDOWN_EVENT:
87 default:
88 break;
89 }
90 return false;
91 }
92
sidForPackage(const QString & packageFamilyName)93 QString sidForPackage(const QString &packageFamilyName)
94 {
95 QString sid;
96 HKEY regKey;
97 LONG result = RegOpenKeyEx(
98 HKEY_CLASSES_ROOT,
99 L"Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Mappings",
100 0, KEY_READ, ®Key);
101 if (result != ERROR_SUCCESS) {
102 qCWarning(lcWinRtRunner) << "Unable to open registry key:" << qt_error_string(result);
103 return sid;
104 }
105
106 DWORD index = 0;
107 wchar_t subKey[MAX_PATH];
108 const wchar_t *packageFamilyNameW = wchar(packageFamilyName);
109 forever {
110 result = RegEnumKey(regKey, index++, subKey, MAX_PATH);
111 if (result != ERROR_SUCCESS)
112 break;
113 wchar_t moniker[MAX_PATH];
114 DWORD monikerSize = MAX_PATH;
115 result = RegGetValue(regKey, subKey, L"Moniker", RRF_RT_REG_SZ, NULL, moniker, &monikerSize);
116 if (result != ERROR_SUCCESS)
117 continue;
118 if (lstrcmp(moniker, packageFamilyNameW) == 0) {
119 sid = QString::fromWCharArray(subKey);
120 break;
121 }
122 }
123 RegCloseKey(regKey);
124 return sid;
125 }
126
127 class OutputDebugMonitor
128 {
129 public:
OutputDebugMonitor()130 OutputDebugMonitor()
131 : runLock(CreateEvent(NULL, FALSE, FALSE, NULL)), thread(0)
132 {
133 }
~OutputDebugMonitor()134 ~OutputDebugMonitor()
135 {
136 if (runLock) {
137 SetEvent(runLock);
138 CloseHandle(runLock);
139 }
140 if (thread) {
141 WaitForSingleObject(thread, INFINITE);
142 CloseHandle(thread);
143 }
144 }
start(const QString & packageFamilyName)145 void start(const QString &packageFamilyName)
146 {
147 if (thread) {
148 qCWarning(lcWinRtRunner) << "OutputDebugMonitor is already running.";
149 return;
150 }
151
152 package = packageFamilyName;
153
154 thread = CreateThread(NULL, 0, &monitor, this, NULL, NULL);
155 if (!thread) {
156 qCWarning(lcWinRtRunner) << "Unable to create thread for app debugging:"
157 << qt_error_string(GetLastError());
158 return;
159 }
160
161 return;
162 }
163 private:
monitor(LPVOID param)164 static DWORD __stdcall monitor(LPVOID param)
165 {
166 OutputDebugMonitor *that = static_cast<OutputDebugMonitor *>(param);
167
168 const QString handleBase = QStringLiteral("Local\\AppContainerNamedObjects\\")
169 + sidForPackage(that->package);
170 const QString eventName = handleBase + QStringLiteral("\\qdebug-event");
171 const QString ackEventName = handleBase + QStringLiteral("\\qdebug-event-ack");
172 const QString shmemName = handleBase + QStringLiteral("\\qdebug-shmem");
173
174 HANDLE event = CreateEvent(NULL, FALSE, FALSE, reinterpret_cast<LPCWSTR>(eventName.utf16()));
175 if (!event) {
176 qCWarning(lcWinRtRunner) << "Unable to open shared event for app debugging:"
177 << qt_error_string(GetLastError());
178 return 1;
179 }
180
181 HANDLE ackEvent = CreateEvent(NULL, FALSE, FALSE, reinterpret_cast<LPCWSTR>(ackEventName.utf16()));
182 if (!ackEvent) {
183 qCWarning(lcWinRtRunner) << "Unable to open shared acknowledge event for app debugging:"
184 << qt_error_string(GetLastError());
185 return 1;
186 }
187
188 HANDLE shmem = 0;
189 DWORD ret = 0;
190 quint64 size = 4096;
191 const quint32 resizeMessageType = QtInfoMsg + 1;
192 HANDLE handles[] = { that->runLock, event };
193 forever {
194 DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
195
196 // runLock set; exit thread
197 if (result == WAIT_OBJECT_0)
198 break;
199
200 if (result == WAIT_OBJECT_0 + 1) {
201 if (!shmem) {
202 shmem = OpenFileMapping(GENERIC_READ, FALSE,
203 reinterpret_cast<LPCWSTR>(shmemName.utf16()));
204 if (!shmem) {
205 qCWarning(lcWinRtRunner) << "Unable to open shared memory for app debugging:"
206 << qt_error_string(GetLastError());
207 ret = 1;
208 break;
209 }
210 }
211
212 const quint32 *data = reinterpret_cast<const quint32 *>(
213 MapViewOfFile(shmem, FILE_MAP_READ, 0, 0, size));
214 if (!data) {
215 qCWarning(lcWinRtRunner) << "Unable to map view of shared memory for app debugging:"
216 << qt_error_string(GetLastError());
217 ret = 1;
218 break;
219 }
220 const quint32 type = data[0];
221 // resize message received; Resize shared memory
222 if (type == resizeMessageType) {
223 size = (data[2] << 8) + data[1];
224 if (!UnmapViewOfFile(data)) {
225 qCWarning(lcWinRtRunner) << "Unable to unmap view of shared memory for app debugging:"
226 << qt_error_string(GetLastError());
227 ret = 1;
228 break;
229 }
230 if (shmem) {
231 if (!CloseHandle(shmem)) {
232 qCWarning(lcWinRtRunner) << "Unable to close shared memory handle:"
233 << qt_error_string(GetLastError());
234 ret = 1;
235 break;
236 }
237 shmem = 0;
238 }
239 SetEvent(ackEvent);
240 continue;
241 }
242
243 // debug event set; print message
244 QtMsgType messageType = static_cast<QtMsgType>(type);
245 QString message = QString::fromWCharArray(
246 reinterpret_cast<const wchar_t *>(data + 1));
247 UnmapViewOfFile(data);
248 SetEvent(ackEvent);
249 switch (messageType) {
250 default:
251 case QtDebugMsg:
252 qCDebug(lcWinRtRunnerApp, qPrintable(message));
253 break;
254 case QtWarningMsg:
255 qCWarning(lcWinRtRunnerApp, qPrintable(message));
256 break;
257 case QtCriticalMsg:
258 case QtFatalMsg:
259 qCCritical(lcWinRtRunnerApp, qPrintable(message));
260 break;
261 }
262 continue;
263 }
264
265 // An error occurred; exit thread
266 qCWarning(lcWinRtRunner) << "Debug output monitor error:"
267 << qt_error_string(GetLastError());
268 ret = 1;
269 break;
270 }
271 if (shmem)
272 CloseHandle(shmem);
273 if (event)
274 CloseHandle(event);
275 if (ackEvent)
276 CloseHandle(ackEvent);
277 return ret;
278 }
279 HANDLE runLock;
280 HANDLE thread;
281 QString package;
282 };
283 Q_GLOBAL_STATIC(OutputDebugMonitor, debugMonitor)
284
285 class AppxLocalEnginePrivate : public AppxEnginePrivate
286 {
287 public:
288 ComPtr<IPackageManager> packageManager;
289 ComPtr<IApplicationActivationManager> appLauncher;
290 ComPtr<IPackageDebugSettings> packageDebug;
291
292 Qt::HANDLE loopbackServerProcessHandle = INVALID_HANDLE_VALUE;
293
294 void retrieveInstalledPackages();
295 };
296
getManifestFile(const QString & fileName,QString * manifest=0)297 static bool getManifestFile(const QString &fileName, QString *manifest = 0)
298 {
299 if (!QFile::exists(fileName)) {
300 qCWarning(lcWinRtRunner) << fileName << "does not exist.";
301 return false;
302 }
303
304 // If it looks like an appx manifest, we're done
305 if (fileName.endsWith(QStringLiteral("AppxManifest.xml"))) {
306
307 if (manifest)
308 *manifest = fileName;
309 return true;
310 }
311
312 // If it looks like an executable, check that manifest is next to it
313 if (fileName.endsWith(QStringLiteral(".exe"))) {
314 QDir appDir = QFileInfo(fileName).absoluteDir();
315 QString manifestFileName = appDir.absoluteFilePath(QStringLiteral("AppxManifest.xml"));
316 if (!QFile::exists(manifestFileName)) {
317 qCWarning(lcWinRtRunner) << manifestFileName << "does not exist.";
318 return false;
319 }
320
321 if (manifest)
322 *manifest = manifestFileName;
323 return true;
324 }
325
326 // TODO: handle already-built package as well
327
328 qCWarning(lcWinRtRunner) << "Appx: unable to determine manifest for" << fileName << ".";
329 return false;
330 }
331
canHandle(Runner * runner)332 bool AppxLocalEngine::canHandle(Runner *runner)
333 {
334 return getManifestFile(runner->app());
335 }
336
create(Runner * runner)337 RunnerEngine *AppxLocalEngine::create(Runner *runner)
338 {
339 QScopedPointer<AppxLocalEngine> engine(new AppxLocalEngine(runner));
340 if (engine->d_ptr->hasFatalError)
341 return 0;
342
343 return engine.take();
344 }
345
deviceNames()346 QStringList AppxLocalEngine::deviceNames()
347 {
348 qCDebug(lcWinRtRunner) << __FUNCTION__;
349 return QStringList(QStringLiteral("local"));
350 }
351
352
353
toProcessorArchitecture(APPX_PACKAGE_ARCHITECTURE appxArch)354 static ProcessorArchitecture toProcessorArchitecture(APPX_PACKAGE_ARCHITECTURE appxArch)
355 {
356 switch (appxArch) {
357 case APPX_PACKAGE_ARCHITECTURE_X86:
358 return ProcessorArchitecture_X86;
359 case APPX_PACKAGE_ARCHITECTURE_ARM:
360 return ProcessorArchitecture_Arm;
361 case APPX_PACKAGE_ARCHITECTURE_X64:
362 return ProcessorArchitecture_X64;
363 case APPX_PACKAGE_ARCHITECTURE_NEUTRAL:
364 // fall-through intended
365 default:
366 return ProcessorArchitecture_Neutral;
367 }
368 }
369
AppxLocalEngine(Runner * runner)370 AppxLocalEngine::AppxLocalEngine(Runner *runner)
371 : AppxEngine(runner, new AppxLocalEnginePrivate)
372 {
373 Q_D(AppxLocalEngine);
374 if (d->hasFatalError)
375 return;
376 d->hasFatalError = true;
377
378 HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Management_Deployment_PackageManager).Get(),
379 &d->packageManager);
380 RETURN_VOID_IF_FAILED("Failed to instantiate package manager");
381
382 hr = CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER,
383 IID_IApplicationActivationManager, &d->appLauncher);
384 RETURN_VOID_IF_FAILED("Failed to instantiate application activation manager");
385
386 hr = CoCreateInstance(CLSID_PackageDebugSettings, nullptr, CLSCTX_INPROC_SERVER,
387 IID_IPackageDebugSettings, &d->packageDebug);
388 RETURN_VOID_IF_FAILED("Failed to instantiate package debug settings");
389
390 d->retrieveInstalledPackages();
391
392 // Set a break handler for gracefully exiting from long-running operations
393 SetConsoleCtrlHandler(&ctrlHandler, true);
394 d->hasFatalError = false;
395 }
396
~AppxLocalEngine()397 AppxLocalEngine::~AppxLocalEngine()
398 {
399 }
400
installPackage(IAppxManifestReader * reader,const QString & filePath)401 bool AppxLocalEngine::installPackage(IAppxManifestReader *reader, const QString &filePath)
402 {
403 Q_D(const AppxLocalEngine);
404 qCDebug(lcWinRtRunner).nospace().noquote()
405 << __FUNCTION__ << " \"" << QDir::toNativeSeparators(filePath) << '"';
406
407 HRESULT hr;
408 if (reader) {
409 ComPtr<IAppxManifestPackageId> packageId;
410 hr = reader->GetPackageId(&packageId);
411 RETURN_FALSE_IF_FAILED("Failed to get package ID from reader.");
412
413 LPWSTR name;
414 hr = packageId->GetName(&name);
415 RETURN_FALSE_IF_FAILED("Failed to get package name from package ID.");
416
417 const QString packageName = QString::fromWCharArray(name);
418 CoTaskMemFree(name);
419 if (d->installedPackages.contains(packageName)) {
420 qCDebug(lcWinRtRunner) << "The package" << packageName << "is already installed.";
421 return true;
422 }
423 }
424
425 const QString nativeFilePath = QDir::toNativeSeparators(QFileInfo(filePath).absoluteFilePath());
426 const bool addInsteadOfRegister = nativeFilePath.endsWith(QStringLiteral(".appx"),
427 Qt::CaseInsensitive);
428 ComPtr<IUriRuntimeClass> uri;
429 hr = d->uriFactory->CreateUri(hStringFromQString(nativeFilePath), &uri);
430 RETURN_FALSE_IF_FAILED("Failed to create an URI for the package");
431
432 ComPtr<DeploymentOperation> deploymentOperation;
433 if (addInsteadOfRegister) {
434 hr = d->packageManager->AddPackageAsync(uri.Get(), NULL, DeploymentOptions_None,
435 &deploymentOperation);
436 RETURN_FALSE_IF_FAILED("Failed to add package");
437 } else {
438 hr = d->packageManager->RegisterPackageAsync(uri.Get(), 0,
439 DeploymentOptions_DevelopmentMode,
440 &deploymentOperation);
441 RETURN_FALSE_IF_FAILED("Failed to start package registration");
442 }
443
444 HANDLE ev = CreateEvent(NULL, FALSE, FALSE, NULL);
445 hr = deploymentOperation->put_Completed(Callback<DeploymentResultHandler>([ev](DeploymentOperation *, AsyncStatus) {
446 SetEvent(ev);
447 return S_OK;
448 }).Get());
449 RETURN_FALSE_IF_FAILED("Could not register deployment completed callback.");
450 DWORD ret = WaitForSingleObjectEx(ev, 60000, FALSE);
451 CloseHandle(ev);
452 if (ret != WAIT_OBJECT_0) {
453 if (ret == WAIT_TIMEOUT)
454 qCWarning(lcWinRtRunner) << "Deployment did not finish within 15 seconds.";
455 else
456 qCWarning(lcWinRtRunner) << "Deployment finished event was not triggered.";
457 return false;
458 }
459
460 ComPtr<IDeploymentResult> results;
461 hr = deploymentOperation->GetResults(&results);
462 RETURN_FALSE_IF_FAILED("Failed to retrieve package registration results.");
463
464 HRESULT errorCode;
465 hr = results->get_ExtendedErrorCode(&errorCode);
466 RETURN_FALSE_IF_FAILED("Failed to retrieve extended error code.");
467
468 if (FAILED(errorCode)) {
469 if (HRESULT_CODE(errorCode) == ERROR_INSTALL_PREREQUISITE_FAILED) {
470 qCWarning(lcWinRtRunner) << "Unable to register package: A requirement for installation was not met. "
471 "Check that your Windows version matches TargetDeviceFamily's MinVersion set in your AppxManifest.xml.";
472 } else {
473 HString errorText;
474 if (SUCCEEDED(results->get_ErrorText(errorText.GetAddressOf()))) {
475 qCWarning(lcWinRtRunner) << "Unable to register package:"
476 << QString::fromWCharArray(errorText.GetRawBuffer(NULL));
477 }
478 }
479 if (HRESULT_CODE(errorCode) == ERROR_INSTALL_POLICY_FAILURE) {
480 // The user's license has expired. Give them the opportunity to renew it.
481 FILETIME expiration;
482 hr = AcquireDeveloperLicense(GetForegroundWindow(), &expiration);
483 RETURN_FALSE_IF_FAILED("Unable to renew developer license");
484 return install(false);
485 }
486 return false;
487 }
488
489 return SUCCEEDED(hr);
490 }
491
parseExitCode()492 bool AppxLocalEngine::parseExitCode()
493 {
494 Q_D(AppxLocalEngine);
495 const QString exitFileName(QStringLiteral("exitCode.tmp"));
496 bool ok = false;
497 QFile exitCodeFile(devicePath(QString::number(pid()).append(QStringLiteral(".pid"))));
498 if (exitCodeFile.open(QIODevice::ReadOnly)) {
499 d->exitCode = exitCodeFile.readAll().toInt(&ok);
500 exitCodeFile.close();
501 exitCodeFile.remove();
502 }
503 if (!ok && !GetExitCodeProcess(d->processHandle, &d->exitCode)) {
504 d->exitCode = UINT_MAX;
505 qCWarning(lcWinRtRunner).nospace() << "Failed to obtain process exit code.";
506 qCDebug(lcWinRtRunner, "GetLastError: 0x%x", GetLastError());
507 return false;
508 }
509 return true;
510 }
511
install(bool removeFirst)512 bool AppxLocalEngine::install(bool removeFirst)
513 {
514 Q_D(const AppxLocalEngine);
515 qCDebug(lcWinRtRunner) << __FUNCTION__;
516
517 ComPtr<IPackage> packageInformation;
518 HRESULT hr = d->packageManager->FindPackageByUserSecurityIdPackageFullName(
519 NULL, hStringFromQString(d->packageFullName), &packageInformation);
520 if (SUCCEEDED(hr) && packageInformation) {
521 qCWarning(lcWinRtRunner) << "Package already installed.";
522 if (removeFirst)
523 remove();
524 else
525 return true;
526 }
527
528 return installDependencies() && installPackage(nullptr, d->manifest);
529 }
530
remove()531 bool AppxLocalEngine::remove()
532 {
533 Q_D(const AppxLocalEngine);
534 qCDebug(lcWinRtRunner) << __FUNCTION__;
535
536 // ### TODO: use RemovePackageWithOptions to preserve previous state when re-installing
537 ComPtr<DeploymentOperation> deploymentOperation;
538 HRESULT hr = d->packageManager->RemovePackageAsync(hStringFromQString(d->packageFullName), &deploymentOperation);
539 RETURN_FALSE_IF_FAILED("Unable to start package removal");
540
541 HANDLE ev = CreateEvent(NULL, FALSE, FALSE, NULL);
542 hr = deploymentOperation->put_Completed(Callback<DeploymentResultHandler>([ev](DeploymentOperation *, AsyncStatus) {
543 SetEvent(ev);
544 return S_OK;
545 }).Get());
546 RETURN_FALSE_IF_FAILED("Could not register deployment completed callback.");
547 DWORD ret = WaitForSingleObjectEx(ev, 60000, FALSE);
548 CloseHandle(ev);
549 if (ret != WAIT_OBJECT_0) {
550 if (ret == WAIT_TIMEOUT)
551 qCWarning(lcWinRtRunner) << "Deployment did not finish within 15 seconds.";
552 else
553 qCWarning(lcWinRtRunner) << "Deployment finished event was not triggered.";
554 return false;
555 }
556
557 ComPtr<IAsyncInfo> asyncInfo;
558 hr = deploymentOperation.As(&asyncInfo);
559 RETURN_FALSE_IF_FAILED("Failed to cast deployment operation.");
560
561 AsyncStatus status;
562 hr = asyncInfo->get_Status(&status);
563 RETURN_FALSE_IF_FAILED("Failed to retrieve deployment operation's status.");
564
565 if (status != Completed) {
566 qCWarning(lcWinRtRunner) << "Unable to remove package.";
567 return false;
568 }
569
570 return true;
571 }
572
start()573 bool AppxLocalEngine::start()
574 {
575 Q_D(AppxLocalEngine);
576 qCDebug(lcWinRtRunner) << __FUNCTION__;
577
578 const QString launchArguments =
579 (d->runner->arguments() << QStringLiteral("-qdevel")).join(QLatin1Char(' '));
580 DWORD pid;
581 const QString activationId = d->packageFamilyName + QStringLiteral("!App");
582 HRESULT hr = d->appLauncher->ActivateApplication(wchar(activationId),
583 wchar(launchArguments), AO_NONE, &pid);
584 RETURN_FALSE_IF_FAILED("Failed to activate application");
585 d->pid = qint64(pid);
586 CloseHandle(d->processHandle);
587 d->processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, pid);
588
589 return true;
590 }
591
enableDebugging(const QString & debuggerExecutable,const QString & debuggerArguments)592 bool AppxLocalEngine::enableDebugging(const QString &debuggerExecutable, const QString &debuggerArguments)
593 {
594 Q_D(AppxLocalEngine);
595
596 const QString &debuggerCommand = debuggerExecutable + QLatin1Char(' ') + debuggerArguments;
597 HRESULT hr = d->packageDebug->EnableDebugging(wchar(d->packageFullName),
598 wchar(debuggerCommand),
599 NULL);
600 RETURN_FALSE_IF_FAILED("Failed to enable debugging for application");
601 return true;
602 }
603
disableDebugging()604 bool AppxLocalEngine::disableDebugging()
605 {
606 Q_D(AppxLocalEngine);
607
608 HRESULT hr = d->packageDebug->DisableDebugging(wchar(d->packageFullName));
609 RETURN_FALSE_IF_FAILED("Failed to disable debugging for application");
610
611 return true;
612 }
613
setLoopbackExemptClientEnabled(bool enabled)614 bool AppxLocalEngine::setLoopbackExemptClientEnabled(bool enabled)
615 {
616 Q_D(AppxLocalEngine);
617 qCDebug(lcWinRtRunner) << __FUNCTION__ << enabled;
618
619 if (!enabled) {
620 PACKAGE_EXECUTION_STATE state;
621 HRESULT hr = d->packageDebug->GetPackageExecutionState(wchar(d->packageFullName), &state);
622 RETURN_FALSE_IF_FAILED("Failed to get package execution state");
623 if (state != PES_TERMINATED && state != PES_UNKNOWN) {
624 qCWarning(lcWinRtRunner) << "Cannot unregister loopback exemption for running program."
625 << "Please use checknetisolation.exe to check/clean up the exemption list.";
626 return false;
627 }
628 }
629
630 QByteArray stdOut;
631 QByteArray stdErr;
632 unsigned long exitCode = 0;
633 QString errorMessage;
634 QStringList arguments;
635 const QString binary = QStringLiteral("checknetisolation.exe");
636 arguments << QStringLiteral("LoopbackExempt")
637 << (enabled ? QStringLiteral("-a") : QStringLiteral("-d"))
638 << QStringLiteral("-p=") + sidForPackage(d->packageFamilyName);
639 if (!runProcess(binary, arguments, QString(), &exitCode, &stdOut, &stdErr, &errorMessage)) {
640 qCWarning(lcWinRtRunner) << "Could not run" << binary;
641 return false;
642 }
643 if (exitCode) {
644 if (errorMessage.isEmpty()) {
645 errorMessage = binary + QStringLiteral(" returned ") + QString::number(exitCode)
646 + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr);
647 }
648 qCWarning(lcWinRtRunner) << errorMessage;
649 return false;
650 }
651 return true;
652 }
653
setLoopbackExemptServerEnabled(bool enabled)654 bool AppxLocalEngine::setLoopbackExemptServerEnabled(bool enabled)
655 {
656 Q_D(AppxLocalEngine);
657 const QOperatingSystemVersion minimal
658 = QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393);
659 if (QOperatingSystemVersion::current() < minimal) {
660 qCWarning(lcWinRtRunner) << "Cannot enable loopback exemption for servers. If you want"
661 << "to use this feature please update to a Windows version >="
662 << minimal;
663 return false;
664 }
665
666 if (enabled) {
667 QStringList arguments;
668 const QString binary = QStringLiteral("checknetisolation.exe");
669 arguments << QStringLiteral("LoopbackExempt") << QStringLiteral("-is")
670 << QStringLiteral("-p=") + sidForPackage(d->packageFamilyName);
671 if (!runElevatedBackgroundProcess(binary, arguments, &d->loopbackServerProcessHandle)) {
672 qCWarning(lcWinRtRunner) << "Could not start" << binary;
673 return false;
674 }
675 } else {
676 if (d->loopbackServerProcessHandle != INVALID_HANDLE_VALUE) {
677 if (!TerminateProcess(d->loopbackServerProcessHandle, 0)) {
678 qCWarning(lcWinRtRunner) << "Could not terminate loopbackexempt debug session";
679 return false;
680 }
681 if (!CloseHandle(d->loopbackServerProcessHandle)) {
682 qCWarning(lcWinRtRunner) << "Could not close loopbackexempt debug session process handle";
683 return false;
684 }
685 } else {
686 qCWarning(lcWinRtRunner) << "loopbackexempt debug session could not be found";
687 return false;
688 }
689 }
690 return true;
691 }
692
setLoggingRules(const QByteArray & rules)693 bool AppxLocalEngine::setLoggingRules(const QByteArray &rules)
694 {
695 qCDebug(lcWinRtRunner) << __FUNCTION__;
696
697 QDir loggingIniDir(devicePath(QLatin1String("QtProject")));
698 if (!loggingIniDir.exists() && !loggingIniDir.mkpath(QStringLiteral("."))) {
699 qCWarning(lcWinRtRunner) << "Could not create" << loggingIniDir;
700 return false;
701 }
702 QFile loggingIniFile(loggingIniDir.absolutePath().append(QLatin1String("/qtlogging.ini")));
703 if (loggingIniFile.exists() && !loggingIniFile.remove()) {
704 qCWarning(lcWinRtRunner) << loggingIniFile << "already exists.";
705 return false;
706 }
707 if (!loggingIniFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
708 qCWarning(lcWinRtRunner) << "Could not open" << loggingIniFile << "for writing.";
709 return false;
710 }
711
712 QTextStream stream(&loggingIniFile);
713 stream << "[Rules]\n" << rules;
714
715 return true;
716 }
717
718
suspend()719 bool AppxLocalEngine::suspend()
720 {
721 Q_D(AppxLocalEngine);
722 qCDebug(lcWinRtRunner) << __FUNCTION__;
723
724 HRESULT hr = d->packageDebug->Suspend(wchar(d->packageFullName));
725 RETURN_FALSE_IF_FAILED("Failed to suspend application");
726
727 return true;
728 }
729
waitForFinished(int secs)730 bool AppxLocalEngine::waitForFinished(int secs)
731 {
732 Q_D(AppxLocalEngine);
733 qCDebug(lcWinRtRunner) << __FUNCTION__;
734
735 debugMonitor->start(d->packageFamilyName);
736
737 g_handleCtrl = true;
738 int time = 0;
739 forever {
740 PACKAGE_EXECUTION_STATE state;
741 HRESULT hr = d->packageDebug->GetPackageExecutionState(wchar(d->packageFullName), &state);
742 RETURN_FALSE_IF_FAILED("Failed to get package execution state");
743 qCDebug(lcWinRtRunner) << "Current execution state:" << state;
744 if (state == PES_TERMINATED || state == PES_UNKNOWN)
745 break;
746
747 ++time;
748 if ((secs && time > secs) || g_ctrlReceived) {
749 g_handleCtrl = false;
750 return false;
751 }
752
753 Sleep(1000); // Wait one second between checks
754 qCDebug(lcWinRtRunner) << "Waiting for app to quit - msecs to go:" << secs - time;
755 }
756 g_handleCtrl = false;
757
758 if (!GetExitCodeProcess(d->processHandle, &d->exitCode))
759 d->exitCode = UINT_MAX;
760
761 return true;
762 }
763
stop()764 bool AppxLocalEngine::stop()
765 {
766 Q_D(AppxLocalEngine);
767 qCDebug(lcWinRtRunner) << __FUNCTION__;
768
769 // ### We won't have a process handle if we didn't start the app. We can look it up
770 // using a process snapshot, or by calling start if we know the process is already running.
771 // For now, simply continue normally, but don't fetch the exit code.
772 if (!d->processHandle)
773 qCDebug(lcWinRtRunner) << "No handle to the process; the exit code won't be available.";
774
775 if (d->processHandle && !parseExitCode()) {
776 return false;
777 }
778
779 if (!d->processHandle || d->exitCode == STILL_ACTIVE) {
780 HRESULT hr = d->packageDebug->TerminateAllProcesses(wchar(d->packageFullName));
781 RETURN_FALSE_IF_FAILED("Failed to terminate package process");
782
783 if (d->processHandle && !GetExitCodeProcess(d->processHandle, &d->exitCode))
784 d->exitCode = UINT_MAX;
785 }
786
787 return true;
788 }
789
devicePath(const QString & relativePath) const790 QString AppxLocalEngine::devicePath(const QString &relativePath) const
791 {
792 Q_D(const AppxLocalEngine);
793 qCDebug(lcWinRtRunner) << __FUNCTION__;
794
795 // Return a path safe for passing to the application
796 QDir localAppDataPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
797 const QString path = localAppDataPath.absoluteFilePath(
798 QStringLiteral("Packages/") + d->packageFamilyName
799 + QStringLiteral("/LocalState/") + relativePath);
800 return QDir::toNativeSeparators(path);
801 }
802
sendFile(const QString & localFile,const QString & deviceFile)803 bool AppxLocalEngine::sendFile(const QString &localFile, const QString &deviceFile)
804 {
805 qCDebug(lcWinRtRunner) << __FUNCTION__;
806
807 // Both files are local, just use QFile
808 QFile source(localFile);
809
810 // Remove the destination, or copy will fail
811 if (QFileInfo(source) != QFileInfo(deviceFile))
812 QFile::remove(deviceFile);
813
814 bool result = source.copy(deviceFile);
815 if (!result) {
816 qCWarning(lcWinRtRunner).nospace().noquote()
817 << "Unable to sendFile: " << source.errorString();
818 }
819
820 return result;
821 }
822
receiveFile(const QString & deviceFile,const QString & localFile)823 bool AppxLocalEngine::receiveFile(const QString &deviceFile, const QString &localFile)
824 {
825 qCDebug(lcWinRtRunner) << __FUNCTION__;
826
827 // Both files are local, so just reverse the sendFile arguments
828 return sendFile(deviceFile, localFile);
829 }
830
extensionSdkPath() const831 QString AppxLocalEngine::extensionSdkPath() const
832 {
833 const QByteArray extensionSdkDirRaw = qgetenv("ExtensionSdkDir");
834 if (extensionSdkDirRaw.isEmpty()) {
835 qCWarning(lcWinRtRunner) << "The environment variable ExtensionSdkDir is not set.";
836 return QString();
837 }
838 return QString::fromLocal8Bit(extensionSdkDirRaw);
839 }
840
retrieveInstalledPackages()841 void AppxLocalEnginePrivate::retrieveInstalledPackages()
842 {
843 qCDebug(lcWinRtRunner) << __FUNCTION__;
844
845 ComPtr<ABI::Windows::Foundation::Collections::IIterable<Package*>> packages;
846 HRESULT hr = packageManager->FindPackagesByUserSecurityId(NULL, &packages);
847 RETURN_VOID_IF_FAILED("Failed to find packages");
848
849 ComPtr<ABI::Windows::Foundation::Collections::IIterator<Package*>> pkgit;
850 hr = packages->First(&pkgit);
851 RETURN_VOID_IF_FAILED("Failed to get package iterator");
852
853 boolean hasCurrent;
854 hr = pkgit->get_HasCurrent(&hasCurrent);
855 while (SUCCEEDED(hr) && hasCurrent) {
856 ComPtr<IPackage> pkg;
857 hr = pkgit->get_Current(&pkg);
858 RETURN_VOID_IF_FAILED("Failed to get current package");
859
860 ComPtr<IPackageId> pkgId;
861 hr = pkg->get_Id(&pkgId);
862 RETURN_VOID_IF_FAILED("Failed to get package id");
863
864 HString name;
865 hr = pkgId->get_Name(name.GetAddressOf());
866 RETURN_VOID_IF_FAILED("Failed retrieve package name");
867
868 ProcessorArchitecture architecture;
869 if (packageArchitecture == ProcessorArchitecture_Neutral) {
870 architecture = packageArchitecture;
871 } else {
872 hr = pkgId->get_Architecture(&architecture);
873 RETURN_VOID_IF_FAILED("Failed to retrieve package architecture");
874 }
875
876 const QString pkgName = QStringFromHString(name.Get());
877 qCDebug(lcWinRtRunner) << "found installed package" << pkgName;
878
879 if (architecture == packageArchitecture)
880 installedPackages.insert(pkgName);
881
882 hr = pkgit->MoveNext(&hasCurrent);
883 }
884 }
885