From 19f62cf8d8cfbb0c5a3e2348d28af11b9926244c Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Thu, 5 Jun 2014 08:41:25 +0300 Subject: winrtrunner: Split AppxEngine into abstract base and Appx Local subclass This allows common code to be reused in future Appx engine types. Change-Id: I4e0ba09242eee6687604abe65d556a96a79346f4 Reviewed-by: Oliver Wolff --- src/winrtrunner/appxengine.cpp | 680 ++---------------------------------- src/winrtrunner/appxengine.h | 27 +- src/winrtrunner/appxengine_p.h | 94 +++++ src/winrtrunner/appxlocalengine.cpp | 669 +++++++++++++++++++++++++++++++++++ src/winrtrunner/appxlocalengine.h | 86 +++++ src/winrtrunner/runner.cpp | 16 +- src/winrtrunner/winrtrunner.pro | 8 +- 7 files changed, 903 insertions(+), 677 deletions(-) create mode 100644 src/winrtrunner/appxengine_p.h create mode 100644 src/winrtrunner/appxlocalengine.cpp create mode 100644 src/winrtrunner/appxlocalengine.h diff --git a/src/winrtrunner/appxengine.cpp b/src/winrtrunner/appxengine.cpp index 039fa4fe2..b79668bdb 100644 --- a/src/winrtrunner/appxengine.cpp +++ b/src/winrtrunner/appxengine.cpp @@ -40,6 +40,7 @@ ****************************************************************************/ #include "appxengine.h" +#include "appxengine_p.h" #include #include @@ -66,280 +67,7 @@ using namespace ABI::Windows::System; QT_USE_NAMESPACE -#define wchar(str) reinterpret_cast(str.utf16()) -#define hStringFromQString(str) HStringReference(reinterpret_cast(str.utf16())).Get() -#define QStringFromHString(hstr) QString::fromWCharArray(WindowsGetStringRawBuffer(hstr, Q_NULLPTR)) - -// 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; -} - -QString sidForPackage(const QString &packageFamilyName) -{ - QString sid; - HKEY regKey; - LONG result = RegOpenKeyEx( - HKEY_CLASSES_ROOT, - L"Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Mappings", - 0, KEY_READ, ®Key); - if (result != ERROR_SUCCESS) { - qCWarning(lcWinRtRunner) << "Unable to open registry key:" << qt_error_string(result); - return sid; - } - - DWORD index = 0; - wchar_t subKey[MAX_PATH]; - forever { - result = RegEnumKey(regKey, index++, subKey, MAX_PATH); - if (result != ERROR_SUCCESS) - break; - wchar_t moniker[MAX_PATH]; - DWORD monikerSize = MAX_PATH; - result = RegGetValue(regKey, subKey, L"Moniker", RRF_RT_REG_SZ, NULL, moniker, &monikerSize); - if (result != ERROR_SUCCESS) - continue; - if (lstrcmp(moniker, reinterpret_cast(packageFamilyName.utf16())) == 0) { - sid = QString::fromWCharArray(subKey); - break; - } - } - RegCloseKey(regKey); - return sid; -} - -class OutputDebugMonitor -{ -public: - OutputDebugMonitor() - : runLock(CreateEvent(NULL, FALSE, FALSE, NULL)), thread(0) - { - } - ~OutputDebugMonitor() - { - if (runLock) { - SetEvent(runLock); - CloseHandle(runLock); - } - if (thread) { - WaitForSingleObject(thread, INFINITE); - CloseHandle(thread); - } - } - void start(const QString &packageFamilyName) - { - if (thread) { - qCWarning(lcWinRtRunner) << "OutputDebugMonitor is already running."; - return; - } - - package = packageFamilyName; - - thread = CreateThread(NULL, 0, &monitor, this, NULL, NULL); - if (!thread) { - qCWarning(lcWinRtRunner) << "Unable to create thread for app debugging:" - << qt_error_string(GetLastError()); - return; - } - - return; - } -private: - static DWORD __stdcall monitor(LPVOID param) - { - OutputDebugMonitor *that = static_cast(param); - - const QString handleBase = QStringLiteral("Local\\AppContainerNamedObjects\\") - + sidForPackage(that->package); - const QString eventName = handleBase + QStringLiteral("\\qdebug-event"); - const QString shmemName = handleBase + QStringLiteral("\\qdebug-shmem"); - - HANDLE event = CreateEvent(NULL, FALSE, FALSE, reinterpret_cast(eventName.utf16())); - if (!event) { - qCWarning(lcWinRtRunner) << "Unable to open shared event for app debugging:" - << qt_error_string(GetLastError()); - return 1; - } - - HANDLE shmem = 0; - DWORD ret = 0; - forever { - HANDLE handles[] = { that->runLock, event }; - DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE); - - // runLock set; exit thread - if (result == WAIT_OBJECT_0) - break; - - // debug event set; print message - if (result == WAIT_OBJECT_0 + 1) { - if (!shmem) { - shmem = OpenFileMapping(GENERIC_READ, FALSE, - reinterpret_cast(shmemName.utf16())); - if (!shmem) { - qCWarning(lcWinRtRunner) << "Unable to open shared memory for app debugging:" - << qt_error_string(GetLastError()); - ret = 1; - break; - } - } - - const quint32 *data = reinterpret_cast( - MapViewOfFile(shmem, FILE_MAP_READ, 0, 0, 4096)); - QtMsgType messageType = static_cast(data[0]); - QString message = QString::fromWCharArray( - reinterpret_cast(data + 1)); - UnmapViewOfFile(data); - switch (messageType) { - default: - case QtDebugMsg: - qCDebug(lcWinRtRunnerApp, qPrintable(message)); - break; - case QtWarningMsg: - qCWarning(lcWinRtRunnerApp, qPrintable(message)); - break; - case QtCriticalMsg: - case QtFatalMsg: - qCCritical(lcWinRtRunnerApp, qPrintable(message)); - break; - } - continue; - } - - // An error occurred; exit thread - qCWarning(lcWinRtRunner) << "Debug output monitor error:" - << qt_error_string(GetLastError()); - ret = 1; - break; - } - if (shmem) - CloseHandle(shmem); - if (event) - CloseHandle(event); - return ret; - } - HANDLE runLock; - HANDLE thread; - QString package; -}; -Q_GLOBAL_STATIC(OutputDebugMonitor, debugMonitor) - -class AppxEnginePrivate -{ -public: - Runner *runner; - bool hasFatalError; - - QString manifest; - QString packageFullName; - QString packageFamilyName; - ProcessorArchitecture packageArchitecture; - QString executable; - qint64 pid; - HANDLE processHandle; - DWORD exitCode; - QSet dependencies; - QSet installedPackages; - - ComPtr packageManager; - ComPtr uriFactory; - ComPtr appLauncher; - ComPtr packageFactory; - ComPtr packageDebug; - - void retrieveInstalledPackages(); -}; - -class XmlStream : public RuntimeClass, IStream> -{ -public: - XmlStream(const QString &fileName) - : m_manifest(fileName) - { - m_manifest.open(QFile::ReadOnly); - } - - ~XmlStream() - { - } - - HRESULT __stdcall Read(void *data, ULONG count, ULONG *bytesRead) - { - *bytesRead = m_manifest.read(reinterpret_cast(data), count); - return S_OK; - } - - HRESULT __stdcall Seek(LARGE_INTEGER pos, DWORD start, ULARGE_INTEGER *newPos) - { - switch (start) { - default: - case STREAM_SEEK_SET: - /* pos.QuadPart += 0; */ - break; - case STREAM_SEEK_CUR: - pos.QuadPart += m_manifest.pos(); - break; - case STREAM_SEEK_END: - pos.QuadPart += m_manifest.size(); - break; - } - if (!m_manifest.seek(pos.QuadPart)) - return STG_E_INVALIDPOINTER; - if (newPos) { - ULARGE_INTEGER newPosInt = { m_manifest.pos() }; - *newPos = newPosInt; - } - return S_OK; - } - - HRESULT __stdcall Stat(STATSTG *stats, DWORD flags) - { - QFileInfo info(m_manifest); - // ms to 100-ns units - ULARGE_INTEGER lastModifiedInt = { info.lastModified().toMSecsSinceEpoch() * 10000 }; - ULARGE_INTEGER createdInt = { info.created().toMSecsSinceEpoch() * 10000 }; - ULARGE_INTEGER lastReadInt = { info.lastRead().toMSecsSinceEpoch() * 10000 }; - STATSTG newStats = { - flags == STATFLAG_NONAME ? NULL : reinterpret_cast(m_manifest.fileName().data()), - STGTY_STREAM, { m_manifest.size() }, - { lastModifiedInt.u.LowPart, lastModifiedInt.u.HighPart }, - { createdInt.u.LowPart, createdInt.u.HighPart }, - { lastReadInt.u.LowPart, lastReadInt.u.HighPart }, - 0, 0, CLSID_NULL, 0, 0 - }; - *stats = newStats; - return S_OK; - } - - // Unimplemented methods - HRESULT __stdcall Write(const void *, ULONG, ULONG *) { return E_NOTIMPL; } - HRESULT __stdcall SetSize(ULARGE_INTEGER) { return E_NOTIMPL; } - HRESULT __stdcall CopyTo(IStream *, ULARGE_INTEGER, ULARGE_INTEGER *, ULARGE_INTEGER *) { return E_NOTIMPL; } - HRESULT __stdcall Commit(DWORD) { return E_NOTIMPL; } - HRESULT __stdcall Revert() { return E_NOTIMPL; } - HRESULT __stdcall LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; } - HRESULT __stdcall UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; } - HRESULT __stdcall Clone(IStream **) { return E_NOTIMPL; } - -private: - QFile m_manifest; -}; - -static bool getManifestFile(const QString &fileName, QString *manifest = 0) +bool AppxEngine::getManifestFile(const QString &fileName, QString *manifest) { if (!QFile::exists(fileName)) { qCWarning(lcWinRtRunner) << fileName << "does not exist."; @@ -374,27 +102,6 @@ static bool getManifestFile(const QString &fileName, QString *manifest = 0) return false; } -bool AppxEngine::canHandle(Runner *runner) -{ - return getManifestFile(runner->app()); -} - -RunnerEngine *AppxEngine::create(Runner *runner) -{ - AppxEngine *engine = new AppxEngine(runner); - if (engine->d_ptr->hasFatalError) { - delete engine; - return 0; - } - return engine; -} - -QStringList AppxEngine::deviceNames() -{ - qCDebug(lcWinRtRunner) << __FUNCTION__; - return QStringList(QStringLiteral("local")); -} - #define CHECK_RESULT(errorMessage, action)\ do {\ if (FAILED(hr)) {\ @@ -424,7 +131,8 @@ static ProcessorArchitecture toProcessorArchitecture(APPX_PACKAGE_ARCHITECTURE a } } -AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) +AppxEngine::AppxEngine(Runner *runner, AppxEnginePrivate *dd) + : d_ptr(dd) { Q_D(AppxEngine); d->runner = runner; @@ -442,27 +150,18 @@ AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); CHECK_RESULT_FATAL("Failed to initialize COM.", return); - hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Management_Deployment_PackageManager).Get(), - &d->packageManager); - CHECK_RESULT_FATAL("Failed to instantiate package manager.", return); - hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_Uri).Get(), IID_PPV_ARGS(&d->uriFactory)); CHECK_RESULT_FATAL("Failed to instantiate URI factory.", return); - hr = CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER, - IID_IApplicationActivationManager, &d->appLauncher); - CHECK_RESULT_FATAL("Failed to instantiate application activation manager.", return); - - hr = CoCreateInstance(CLSID_PackageDebugSettings, nullptr, CLSCTX_INPROC_SERVER, - IID_IPackageDebugSettings, &d->packageDebug); - CHECK_RESULT_FATAL("Failed to instantiate package debug settings.", return); - hr = CoCreateInstance(CLSID_AppxFactory, nullptr, CLSCTX_INPROC_SERVER, IID_IAppxFactory, &d->packageFactory); CHECK_RESULT_FATAL("Failed to instantiate package factory.", return); - ComPtr manifestStream = Make(d->manifest); + ComPtr manifestStream; + hr = SHCreateStreamOnFile(wchar(d->manifest), STGM_READ, &manifestStream); + CHECK_RESULT_FATAL("Failed to open manifest stream.", return); + ComPtr manifestReader; hr = d->packageFactory->CreateManifestReader(manifestStream.Get(), &manifestReader); if (FAILED(hr)) { @@ -520,8 +219,6 @@ AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) .absoluteFilePath(QString::fromWCharArray(executable)); CoTaskMemFree(executable); - d->retrieveInstalledPackages(); - ComPtr dependencies; hr = manifestReader->GetPackageDependencies(&dependencies); CHECK_RESULT_FATAL("Failed to retrieve the package dependencies from the manifest.", return); @@ -540,9 +237,6 @@ AppxEngine::AppxEngine(Runner *runner) : d_ptr(new AppxEnginePrivate) CoTaskMemFree(name); hr = dependencies->MoveNext(&hasCurrent); } - - // Set a break handler for gracefully exiting from long-running operations - SetConsoleCtrlHandler(&ctrlHandler, true); } AppxEngine::~AppxEngine() @@ -551,6 +245,30 @@ AppxEngine::~AppxEngine() CloseHandle(d->processHandle); } +qint64 AppxEngine::pid() const +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + return d->pid; +} + +int AppxEngine::exitCode() const +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + return d->exitCode == UINT_MAX ? -1 : HRESULT_CODE(d->exitCode); +} + +QString AppxEngine::executable() const +{ + Q_D(const AppxEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + return d->executable; +} + bool AppxEngine::installDependencies() { Q_D(AppxEngine); @@ -558,8 +276,6 @@ bool AppxEngine::installDependencies() QSet toInstall; foreach (const QString &dependencyName, d->dependencies) { - if (d->installedPackages.contains(dependencyName)) - continue; toInstall.insert(dependencyName); qCDebug(lcWinRtRunner).nospace() << "dependency to be installed: " << dependencyName; @@ -568,13 +284,7 @@ bool AppxEngine::installDependencies() if (toInstall.isEmpty()) return true; - const QByteArray extensionSdkDirRaw = qgetenv("ExtensionSdkDir"); - if (extensionSdkDirRaw.isEmpty()) { - qCWarning(lcWinRtRunner).nospace() - << QStringLiteral("The environment variable ExtensionSdkDir is not set."); - return false; - } - const QString extensionSdkDir = QString::fromLocal8Bit(extensionSdkDirRaw); + const QString extensionSdkDir = extensionSdkPath(); if (!QFile::exists(extensionSdkDir)) { qCWarning(lcWinRtRunner).nospace() << QString(QStringLiteral("The directory '%1' does not exist.")).arg( @@ -625,331 +335,11 @@ bool AppxEngine::installDependencies() qCDebug(lcWinRtRunner).nospace() << "installing dependency " << name << " from " << dit.filePath(); - if (installPackage(dit.filePath())) - toInstall.remove(name); - } - - return true; -} - -bool AppxEngine::installPackage(const QString &filePath) -{ - Q_D(const AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__ << filePath; - - const QString nativeFilePath = QDir::toNativeSeparators(QFileInfo(filePath).absoluteFilePath()); - const bool addInsteadOfRegister = nativeFilePath.endsWith(QStringLiteral(".appx"), - Qt::CaseInsensitive); - HRESULT hr; - ComPtr uri; - hr = d->uriFactory->CreateUri(hStringFromQString(nativeFilePath), &uri); - CHECK_RESULT("Unable to create an URI for the package.", return false); - - ComPtr> deploymentOperation; - if (addInsteadOfRegister) { - hr = d->packageManager->AddPackageAsync(uri.Get(), NULL, DeploymentOptions_None, - &deploymentOperation); - CHECK_RESULT("Unable to add package.", return false); - } else { - hr = d->packageManager->RegisterPackageAsync(uri.Get(), 0, - DeploymentOptions_DevelopmentMode, - &deploymentOperation); - CHECK_RESULT("Unable to start package registration.", return false); - } - - ComPtr results; - while ((hr = deploymentOperation->GetResults(&results)) == E_ILLEGAL_METHOD_CALL) - Sleep(1); - - HRESULT errorCode; - hr = results->get_ExtendedErrorCode(&errorCode); - CHECK_RESULT("Unable to retrieve package registration results.", return false); - - if (FAILED(errorCode)) { - HString errorText; - if (SUCCEEDED(results->get_ErrorText(errorText.GetAddressOf()))) { - qCWarning(lcWinRtRunner) << "Unable to register package:" - << QString::fromWCharArray(errorText.GetRawBuffer(NULL)); - } - if (HRESULT_CODE(errorCode) == ERROR_INSTALL_POLICY_FAILURE) { - // The user's license has expired. Give them the opportunity to renew it. - FILETIME expiration; - hr = AcquireDeveloperLicense(GetForegroundWindow(), &expiration); - if (FAILED(hr)) { - qCWarning(lcWinRtRunner) << "Unable to renew developer license:" - << qt_error_string(hr); - } - if (SUCCEEDED(hr)) - return install(false); - } - return false; - } - - return SUCCEEDED(hr); -} - -bool AppxEngine::install(bool removeFirst) -{ - Q_D(const AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - ComPtr packageInformation; - HRESULT hr = d->packageManager->FindPackageByUserSecurityIdPackageFullName( - NULL, hStringFromQString(d->packageFullName), &packageInformation); - if (SUCCEEDED(hr) && packageInformation) { - qCWarning(lcWinRtRunner) << "Package already installed."; - if (removeFirst) - remove(); - else - return true; - } - - return installDependencies() && installPackage(d->manifest); -} - -bool AppxEngine::remove() -{ - Q_D(const AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - ComPtr> deploymentOperation; - HRESULT hr = d->packageManager->RemovePackageAsync(hStringFromQString(d->packageFullName), &deploymentOperation); - if (FAILED(hr)) { - qCWarning(lcWinRtRunner) << "Unable to start package removal:" << QDir::toNativeSeparators(d->manifest); - return false; - } - - ComPtr results; - while ((hr = deploymentOperation.Get()->GetResults(&results)) == E_ILLEGAL_METHOD_CALL) - Sleep(1); - - if (FAILED(hr)) { - qCWarning(lcWinRtRunner) << "Unable to remove package:" << QDir::toNativeSeparators(d->manifest); - return false; - } - - return SUCCEEDED(hr); -} - -bool AppxEngine::start() -{ - Q_D(AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - const QString launchArguments = - (d->runner->arguments() << QStringLiteral("-qdevel")).join(QLatin1Char(' ')); - DWORD pid; - const QString activationId = d->packageFamilyName + QStringLiteral("!App"); - HRESULT hr = d->appLauncher->ActivateApplication(wchar(activationId), - wchar(launchArguments), AO_NONE, &pid); - CHECK_RESULT("Failed to activate application.", return false); - d->pid = qint64(pid); - CloseHandle(d->processHandle); - d->processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, pid); - - return true; -} - -bool AppxEngine::enableDebugging(const QString &debuggerExecutable, const QString &debuggerArguments) -{ - Q_D(AppxEngine); - - const QString &debuggerCommand = debuggerExecutable + QLatin1Char(' ') + debuggerArguments; - HRESULT hr = d->packageDebug->EnableDebugging(wchar(d->packageFullName), - wchar(debuggerCommand), - NULL); - CHECK_RESULT("Failed to enable debugging for application.", return false); - return true; -} - -bool AppxEngine::disableDebugging() -{ - Q_D(AppxEngine); - - HRESULT hr = d->packageDebug->DisableDebugging(wchar(d->packageFullName)); - CHECK_RESULT("Failed to disable debugging for application.", return false); - - return true; -} - -bool AppxEngine::suspend() -{ - Q_D(AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - HRESULT hr = d->packageDebug->Suspend(wchar(d->packageFullName)); - CHECK_RESULT("Failed to suspend application.", return false); - - return true; -} - -bool AppxEngine::waitForFinished(int secs) -{ - Q_D(AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - debugMonitor->start(d->packageFamilyName); - - g_handleCtrl = true; - int time = 0; - forever { - PACKAGE_EXECUTION_STATE state; - HRESULT hr = d->packageDebug->GetPackageExecutionState(wchar(d->packageFullName), &state); - CHECK_RESULT("Failed to get package execution state.", return false); - qCDebug(lcWinRtRunner) << "Current execution state:" << state; - if (state == PES_TERMINATED || state == PES_UNKNOWN) - break; - - ++time; - if ((secs && time > secs) || g_ctrlReceived) { - g_handleCtrl = false; + if (!installPackage(manifestReader.Get(), dit.filePath())) { + qCWarning(lcWinRtRunner) << "Failed to install package:" << name; return false; } - - Sleep(1000); // Wait one second between checks - qCDebug(lcWinRtRunner) << "Waiting for app to quit - msecs to go:" << secs - time; } - g_handleCtrl = false; - - if (!GetExitCodeProcess(d->processHandle, &d->exitCode)) - d->exitCode = UINT_MAX; return true; } - -bool AppxEngine::stop() -{ - Q_D(AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - // ### We won't have a process handle if we didn't start the app. We can look it up - // using a process snapshot, or by calling start if we know the process is already running. - // For now, simply continue normally, but don't fetch the exit code. - if (!d->processHandle) - qCDebug(lcWinRtRunner) << "No handle to the process; the exit code won't be available."; - - if (d->processHandle && !GetExitCodeProcess(d->processHandle, &d->exitCode)) { - d->exitCode = UINT_MAX; - qCWarning(lcWinRtRunner).nospace() << "Failed to obtain process exit code."; - qCDebug(lcWinRtRunner, "GetLastError: 0x%x", GetLastError()); - return false; - } - - if (!d->processHandle || d->exitCode == STILL_ACTIVE) { - HRESULT hr = d->packageDebug->TerminateAllProcesses(wchar(d->packageFullName)); - CHECK_RESULT("Failed to terminate package process.", return false); - - if (d->processHandle && !GetExitCodeProcess(d->processHandle, &d->exitCode)) - d->exitCode = UINT_MAX; - } - - return true; -} - -qint64 AppxEngine::pid() const -{ - Q_D(const AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - return d->pid; -} - -int AppxEngine::exitCode() const -{ - Q_D(const AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - return d->exitCode == UINT_MAX ? -1 : HRESULT_CODE(d->exitCode); -} - -QString AppxEngine::executable() const -{ - Q_D(const AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - return d->executable; -} - -QString AppxEngine::devicePath(const QString &relativePath) const -{ - Q_D(const AppxEngine); - qCDebug(lcWinRtRunner) << __FUNCTION__; - - // Return a path safe for passing to the application - QDir localAppDataPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); - const QString path = localAppDataPath.absoluteFilePath( - QStringLiteral("Packages/") + d->packageFamilyName - + QStringLiteral("/LocalState/") + relativePath); - return QDir::toNativeSeparators(path); -} - -bool AppxEngine::sendFile(const QString &localFile, const QString &deviceFile) -{ - qCDebug(lcWinRtRunner) << __FUNCTION__; - - // Both files are local, just use QFile - QFile source(localFile); - - // Remove the destination, or copy will fail - if (QFileInfo(source) != QFileInfo(deviceFile)) - QFile::remove(deviceFile); - - bool result = source.copy(deviceFile); - if (!result) - qCWarning(lcWinRtRunner) << "Unable to sendFile:" << source.errorString(); - - return result; -} - -bool AppxEngine::receiveFile(const QString &deviceFile, const QString &localFile) -{ - qCDebug(lcWinRtRunner) << __FUNCTION__; - - // Both files are local, so just reverse the sendFile arguments - return sendFile(deviceFile, localFile); -} - -void AppxEnginePrivate::retrieveInstalledPackages() -{ - qCDebug(lcWinRtRunner) << __FUNCTION__; - - ComPtr> packages; - HRESULT hr = packageManager->FindPackagesByUserSecurityId(NULL, &packages); - CHECK_RESULT("Failed to find packages.", return); - - ComPtr> pkgit; - hr = packages->First(&pkgit); - CHECK_RESULT("Failed to get package iterator.", return); - - boolean hasCurrent; - hr = pkgit->get_HasCurrent(&hasCurrent); - while (SUCCEEDED(hr) && hasCurrent) { - ComPtr pkg; - hr = pkgit->get_Current(&pkg); - CHECK_RESULT("Failed to get current package.", return); - - ComPtr pkgId; - hr = pkg->get_Id(&pkgId); - CHECK_RESULT("Failed to get package id.", return); - - HString name; - hr = pkgId->get_Name(name.GetAddressOf()); - CHECK_RESULT("Failed retrieve package name.", return); - - ProcessorArchitecture architecture; - if (packageArchitecture == ProcessorArchitecture_Neutral) { - architecture = packageArchitecture; - } else { - hr = pkgId->get_Architecture(&architecture); - CHECK_RESULT("Failed to retrieve package architecture.", return); - } - - const QString pkgName = QStringFromHString(name.Get()); - qCDebug(lcWinRtRunner) << "found installed package" << pkgName; - - if (architecture == packageArchitecture) - installedPackages.insert(pkgName); - - hr = pkgit->MoveNext(&hasCurrent); - } -} diff --git a/src/winrtrunner/appxengine.h b/src/winrtrunner/appxengine.h index d930751c0..0390eeaec 100644 --- a/src/winrtrunner/appxengine.h +++ b/src/winrtrunner/appxengine.h @@ -50,37 +50,24 @@ QT_USE_NAMESPACE +struct IAppxManifestReader; class AppxEnginePrivate; class AppxEngine : public RunnerEngine { 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; qint64 pid() const Q_DECL_OVERRIDE; int exitCode() const Q_DECL_OVERRIDE; - QString executable() const 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 AppxEngine(Runner *runner); +protected: + explicit AppxEngine(Runner *runner, AppxEnginePrivate *dd); ~AppxEngine(); + virtual QString extensionSdkPath() const = 0; + virtual bool installPackage(IAppxManifestReader *reader, const QString &filePath) = 0; + bool installDependencies(); - bool installPackage(const QString &filePath); + static bool getManifestFile(const QString &fileName, QString *manifest = 0); QScopedPointer d_ptr; Q_DECLARE_PRIVATE(AppxEngine) diff --git a/src/winrtrunner/appxengine_p.h b/src/winrtrunner/appxengine_p.h new file mode 100644 index 000000000..dde97b73a --- /dev/null +++ b/src/winrtrunner/appxengine_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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 APPXENGINE_P_H +#define APPXENGINE_P_H + +#include +#include +#include + +#include +#include + +QT_USE_NAMESPACE + +class Runner; +struct IAppxFactory; +class AppxEnginePrivate +{ +public: + virtual ~AppxEnginePrivate() { } + Runner *runner; + bool hasFatalError; + + QString manifest; + QString packageFullName; + QString packageFamilyName; + ABI::Windows::System::ProcessorArchitecture packageArchitecture; + QString executable; + qint64 pid; + HANDLE processHandle; + DWORD exitCode; + QSet dependencies; + QSet installedPackages; + + Microsoft::WRL::ComPtr uriFactory; + Microsoft::WRL::ComPtr packageFactory; +}; + +#define wchar(str) reinterpret_cast(str.utf16()) +#define hStringFromQString(str) HStringReference(reinterpret_cast(str.utf16())).Get() +#define QStringFromHString(hstr) QString::fromWCharArray(WindowsGetStringRawBuffer(hstr, Q_NULLPTR)) + +#define RETURN_IF_FAILED(msg, ret) \ + if (FAILED(hr)) { \ + qCWarning(lcWinRtRunner).nospace() << msg << ": 0x" << QByteArray::number(hr, 16).constData() \ + << ' ' << qt_error_string(hr); \ + ret; \ + } + +#define RETURN_HR_IF_FAILED(msg) RETURN_IF_FAILED(msg, return hr) +#define RETURN_OK_IF_FAILED(msg) RETURN_IF_FAILED(msg, return S_OK) +#define RETURN_FALSE_IF_FAILED(msg) RETURN_IF_FAILED(msg, return false) +#define RETURN_VOID_IF_FAILED(msg) RETURN_IF_FAILED(msg, return) + +#endif // APPXENGINE_P_H diff --git a/src/winrtrunner/appxlocalengine.cpp b/src/winrtrunner/appxlocalengine.cpp new file mode 100644 index 000000000..03379910a --- /dev/null +++ b/src/winrtrunner/appxlocalengine.cpp @@ -0,0 +1,669 @@ +/**************************************************************************** +** +** 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 "appxlocalengine.h" +#include "appxengine_p.h" + +#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; + +QT_USE_NAMESPACE + +// 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; +} + +QString sidForPackage(const QString &packageFamilyName) +{ + QString sid; + HKEY regKey; + LONG result = RegOpenKeyEx( + HKEY_CLASSES_ROOT, + L"Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Mappings", + 0, KEY_READ, ®Key); + if (result != ERROR_SUCCESS) { + qCWarning(lcWinRtRunner) << "Unable to open registry key:" << qt_error_string(result); + return sid; + } + + DWORD index = 0; + wchar_t subKey[MAX_PATH]; + const wchar_t *packageFamilyNameW = wchar(packageFamilyName); + forever { + result = RegEnumKey(regKey, index++, subKey, MAX_PATH); + if (result != ERROR_SUCCESS) + break; + wchar_t moniker[MAX_PATH]; + DWORD monikerSize = MAX_PATH; + result = RegGetValue(regKey, subKey, L"Moniker", RRF_RT_REG_SZ, NULL, moniker, &monikerSize); + if (result != ERROR_SUCCESS) + continue; + if (lstrcmp(moniker, packageFamilyNameW) == 0) { + sid = QString::fromWCharArray(subKey); + break; + } + } + RegCloseKey(regKey); + return sid; +} + +class OutputDebugMonitor +{ +public: + OutputDebugMonitor() + : runLock(CreateEvent(NULL, FALSE, FALSE, NULL)), thread(0) + { + } + ~OutputDebugMonitor() + { + if (runLock) { + SetEvent(runLock); + CloseHandle(runLock); + } + if (thread) { + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + } + } + void start(const QString &packageFamilyName) + { + if (thread) { + qCWarning(lcWinRtRunner) << "OutputDebugMonitor is already running."; + return; + } + + package = packageFamilyName; + + thread = CreateThread(NULL, 0, &monitor, this, NULL, NULL); + if (!thread) { + qCWarning(lcWinRtRunner) << "Unable to create thread for app debugging:" + << qt_error_string(GetLastError()); + return; + } + + return; + } +private: + static DWORD __stdcall monitor(LPVOID param) + { + OutputDebugMonitor *that = static_cast(param); + + const QString handleBase = QStringLiteral("Local\\AppContainerNamedObjects\\") + + sidForPackage(that->package); + const QString eventName = handleBase + QStringLiteral("\\qdebug-event"); + const QString shmemName = handleBase + QStringLiteral("\\qdebug-shmem"); + + HANDLE event = CreateEvent(NULL, FALSE, FALSE, reinterpret_cast(eventName.utf16())); + if (!event) { + qCWarning(lcWinRtRunner) << "Unable to open shared event for app debugging:" + << qt_error_string(GetLastError()); + return 1; + } + + HANDLE shmem = 0; + DWORD ret = 0; + forever { + HANDLE handles[] = { that->runLock, event }; + DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + + // runLock set; exit thread + if (result == WAIT_OBJECT_0) + break; + + // debug event set; print message + if (result == WAIT_OBJECT_0 + 1) { + if (!shmem) { + shmem = OpenFileMapping(GENERIC_READ, FALSE, + reinterpret_cast(shmemName.utf16())); + if (!shmem) { + qCWarning(lcWinRtRunner) << "Unable to open shared memory for app debugging:" + << qt_error_string(GetLastError()); + ret = 1; + break; + } + } + + const quint32 *data = reinterpret_cast( + MapViewOfFile(shmem, FILE_MAP_READ, 0, 0, 4096)); + QtMsgType messageType = static_cast(data[0]); + QString message = QString::fromWCharArray( + reinterpret_cast(data + 1)); + UnmapViewOfFile(data); + switch (messageType) { + default: + case QtDebugMsg: + qCDebug(lcWinRtRunnerApp, qPrintable(message)); + break; + case QtWarningMsg: + qCWarning(lcWinRtRunnerApp, qPrintable(message)); + break; + case QtCriticalMsg: + case QtFatalMsg: + qCCritical(lcWinRtRunnerApp, qPrintable(message)); + break; + } + continue; + } + + // An error occurred; exit thread + qCWarning(lcWinRtRunner) << "Debug output monitor error:" + << qt_error_string(GetLastError()); + ret = 1; + break; + } + if (shmem) + CloseHandle(shmem); + if (event) + CloseHandle(event); + return ret; + } + HANDLE runLock; + HANDLE thread; + QString package; +}; +Q_GLOBAL_STATIC(OutputDebugMonitor, debugMonitor) + +class AppxLocalEnginePrivate : public AppxEnginePrivate +{ +public: + ComPtr packageManager; + ComPtr appLauncher; + ComPtr packageDebug; + + void retrieveInstalledPackages(); +}; + +static bool getManifestFile(const QString &fileName, QString *manifest = 0) +{ + if (!QFile::exists(fileName)) { + qCWarning(lcWinRtRunner) << fileName << "does not exist."; + return false; + } + + // If it looks like an appx manifest, we're done + if (fileName.endsWith(QStringLiteral("AppxManifest.xml"))) { + + if (manifest) + *manifest = fileName; + return true; + } + + // If it looks like an executable, check that manifest is next to it + if (fileName.endsWith(QStringLiteral(".exe"))) { + QDir appDir = QFileInfo(fileName).absoluteDir(); + QString manifestFileName = appDir.absoluteFilePath(QStringLiteral("AppxManifest.xml")); + if (!QFile::exists(manifestFileName)) { + qCWarning(lcWinRtRunner) << manifestFileName << "does not exist."; + return false; + } + + if (manifest) + *manifest = manifestFileName; + return true; + } + + // TODO: handle already-built package as well + + qCWarning(lcWinRtRunner) << "Appx: unable to determine manifest for" << fileName << "."; + return false; +} + +bool AppxLocalEngine::canHandle(Runner *runner) +{ + return getManifestFile(runner->app()); +} + +RunnerEngine *AppxLocalEngine::create(Runner *runner) +{ + QScopedPointer engine(new AppxLocalEngine(runner)); + if (engine->d_ptr->hasFatalError) + return 0; + + return engine.take(); +} + +QStringList AppxLocalEngine::deviceNames() +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + return QStringList(QStringLiteral("local")); +} + + + +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; + } +} + +AppxLocalEngine::AppxLocalEngine(Runner *runner) + : AppxEngine(runner, new AppxLocalEnginePrivate) +{ + Q_D(AppxLocalEngine); + if (d->hasFatalError) + return; + d->hasFatalError = true; + + HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Management_Deployment_PackageManager).Get(), + &d->packageManager); + RETURN_VOID_IF_FAILED("Failed to instantiate package manager"); + + hr = CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER, + IID_IApplicationActivationManager, &d->appLauncher); + RETURN_VOID_IF_FAILED("Failed to instantiate application activation manager"); + + hr = CoCreateInstance(CLSID_PackageDebugSettings, nullptr, CLSCTX_INPROC_SERVER, + IID_IPackageDebugSettings, &d->packageDebug); + RETURN_VOID_IF_FAILED("Failed to instantiate package debug settings"); + + d->retrieveInstalledPackages(); + + // Set a break handler for gracefully exiting from long-running operations + SetConsoleCtrlHandler(&ctrlHandler, true); + d->hasFatalError = false; +} + +AppxLocalEngine::~AppxLocalEngine() +{ + Q_D(const AppxLocalEngine); + CloseHandle(d->processHandle); +} + +bool AppxLocalEngine::installPackage(IAppxManifestReader *reader, const QString &filePath) +{ + Q_D(const AppxLocalEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__ << filePath; + + HRESULT hr; + if (reader) { + ComPtr packageId; + hr = reader->GetPackageId(&packageId); + RETURN_FALSE_IF_FAILED("Failed to get package ID from reader."); + + LPWSTR name; + hr = packageId->GetName(&name); + RETURN_FALSE_IF_FAILED("Failed to get package name from package ID."); + + const QString packageName = QString::fromWCharArray(name); + CoTaskMemFree(name); + if (d->installedPackages.contains(packageName)) { + qCDebug(lcWinRtRunner) << "The package" << packageName << "is already installed."; + return true; + } + } + + const QString nativeFilePath = QDir::toNativeSeparators(QFileInfo(filePath).absoluteFilePath()); + const bool addInsteadOfRegister = nativeFilePath.endsWith(QStringLiteral(".appx"), + Qt::CaseInsensitive); + ComPtr uri; + hr = d->uriFactory->CreateUri(hStringFromQString(nativeFilePath), &uri); + RETURN_FALSE_IF_FAILED("Failed to create an URI for the package"); + + ComPtr> deploymentOperation; + if (addInsteadOfRegister) { + hr = d->packageManager->AddPackageAsync(uri.Get(), NULL, DeploymentOptions_None, + &deploymentOperation); + RETURN_FALSE_IF_FAILED("Failed to add package"); + } else { + hr = d->packageManager->RegisterPackageAsync(uri.Get(), 0, + DeploymentOptions_DevelopmentMode, + &deploymentOperation); + RETURN_FALSE_IF_FAILED("Failed to start package registration"); + } + + ComPtr results; + while ((hr = deploymentOperation->GetResults(&results)) == E_ILLEGAL_METHOD_CALL) + Sleep(1); + + HRESULT errorCode; + hr = results->get_ExtendedErrorCode(&errorCode); + RETURN_FALSE_IF_FAILED("Failed to retrieve package registration results."); + + if (FAILED(errorCode)) { + HString errorText; + if (SUCCEEDED(results->get_ErrorText(errorText.GetAddressOf()))) { + qCWarning(lcWinRtRunner) << "Unable to register package:" + << QString::fromWCharArray(errorText.GetRawBuffer(NULL)); + } + if (HRESULT_CODE(errorCode) == ERROR_INSTALL_POLICY_FAILURE) { + // The user's license has expired. Give them the opportunity to renew it. + FILETIME expiration; + hr = AcquireDeveloperLicense(GetForegroundWindow(), &expiration); + RETURN_FALSE_IF_FAILED("Unable to renew developer license"); + return install(false); + } + return false; + } + + return SUCCEEDED(hr); +} + +bool AppxLocalEngine::install(bool removeFirst) +{ + Q_D(const AppxLocalEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + ComPtr packageInformation; + HRESULT hr = d->packageManager->FindPackageByUserSecurityIdPackageFullName( + NULL, hStringFromQString(d->packageFullName), &packageInformation); + if (SUCCEEDED(hr) && packageInformation) { + qCWarning(lcWinRtRunner) << "Package already installed."; + if (removeFirst) + remove(); + else + return true; + } + + return installDependencies() && installPackage(Q_NULLPTR, d->manifest); +} + +bool AppxLocalEngine::remove() +{ + Q_D(const AppxLocalEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // ### TODO: use RemovePackageWithOptions to preserve previous state when re-installing + ComPtr> deploymentOperation; + HRESULT hr = d->packageManager->RemovePackageAsync(hStringFromQString(d->packageFullName), &deploymentOperation); + RETURN_FALSE_IF_FAILED("Unable to start package removal"); + + ComPtr results; + while ((hr = deploymentOperation.Get()->GetResults(&results)) == E_ILLEGAL_METHOD_CALL) + Sleep(1); + + RETURN_FALSE_IF_FAILED("Unable to remove package"); + + return SUCCEEDED(hr); +} + +bool AppxLocalEngine::start() +{ + Q_D(AppxLocalEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + const QString launchArguments = + (d->runner->arguments() << QStringLiteral("-qdevel")).join(QLatin1Char(' ')); + DWORD pid; + const QString activationId = d->packageFamilyName + QStringLiteral("!App"); + HRESULT hr = d->appLauncher->ActivateApplication(wchar(activationId), + wchar(launchArguments), AO_NONE, &pid); + RETURN_FALSE_IF_FAILED("Failed to activate application"); + d->pid = qint64(pid); + CloseHandle(d->processHandle); + d->processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, pid); + + return true; +} + +bool AppxLocalEngine::enableDebugging(const QString &debuggerExecutable, const QString &debuggerArguments) +{ + Q_D(AppxLocalEngine); + + const QString &debuggerCommand = debuggerExecutable + QLatin1Char(' ') + debuggerArguments; + HRESULT hr = d->packageDebug->EnableDebugging(wchar(d->packageFullName), + wchar(debuggerCommand), + NULL); + RETURN_FALSE_IF_FAILED("Failed to enable debugging for application"); + return true; +} + +bool AppxLocalEngine::disableDebugging() +{ + Q_D(AppxLocalEngine); + + HRESULT hr = d->packageDebug->DisableDebugging(wchar(d->packageFullName)); + RETURN_FALSE_IF_FAILED("Failed to disable debugging for application"); + + return true; +} + +bool AppxLocalEngine::suspend() +{ + Q_D(AppxLocalEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + HRESULT hr = d->packageDebug->Suspend(wchar(d->packageFullName)); + RETURN_FALSE_IF_FAILED("Failed to suspend application"); + + return true; +} + +bool AppxLocalEngine::waitForFinished(int secs) +{ + Q_D(AppxLocalEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + debugMonitor->start(d->packageFamilyName); + + g_handleCtrl = true; + int time = 0; + forever { + PACKAGE_EXECUTION_STATE state; + HRESULT hr = d->packageDebug->GetPackageExecutionState(wchar(d->packageFullName), &state); + RETURN_FALSE_IF_FAILED("Failed to get package execution state"); + qCDebug(lcWinRtRunner) << "Current execution state:" << state; + if (state == PES_TERMINATED || state == PES_UNKNOWN) + break; + + ++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; + + if (!GetExitCodeProcess(d->processHandle, &d->exitCode)) + d->exitCode = UINT_MAX; + + return true; +} + +bool AppxLocalEngine::stop() +{ + Q_D(AppxLocalEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // ### We won't have a process handle if we didn't start the app. We can look it up + // using a process snapshot, or by calling start if we know the process is already running. + // For now, simply continue normally, but don't fetch the exit code. + if (!d->processHandle) + qCDebug(lcWinRtRunner) << "No handle to the process; the exit code won't be available."; + + if (d->processHandle && !GetExitCodeProcess(d->processHandle, &d->exitCode)) { + d->exitCode = UINT_MAX; + qCWarning(lcWinRtRunner).nospace() << "Failed to obtain process exit code."; + qCDebug(lcWinRtRunner, "GetLastError: 0x%x", GetLastError()); + return false; + } + + if (!d->processHandle || d->exitCode == STILL_ACTIVE) { + HRESULT hr = d->packageDebug->TerminateAllProcesses(wchar(d->packageFullName)); + RETURN_FALSE_IF_FAILED("Failed to terminate package process"); + + if (d->processHandle && !GetExitCodeProcess(d->processHandle, &d->exitCode)) + d->exitCode = UINT_MAX; + } + + return true; +} + +QString AppxLocalEngine::devicePath(const QString &relativePath) const +{ + Q_D(const AppxLocalEngine); + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // Return a path safe for passing to the application + QDir localAppDataPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + const QString path = localAppDataPath.absoluteFilePath( + QStringLiteral("Packages/") + d->packageFamilyName + + QStringLiteral("/LocalState/") + relativePath); + return QDir::toNativeSeparators(path); +} + +bool AppxLocalEngine::sendFile(const QString &localFile, const QString &deviceFile) +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // Both files are local, just use QFile + QFile source(localFile); + + // Remove the destination, or copy will fail + if (QFileInfo(source) != QFileInfo(deviceFile)) + QFile::remove(deviceFile); + + bool result = source.copy(deviceFile); + if (!result) + qCWarning(lcWinRtRunner) << "Unable to sendFile:" << source.errorString(); + + return result; +} + +bool AppxLocalEngine::receiveFile(const QString &deviceFile, const QString &localFile) +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + + // Both files are local, so just reverse the sendFile arguments + return sendFile(deviceFile, localFile); +} + +QString AppxLocalEngine::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); +} + +void AppxLocalEnginePrivate::retrieveInstalledPackages() +{ + qCDebug(lcWinRtRunner) << __FUNCTION__; + + ComPtr> packages; + HRESULT hr = packageManager->FindPackagesByUserSecurityId(NULL, &packages); + RETURN_VOID_IF_FAILED("Failed to find packages"); + + ComPtr> pkgit; + hr = packages->First(&pkgit); + RETURN_VOID_IF_FAILED("Failed to get package iterator"); + + boolean hasCurrent; + hr = pkgit->get_HasCurrent(&hasCurrent); + while (SUCCEEDED(hr) && hasCurrent) { + ComPtr pkg; + hr = pkgit->get_Current(&pkg); + RETURN_VOID_IF_FAILED("Failed to get current package"); + + ComPtr pkgId; + hr = pkg->get_Id(&pkgId); + RETURN_VOID_IF_FAILED("Failed to get package id"); + + HString name; + hr = pkgId->get_Name(name.GetAddressOf()); + RETURN_VOID_IF_FAILED("Failed retrieve package name"); + + ProcessorArchitecture architecture; + if (packageArchitecture == ProcessorArchitecture_Neutral) { + architecture = packageArchitecture; + } else { + hr = pkgId->get_Architecture(&architecture); + RETURN_VOID_IF_FAILED("Failed to retrieve package architecture"); + } + + const QString pkgName = QStringFromHString(name.Get()); + qCDebug(lcWinRtRunner) << "found installed package" << pkgName; + + if (architecture == packageArchitecture) + installedPackages.insert(pkgName); + + hr = pkgit->MoveNext(&hasCurrent); + } +} diff --git a/src/winrtrunner/appxlocalengine.h b/src/winrtrunner/appxlocalengine.h new file mode 100644 index 000000000..3b09907fb --- /dev/null +++ b/src/winrtrunner/appxlocalengine.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 APPXLOCALENGINE_H +#define APPXLOCALENGINE_H + +#include "appxengine.h" +#include "runner.h" + +#include +#include + +QT_USE_NAMESPACE + +class AppxLocalEnginePrivate; +class AppxLocalEngine : 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 AppxLocalEngine(Runner *runner); + ~AppxLocalEngine(); + + QString extensionSdkPath() const Q_DECL_OVERRIDE; + bool installPackage(IAppxManifestReader *reader, const QString &filePath) Q_DECL_OVERRIDE; + + friend struct QScopedPointerDeleter; + Q_DECLARE_PRIVATE(AppxLocalEngine) +}; + +#endif // APPXLOCALENGINE_H diff --git a/src/winrtrunner/runner.cpp b/src/winrtrunner/runner.cpp index 9bf6a91b2..5b4725991 100644 --- a/src/winrtrunner/runner.cpp +++ b/src/winrtrunner/runner.cpp @@ -43,8 +43,8 @@ #include "runnerengine.h" -#ifndef RTRUNNER_NO_APPX -#include "appxengine.h" +#ifndef RTRUNNER_NO_APPXLOCAL +#include "appxlocalengine.h" #endif #ifndef RTRUNNER_NO_XAP #include "xapengine.h" @@ -77,8 +77,8 @@ public: QMap Runner::deviceNames() { QMap deviceNames; -#ifndef RTRUNNER_NO_APPX - deviceNames.insert(QStringLiteral("Appx"), AppxEngine::deviceNames()); +#ifndef RTRUNNER_NO_APPXLOCAL + deviceNames.insert(QStringLiteral("Appx"), AppxLocalEngine::deviceNames()); #endif #ifndef RTRUNNER_NO_XAP deviceNames.insert(QStringLiteral("Xap"), XapEngine::deviceNames()); @@ -98,15 +98,15 @@ Runner::Runner(const QString &app, const QStringList &arguments, bool deviceIndexKnown; d->deviceIndex = deviceName.toInt(&deviceIndexKnown); -#ifndef RTRUNNER_NO_APPX +#ifndef RTRUNNER_NO_APPXLOCAL if (!deviceIndexKnown) { - d->deviceIndex = AppxEngine::deviceNames().indexOf(deviceName); + d->deviceIndex = AppxLocalEngine::deviceNames().indexOf(deviceName); if (d->deviceIndex < 0) d->deviceIndex = 0; } if ((d->profile.isEmpty() || d->profile.toLower() == QStringLiteral("appx")) - && AppxEngine::canHandle(this)) { - if (RunnerEngine *engine = AppxEngine::create(this)) { + && AppxLocalEngine::canHandle(this)) { + if (RunnerEngine *engine = AppxLocalEngine::create(this)) { d->engine.reset(engine); d->isValid = true; qCWarning(lcWinRtRunner) << "Using the Appx profile."; diff --git a/src/winrtrunner/winrtrunner.pro b/src/winrtrunner/winrtrunner.pro index 7203fd67e..eb2fdfcb6 100644 --- a/src/winrtrunner/winrtrunner.pro +++ b/src/winrtrunner/winrtrunner.pro @@ -6,13 +6,13 @@ 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_APPX RTRUNNER_NO_XAP +DEFINES += RTRUNNER_NO_APPXLOCAL RTRUNNER_NO_XAP win32-msvc2012|win32-msvc2013 { - SOURCES += appxengine.cpp - HEADERS += appxengine.h + SOURCES += appxengine.cpp appxlocalengine.cpp + HEADERS += appxengine.h appxengine_p.h appxlocalengine.h LIBS += -lruntimeobject -lwsclient -lShlwapi - DEFINES -= RTRUNNER_NO_APPX + DEFINES -= RTRUNNER_NO_APPXLOCAL include(../shared/corecon/corecon.pri) SOURCES += xapengine.cpp -- cgit v1.2.3