From 47ec82cc6adf991e0c2d1f83bfeb957adfd9e1fa Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Fri, 13 Jun 2014 11:53:11 +0300 Subject: winrtrunner: Move COM initialization into the private class This ensures Appx engine subclasses get COM initialized in the same way. Change-Id: Ib376842f808488353d19fe36c76bb2411332c0e4 Reviewed-by: Oliver Wolff --- src/winrtrunner/appxengine.cpp | 8 ++++---- src/winrtrunner/appxengine_p.h | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/winrtrunner/appxengine.cpp b/src/winrtrunner/appxengine.cpp index b79668bdb..0ef84655b 100644 --- a/src/winrtrunner/appxengine.cpp +++ b/src/winrtrunner/appxengine.cpp @@ -135,8 +135,10 @@ AppxEngine::AppxEngine(Runner *runner, AppxEnginePrivate *dd) : d_ptr(dd) { Q_D(AppxEngine); + if (d->hasFatalError) + return; + d->runner = runner; - d->hasFatalError = false; d->processHandle = NULL; d->pid = -1; d->exitCode = UINT_MAX; @@ -147,9 +149,7 @@ AppxEngine::AppxEngine(Runner *runner, AppxEnginePrivate *dd) return; } - HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - CHECK_RESULT_FATAL("Failed to initialize COM.", return); - + HRESULT hr; hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_Uri).Get(), IID_PPV_ARGS(&d->uriFactory)); CHECK_RESULT_FATAL("Failed to instantiate URI factory.", return); diff --git a/src/winrtrunner/appxengine_p.h b/src/winrtrunner/appxengine_p.h index dde97b73a..c2e4ad524 100644 --- a/src/winrtrunner/appxengine_p.h +++ b/src/winrtrunner/appxengine_p.h @@ -56,7 +56,21 @@ struct IAppxFactory; class AppxEnginePrivate { public: - virtual ~AppxEnginePrivate() { } + AppxEnginePrivate() + { + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (FAILED(hr)) { + qCWarning(lcWinRtRunner) << "Failed to initialize COM:" << qt_error_string(hr); + hasFatalError = true; + } + hasFatalError = false; + } + + virtual ~AppxEnginePrivate() + { + CoUninitialize(); + } + Runner *runner; bool hasFatalError; -- cgit v1.2.1 From f1d1e853d53bc677a912e221515397651d1369ef Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Thu, 5 Jun 2014 13:39:41 +0300 Subject: winrtrunner: Add an inherited method for creating packages This method can be used by AppxEngine subclasses for creating package (*.appx) files. Change-Id: I7aab9bced144353c3c9e8cf6fbb914bd2f5b7fbb Reviewed-by: Oliver Wolff --- src/winrtrunner/appxengine.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++ src/winrtrunner/appxengine.h | 1 + 2 files changed, 93 insertions(+) diff --git a/src/winrtrunner/appxengine.cpp b/src/winrtrunner/appxengine.cpp index 0ef84655b..a0b5ea653 100644 --- a/src/winrtrunner/appxengine.cpp +++ b/src/winrtrunner/appxengine.cpp @@ -343,3 +343,95 @@ bool AppxEngine::installDependencies() return true; } + +bool AppxEngine::createPackage(const QString &packageFileName) +{ + Q_D(AppxEngine); + + static QHash contentTypes; + if (contentTypes.isEmpty()) { + contentTypes.insert(QStringLiteral("dll"), QStringLiteral("application/x-msdownload")); + contentTypes.insert(QStringLiteral("exe"), QStringLiteral("application/x-msdownload")); + contentTypes.insert(QStringLiteral("png"), QStringLiteral("image/png")); + contentTypes.insert(QStringLiteral("xml"), QStringLiteral("vnd.ms-appx.manifest+xml")); + } + + // Check for package map, or create one if needed + QDir base = QFileInfo(d->manifest).absoluteDir(); + QFile packageFile(packageFileName); + + QHash files; + QFile mappingFile(base.absoluteFilePath(QStringLiteral("AppxManifest.map"))); + if (mappingFile.exists()) { + qCWarning(lcWinRtRunner) << "Creating package from mapping file:" << mappingFile.fileName(); + if (!mappingFile.open(QFile::ReadOnly)) { + qCWarning(lcWinRtRunner) << "Unable to read mapping file:" << mappingFile.errorString(); + return false; + } + + QRegExp pattern(QStringLiteral("^\"([^\"]*)\"\\s*\"([^\"]*)\"$")); + bool inFileSection = false; + while (!mappingFile.atEnd()) { + const QString line = QString::fromUtf8(mappingFile.readLine()).trimmed(); + if (line.startsWith(QLatin1Char('['))) { + inFileSection = line == QStringLiteral("[Files]"); + continue; + } + if (pattern.cap(2).compare(QStringLiteral("AppxManifest.xml"), Qt::CaseInsensitive) == 0) + continue; + if (inFileSection && pattern.indexIn(line) >= 0 && pattern.captureCount() == 2) { + QString inputFile = pattern.cap(1); + if (!QFile::exists(inputFile)) + inputFile = base.absoluteFilePath(inputFile); + files.insert(QDir::toNativeSeparators(inputFile), QDir::toNativeSeparators(pattern.cap(2))); + } + } + } else { + qCWarning(lcWinRtRunner) << "No mapping file exists. Only recognized files will be packaged."; + // Add executable + files.insert(QDir::toNativeSeparators(d->executable), QFileInfo(d->executable).fileName()); + // Add potential Qt files + const QStringList fileTypes = QStringList() + << QStringLiteral("*.dll") << QStringLiteral("*.png") << QStringLiteral("*.qm") + << QStringLiteral("*.qml") << QStringLiteral("*.qmldir"); + QDirIterator dirIterator(base.absolutePath(), fileTypes, QDir::Files, QDirIterator::Subdirectories); + while (dirIterator.hasNext()) { + const QString filePath = dirIterator.next(); + files.insert(QDir::toNativeSeparators(filePath), QDir::toNativeSeparators(base.relativeFilePath(filePath))); + } + } + + ComPtr outputStream; + HRESULT hr = SHCreateStreamOnFile(wchar(packageFile.fileName()), STGM_WRITE|STGM_CREATE, &outputStream); + RETURN_FALSE_IF_FAILED("Failed to create package file output stream"); + + ComPtr hashMethod; + hr = CreateUri(L"http://www.w3.org/2001/04/xmlenc#sha512", Uri_CREATE_CANONICALIZE, 0, &hashMethod); + RETURN_FALSE_IF_FAILED("Failed to create the has method URI"); + + APPX_PACKAGE_SETTINGS packageSettings = { FALSE, hashMethod.Get() }; + ComPtr packageWriter; + hr = d->packageFactory->CreatePackageWriter(outputStream.Get(), &packageSettings, &packageWriter); + RETURN_FALSE_IF_FAILED("Failed to create package writer"); + + for (QHash::const_iterator i = files.begin(); i != files.end(); ++i) { + qCDebug(lcWinRtRunner) << "Packaging" << i.key() << i.value(); + ComPtr inputStream; + hr = SHCreateStreamOnFile(wchar(i.key()), STGM_READ, &inputStream); + RETURN_FALSE_IF_FAILED("Failed to open file"); + const QString contentType = contentTypes.value(QFileInfo(i.key()).suffix().toLower(), + QStringLiteral("application/octet-stream")); + hr = packageWriter->AddPayloadFile(wchar(i.value()), wchar(contentType), + APPX_COMPRESSION_OPTION_NORMAL, inputStream.Get()); + RETURN_FALSE_IF_FAILED("Failed to add payload file"); + } + + // Write out the manifest + ComPtr manifestStream; + hr = SHCreateStreamOnFile(wchar(d->manifest), STGM_READ, &manifestStream); + RETURN_FALSE_IF_FAILED("Failed to open manifest for packaging"); + hr = packageWriter->Close(manifestStream.Get()); + RETURN_FALSE_IF_FAILED("Failed to finalize package."); + + return true; +} diff --git a/src/winrtrunner/appxengine.h b/src/winrtrunner/appxengine.h index 0390eeaec..2e1b74e1b 100644 --- a/src/winrtrunner/appxengine.h +++ b/src/winrtrunner/appxengine.h @@ -67,6 +67,7 @@ protected: virtual bool installPackage(IAppxManifestReader *reader, const QString &filePath) = 0; bool installDependencies(); + bool createPackage(const QString &packageFileName); static bool getManifestFile(const QString &fileName, QString *manifest = 0); QScopedPointer d_ptr; -- cgit v1.2.1 From aa35d132010f4410d72e30d03e3dd713c7a2241d Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Thu, 5 Jun 2014 17:13:01 +0300 Subject: winrtrunner: Support Windows Phone 8.1 This allows the runner to deploy to Windows Phone 8.1 devices and emulators. The following limitations are noted: - App argument passing doesn't currently work. A workaround could be to modify the entry point in the AppxManifest.xml, which would also require changes to qtmain. WP8.0 already uses a similar workaround, but this is messy and should be avoided if possible. - Querying for the application state doesn't work, so -wait won't exit if the app stops. A workaround for this exists in WP8.0, by passing the -qdevel parameter to the app, but this is invasive. If an alternative can't be found, this can be fixed in a follow- - Stopping applications doesn't appear to work (the API call succeeds, but the app remains running). One workaround is to -remove the app. - Getting the exit code still isn't possible. Task-number: QTBUG-37771 Change-Id: Ifa7188fc2ccb156e4c411b50fefd4f12a0590883 Reviewed-by: Oliver Wolff --- src/winrtrunner/appxphoneengine.cpp | 544 ++++++++++++++++++++++++++++++++++++ src/winrtrunner/appxphoneengine.h | 88 ++++++ src/winrtrunner/runner.cpp | 22 ++ src/winrtrunner/winrtrunner.pro | 9 +- 4 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 src/winrtrunner/appxphoneengine.cpp create mode 100644 src/winrtrunner/appxphoneengine.h diff --git a/src/winrtrunner/appxphoneengine.cpp b/src/winrtrunner/appxphoneengine.cpp new file mode 100644 index 000000000..b1d246aec --- /dev/null +++ b/src/winrtrunner/appxphoneengine.cpp @@ -0,0 +1,544 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "appxphoneengine.h" +#include "appxengine_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Management::Deployment; +using namespace ABI::Windows::ApplicationModel; +using namespace ABI::Windows::System; + +// From Microsoft.Phone.Tools.Deploy assembly +namespace PhoneTools { + enum DeploymentOptions + { + None = 0, + PA = 1, + Debug = 2, + Infused = 4, + Lightup = 8, + Enterprise = 16, + Sideload = 32, + TypeMask = 255, + UninstallDisabled = 256, + SkipUpdateAppInForeground = 512, + DeleteXap = 1024, + InstallOnSD = 65536, + OptOutSD = 131072 + }; + enum PackageType + { + UnknownAppx = 0, + Main = 1, + Framework = 2, + Resource = 4, + Bundle = 8, + Xap = 0 + }; +} + +QT_USE_NAMESPACE + +#include +#include +Q_GLOBAL_STATIC_WITH_ARGS(CoreConServer, coreConServer, (12)) + +#undef RETURN_IF_FAILED +#define RETURN_IF_FAILED(msg, ret) \ + if (FAILED(hr)) { \ + qCWarning(lcWinRtRunner).nospace() << msg << ": 0x" << QByteArray::number(hr, 16).constData() \ + << ' ' << coreConServer->formatError(hr); \ + ret; \ + } + +// Set a break handler for gracefully breaking long-running ops +static bool g_ctrlReceived = false; +static bool g_handleCtrl = false; +static BOOL WINAPI ctrlHandler(DWORD type) +{ + switch (type) { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + g_ctrlReceived = g_handleCtrl; + return g_handleCtrl; + case CTRL_BREAK_EVENT: + case CTRL_SHUTDOWN_EVENT: + default: + break; + } + return false; +} + +class AppxPhoneEnginePrivate : public AppxEnginePrivate +{ +public: + QString productId; + + ComPtr connection; + CoreConDevice *device; + QSet dependencies; +}; + +static ProcessorArchitecture toProcessorArchitecture(APPX_PACKAGE_ARCHITECTURE appxArch) +{ + switch (appxArch) { + case APPX_PACKAGE_ARCHITECTURE_X86: + return ProcessorArchitecture_X86; + case APPX_PACKAGE_ARCHITECTURE_ARM: + return ProcessorArchitecture_Arm; + case APPX_PACKAGE_ARCHITECTURE_X64: + return ProcessorArchitecture_X64; + case APPX_PACKAGE_ARCHITECTURE_NEUTRAL: + // fall-through intended + default: + return ProcessorArchitecture_Neutral; + } +} + +static bool getPhoneProductId(IStream *manifestStream, QString *productId) +{ + // Read out the phone product ID (not supported by AppxManifestReader) + ComPtr xmlReader; + HRESULT hr = CreateXmlReader(IID_PPV_ARGS(&xmlReader), NULL); + RETURN_FALSE_IF_FAILED("Failed to create XML reader"); + + hr = xmlReader->SetInput(manifestStream); + RETURN_FALSE_IF_FAILED("Failed to set manifest as input"); + + while (!xmlReader->IsEOF()) { + XmlNodeType nodeType; + hr = xmlReader->Read(&nodeType); + RETURN_FALSE_IF_FAILED("Failed to read next node in manifest"); + if (nodeType == XmlNodeType_Element) { + PCWSTR uri; + hr = xmlReader->GetNamespaceUri(&uri, NULL); + RETURN_FALSE_IF_FAILED("Failed to read namespace URI of current node"); + if (wcscmp(uri, L"http://schemas.microsoft.com/appx/2014/phone/manifest") == 0) { + PCWSTR localName; + hr = xmlReader->GetLocalName(&localName, NULL); + RETURN_FALSE_IF_FAILED("Failed to get local name of current node"); + if (wcscmp(localName, L"PhoneIdentity") == 0) { + hr = xmlReader->MoveToAttributeByName(L"PhoneProductId", NULL); + if (hr == S_FALSE) + continue; + RETURN_FALSE_IF_FAILED("Failed to seek to the PhoneProductId attribute"); + PCWSTR phoneProductId; + UINT length; + hr = xmlReader->GetValue(&phoneProductId, &length); + RETURN_FALSE_IF_FAILED("Failed to read the value of the PhoneProductId attribute"); + *productId = QLatin1Char('{') + QString::fromWCharArray(phoneProductId, length) + QLatin1Char('}'); + return true; + } + } + } + } + return false; +} + +bool AppxPhoneEngine::canHandle(Runner *runner) +{ + return getManifestFile(runner->app()); +} + +RunnerEngine *AppxPhoneEngine::create(Runner *runner) +{ + QScopedPointer engine(new AppxPhoneEngine(runner)); + if (engine->d_ptr->hasFatalError) + return 0; + + return engine.take(); +} + +QStringList AppxPhoneEngine::deviceNames() +{ + QStringList deviceNames; + foreach (const CoreConDevice *device, coreConServer->devices()) + deviceNames.append(device->name()); + return deviceNames; +} + +AppxPhoneEngine::AppxPhoneEngine(Runner *runner) + : AppxEngine(runner, new AppxPhoneEnginePrivate) +{ + Q_D(AppxPhoneEngine); + if (d->hasFatalError) + return; + d->hasFatalError = true; + + ComPtr manifestStream; + HRESULT hr = SHCreateStreamOnFile(wchar(d->manifest), STGM_READ, &manifestStream); + RETURN_VOID_IF_FAILED("Failed to open manifest stream"); + + if (!getPhoneProductId(manifestStream.Get(), &d->productId)) { + qCWarning(lcWinRtRunner) << "Failed to read phone product ID from the manifest."; + return; + } + + if (!coreConServer->initialize()) { + while (!coreConServer.exists()) + Sleep(1); + } + + // Get the device + d->device = coreConServer->devices().value(d->runner->deviceIndex()); + if (!d->device || !d->device->handle()) { + d->hasFatalError = true; + qCWarning(lcWinRtRunner) << "Invalid device specified:" << d->runner->deviceIndex(); + return; + } + + + + // Set a break handler for gracefully exiting from long-running operations + SetConsoleCtrlHandler(&ctrlHandler, true); + d->hasFatalError = false; +} + +AppxPhoneEngine::~AppxPhoneEngine() +{ +} + +QString AppxPhoneEngine::extensionSdkPath() const +{ + HKEY regKey; + LONG hr = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\WindowsPhoneApp\\v8.1", + 0, KEY_READ, ®Key); + if (hr != ERROR_SUCCESS) { + qCWarning(lcWinRtRunner) << "Failed to open registry key:" << qt_error_string(hr); + return QString(); + } + + wchar_t pathData[MAX_PATH]; + DWORD pathLength = MAX_PATH; + hr = RegGetValue(regKey, L"Install Path", L"Install Path", RRF_RT_REG_SZ, NULL, pathData, &pathLength); + if (hr != ERROR_SUCCESS) { + qCWarning(lcWinRtRunner) << "Failed to get installation path value:" << qt_error_string(hr); + return QString(); + } + + return QString::fromWCharArray(pathData, (pathLength - 1) / sizeof(wchar_t)) + + QLatin1String("ExtensionSDKs"); +} + +bool AppxPhoneEngine::installPackage(IAppxManifestReader *reader, const QString &filePath) +{ + Q_D(AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__ << filePath; + + ComPtr connection; + HRESULT hr = d->connection.As(&connection); + RETURN_FALSE_IF_FAILED("Failed to obtain connection object"); + + ComPtr manifestStream; + hr = reader->GetStream(&manifestStream); + RETURN_FALSE_IF_FAILED("Failed to get manifest stream from reader"); + + QString productIdString; + if (!getPhoneProductId(manifestStream.Get(), &productIdString)) { + qCWarning(lcWinRtRunner) << "Failed to get phone product ID from manifest reader."; + return false; + } + _bstr_t productId(wchar(productIdString)); + + VARIANT_BOOL isInstalled; + hr = connection->IsApplicationInstalled(productId, &isInstalled); + RETURN_FALSE_IF_FAILED("Failed to determine if package is installed"); + if (isInstalled) { + qCDebug(lcWinRtRunner) << "Package" << productIdString << "is already installed"; + return true; + } + + ComPtr properties; + hr = reader->GetProperties(&properties); + RETURN_FALSE_IF_FAILED("Failed to get manifest properties"); + + BOOL isFramework; + hr = properties->GetBoolValue(L"Framework", &isFramework); + RETURN_FALSE_IF_FAILED("Failed to determine whether package is a framework"); + + const QString deploymentFlags = QString::number(isFramework ? PhoneTools::None : PhoneTools::Sideload); + _bstr_t deploymentFlagsAsGenre(wchar(deploymentFlags)); + const QString packageType = QString::number(isFramework ? PhoneTools::Framework : PhoneTools::Main); + _bstr_t packageTypeAsIconPath(wchar(packageType)); + _bstr_t packagePath(wchar(QDir::toNativeSeparators(filePath))); + hr = connection->InstallApplication(productId, productId, deploymentFlagsAsGenre, + packageTypeAsIconPath, packagePath); + RETURN_FALSE_IF_FAILED("Failed to install the package"); + + return true; +} + +bool AppxPhoneEngine::connect() +{ + Q_D(AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + HRESULT hr; + if (!d->connection) { + _bstr_t connectionName; + hr = static_cast(coreConServer->handle())->GetConnection( + static_cast(d->device->handle()), 5000, NULL, connectionName.GetAddress(), &d->connection); + RETURN_FALSE_IF_FAILED("Failed to connect to device"); + } + + VARIANT_BOOL connected; + hr = d->connection->IsConnected(&connected); + RETURN_FALSE_IF_FAILED("Failed to determine connection state"); + if (connected) + return true; + + hr = d->connection->ConnectDevice(); + RETURN_FALSE_IF_FAILED("Failed to connect to device"); + + return true; +} + +bool AppxPhoneEngine::install(bool removeFirst) +{ + Q_D(AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + if (!connect()) + return false; + + ComPtr connection; + HRESULT hr = d->connection.As(&connection); + RETURN_FALSE_IF_FAILED("Failed to obtain connection object"); + + _bstr_t productId(wchar(d->productId)); + VARIANT_BOOL isInstalled; + hr = connection->IsApplicationInstalled(productId, &isInstalled); + RETURN_FALSE_IF_FAILED("Failed to obtain the installation status"); + if (isInstalled) { + if (!removeFirst) + return true; + if (!remove()) + return false; + } + + if (!installDependencies()) + return false; + + const QDir base = QFileInfo(d->manifest).absoluteDir(); + const QString packageFileName = base.absoluteFilePath(d->packageFamilyName + QStringLiteral(".appx")); + if (!createPackage(packageFileName)) + return false; + + ComPtr manifestStream; + hr = SHCreateStreamOnFile(wchar(d->manifest), STGM_READ, &manifestStream); + RETURN_FALSE_IF_FAILED("Failed to open manifest stream"); + + ComPtr manifestReader; + hr = d->packageFactory->CreateManifestReader(manifestStream.Get(), &manifestReader); + RETURN_FALSE_IF_FAILED("Failed to create manifest reader for installation"); + + return installPackage(manifestReader.Get(), packageFileName); +} + +bool AppxPhoneEngine::remove() +{ + Q_D(AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + if (!connect()) + return false; + + if (!d->connection) + return false; + + ComPtr connection; + HRESULT hr = d->connection.As(&connection); + RETURN_FALSE_IF_FAILED("Failed to obtain connection object"); + + _bstr_t app = wchar(d->productId); + hr = connection->UninstallApplication(app); + RETURN_FALSE_IF_FAILED("Failed to uninstall the package"); + + return true; +} + +bool AppxPhoneEngine::start() +{ + Q_D(AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + if (!connect()) + return false; + + if (!d->runner->arguments().isEmpty()) + qCWarning(lcWinRtRunner) << "Arguments are not currently supported for Windows Phone Appx packages."; + + ComPtr connection; + HRESULT hr = d->connection.As(&connection); + RETURN_FALSE_IF_FAILED("Failed to cast connection object"); + + _bstr_t productId(wchar(d->productId)); + DWORD pid; + hr = connection->LaunchApplication(productId, &pid); + RETURN_FALSE_IF_FAILED("Failed to start the package"); + + d->pid = pid; + return true; +} + +bool AppxPhoneEngine::enableDebugging(const QString &debuggerExecutable, const QString &debuggerArguments) +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + Q_UNUSED(debuggerExecutable); + Q_UNUSED(debuggerArguments); + return false; +} + +bool AppxPhoneEngine::disableDebugging() +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + return false; +} + +bool AppxPhoneEngine::suspend() +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + return false; +} + +bool AppxPhoneEngine::waitForFinished(int secs) +{ + Q_D(AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + ComPtr connection; + HRESULT hr = d->connection.As(&connection); + RETURN_FALSE_IF_FAILED("Failed to cast connection"); + + g_handleCtrl = true; + int time = 0; + forever { + ++time; + if ((secs && time > secs) || g_ctrlReceived) { + g_handleCtrl = false; + return false; + } + + Sleep(1000); // Wait one second between checks + qCDebug(lcWinRtRunner) << "Waiting for app to quit - msecs to go: " << secs - time; + } + g_handleCtrl = false; + return true; +} + +bool AppxPhoneEngine::stop() +{ + Q_D(AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + if (!connect()) + return false; + + ComPtr connection; + HRESULT hr = d->connection.As(&connection); + RETURN_FALSE_IF_FAILED("Failed to cast connection object"); + + _bstr_t productId(wchar(d->productId)); + hr = connection->TerminateRunningApplicationInstances(productId); + RETURN_FALSE_IF_FAILED("Failed to stop the package"); + + return true; +} + +QString AppxPhoneEngine::devicePath(const QString &relativePath) const +{ + Q_D(const AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + return QStringLiteral("%FOLDERID_APPID_ISOROOT%\\") + d->productId + + QStringLiteral("\\%LOCL%\\") + relativePath; +} + +bool AppxPhoneEngine::sendFile(const QString &localFile, const QString &deviceFile) +{ + Q_D(const AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + HRESULT hr = d->connection->SendFile(_bstr_t(wchar(localFile)), _bstr_t(wchar(deviceFile)), + CREATE_ALWAYS, NULL); + RETURN_FALSE_IF_FAILED("Failed to send the file"); + + return true; +} + +bool AppxPhoneEngine::receiveFile(const QString &deviceFile, const QString &localFile) +{ + Q_D(const AppxPhoneEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + HRESULT hr = d->connection->ReceiveFile(_bstr_t(wchar(deviceFile)), + _bstr_t(wchar(localFile)), uint(2)); + RETURN_FALSE_IF_FAILED("Failed to receive the file"); + + return true; +} diff --git a/src/winrtrunner/appxphoneengine.h b/src/winrtrunner/appxphoneengine.h new file mode 100644 index 000000000..9f4933355 --- /dev/null +++ b/src/winrtrunner/appxphoneengine.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef APPXPHONEENGINE_H +#define APPXPHONEENGINE_H + +#include "appxengine.h" +#include "runnerengine.h" +#include "runner.h" + +#include + +QT_USE_NAMESPACE + +class AppxPhoneEnginePrivate; +class AppxPhoneEngine : public AppxEngine +{ +public: + static bool canHandle(Runner *runner); + static RunnerEngine *create(Runner *runner); + static QStringList deviceNames(); + + bool install(bool removeFirst = false) Q_DECL_OVERRIDE; + bool remove() Q_DECL_OVERRIDE; + bool start() Q_DECL_OVERRIDE; + bool enableDebugging(const QString &debuggerExecutable, + const QString &debuggerArguments) Q_DECL_OVERRIDE; + bool disableDebugging() Q_DECL_OVERRIDE; + bool suspend() Q_DECL_OVERRIDE; + bool waitForFinished(int secs) Q_DECL_OVERRIDE; + bool stop() Q_DECL_OVERRIDE; + + QString devicePath(const QString &relativePath) const Q_DECL_OVERRIDE; + bool sendFile(const QString &localFile, const QString &deviceFile) Q_DECL_OVERRIDE; + bool receiveFile(const QString &deviceFile, const QString &localFile) Q_DECL_OVERRIDE; + +private: + explicit AppxPhoneEngine(Runner *runner); + ~AppxPhoneEngine(); + + QString extensionSdkPath() const; + bool installPackage(IAppxManifestReader *reader, const QString &filePath) Q_DECL_OVERRIDE; + + bool connect(); + + friend struct QScopedPointerDeleter; + Q_DECLARE_PRIVATE(AppxPhoneEngine) +}; + +#endif // APPXPHONEENGINE_H diff --git a/src/winrtrunner/runner.cpp b/src/winrtrunner/runner.cpp index 5b4725991..fa31c30d8 100644 --- a/src/winrtrunner/runner.cpp +++ b/src/winrtrunner/runner.cpp @@ -43,6 +43,9 @@ #include "runnerengine.h" +#ifndef RTRUNNER_NO_APPXPHONE +#include "appxphoneengine.h" +#endif #ifndef RTRUNNER_NO_APPXLOCAL #include "appxlocalengine.h" #endif @@ -80,6 +83,9 @@ QMap Runner::deviceNames() #ifndef RTRUNNER_NO_APPXLOCAL deviceNames.insert(QStringLiteral("Appx"), AppxLocalEngine::deviceNames()); #endif +#ifndef RTRUNNER_NO_APPXPHONE + deviceNames.insert(QStringLiteral("Phone"), AppxPhoneEngine::deviceNames()); +#endif #ifndef RTRUNNER_NO_XAP deviceNames.insert(QStringLiteral("Xap"), XapEngine::deviceNames()); #endif @@ -98,6 +104,22 @@ Runner::Runner(const QString &app, const QStringList &arguments, bool deviceIndexKnown; d->deviceIndex = deviceName.toInt(&deviceIndexKnown); +#ifndef RTRUNNER_NO_APPXPHONE + if (!deviceIndexKnown) { + d->deviceIndex = AppxPhoneEngine::deviceNames().indexOf(deviceName); + if (d->deviceIndex < 0) + d->deviceIndex = 0; + } + if ((d->profile.isEmpty() || d->profile.toLower() == QStringLiteral("appxphone")) + && AppxPhoneEngine::canHandle(this)) { + if (RunnerEngine *engine = AppxPhoneEngine::create(this)) { + d->engine.reset(engine); + d->isValid = true; + qCWarning(lcWinRtRunner) << "Using the AppxPhone profile."; + return; + } + } +#endif #ifndef RTRUNNER_NO_APPXLOCAL if (!deviceIndexKnown) { d->deviceIndex = AppxLocalEngine::deviceNames().indexOf(deviceName); diff --git a/src/winrtrunner/winrtrunner.pro b/src/winrtrunner/winrtrunner.pro index eb2fdfcb6..e1b59846a 100644 --- a/src/winrtrunner/winrtrunner.pro +++ b/src/winrtrunner/winrtrunner.pro @@ -6,7 +6,7 @@ DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII WINRT_LIBRARY SOURCES += main.cpp runner.cpp HEADERS += runner.h runnerengine.h -DEFINES += RTRUNNER_NO_APPXLOCAL RTRUNNER_NO_XAP +DEFINES += RTRUNNER_NO_APPXLOCAL RTRUNNER_NO_APPXPHONE RTRUNNER_NO_XAP win32-msvc2012|win32-msvc2013 { SOURCES += appxengine.cpp appxlocalengine.cpp @@ -19,6 +19,13 @@ win32-msvc2012|win32-msvc2013 { HEADERS += xapengine.h DEFINES -= RTRUNNER_NO_XAP + win32-msvc2013 { + LIBS += -lurlmon -lxmllite + SOURCES += appxphoneengine.cpp + HEADERS += appxphoneengine.h + DEFINES -= RTRUNNER_NO_APPXPHONE + } + # Use zip class from qtbase SOURCES += \ qzip/qzip.cpp -- cgit v1.2.1