blob: 39c3b2712457e3218c7964056a9a8009d1de2c2c [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "appxphoneengine.h"
#include "appxengine_p.h"
#include <QtCore/QDir>
#include <QtCore/QDirIterator>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QHash>
#include <QtCore/QUuid>
#include <QtCore/QLoggingCategory>
#include <QtCore/QDateTime>
#include <comdef.h>
#include <psapi.h>
#include <ShlObj.h>
#include <Shlwapi.h>
#include <wsdevlicensing.h>
#include <AppxPackaging.h>
#include <xmllite.h>
#include <wrl.h>
#include <windows.applicationmodel.h>
#include <windows.management.deployment.h>
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 <corecon.h>
#include <ccapi_12.h>
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<ICcConnection> connection;
CoreConDevice *device;
QSet<QString> 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<IXmlReader> 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<AppxPhoneEngine> engine(new AppxPhoneEngine(runner));
if (engine->d_ptr->hasFatalError)
return 0;
return engine.take();
}
QStringList AppxPhoneEngine::deviceNames()
{
QStringList deviceNames;
const QList<CoreConDevice *> devices = coreConServer->devices();
for (const CoreConDevice *device : 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<IStream> manifestStream;
HRESULT hr;
if (d->manifestReader) {
hr = d->manifestReader->GetStream(&manifestStream);
RETURN_VOID_IF_FAILED("Failed to query manifest stream from manifest reader.");
} else {
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
{
const QByteArray extensionSdkDirRaw = qgetenv("ExtensionSdkDir");
if (extensionSdkDirRaw.isEmpty()) {
qCWarning(lcWinRtRunner) << "The environment variable ExtensionSdkDir is not set.";
return QString();
}
return QString::fromLocal8Bit(extensionSdkDirRaw);
}
bool AppxPhoneEngine::installPackage(IAppxManifestReader *reader, const QString &filePath)
{
Q_D(AppxPhoneEngine);
qCDebug(lcWinRtRunner) << __FUNCTION__ << filePath;
ComPtr<ICcConnection3> connection;
HRESULT hr = d->connection.As(&connection);
RETURN_FALSE_IF_FAILED("Failed to obtain connection object");
ComPtr<IStream> 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<IAppxManifestProperties> 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);
if (hr == 0x80073d06) { // No public E_* macro available
qCWarning(lcWinRtRunner) << "Found a newer version of " << filePath
<< " on the target device, skipping...";
} else {
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<ICcServer *>(coreConServer->handle())->GetConnection(
static_cast<ICcDevice *>(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<ICcConnection3> 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->executable).absoluteDir();
const bool existingPackage = d->runner->app().endsWith(QLatin1String(".appx"));
const QString packageFileName = existingPackage
? d->runner->app()
: base.absoluteFilePath(d->packageFamilyName + QStringLiteral(".appx"));
if (!existingPackage) {
if (!createPackage(packageFileName))
return false;
if (!sign(packageFileName))
return false;
} else {
qCDebug(lcWinRtRunner) << "Installing existing package.";
}
return installPackage(d->manifestReader.Get(), packageFileName);
}
bool AppxPhoneEngine::remove()
{
Q_D(AppxPhoneEngine);
qCDebug(lcWinRtRunner) << __FUNCTION__;
if (!connect())
return false;
if (!d->connection)
return false;
ComPtr<ICcConnection3> 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<ICcConnection3> 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::setLoopbackExemptClientEnabled(bool)
{
qCDebug(lcWinRtRunner) << __FUNCTION__;
return false;
}
bool AppxPhoneEngine::setLoopbackExemptServerEnabled(bool)
{
qCDebug(lcWinRtRunner) << __FUNCTION__;
return false;
}
bool AppxPhoneEngine::setLoggingRules(const QByteArray &)
{
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<ICcConnection3> 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()
{
qCDebug(lcWinRtRunner) << __FUNCTION__;
if (!connect())
return false;
#if 0 // This does not actually stop the app - QTBUG-41946
Q_D(AppxPhoneEngine);
ComPtr<ICcConnection3> 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;
#else
return remove();
#endif
}
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;
}