/**************************************************************************** ** ** 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 #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; const QList 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 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 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); 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(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->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 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::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 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 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; }