From 39efb9a7c2162021027a5b3e798dc818d3fc6999 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Fri, 29 Dec 2017 15:32:25 +0200 Subject: Prefer hiding the MO proxy when waiting for spawned processes --- src/mainwindow.cpp | 4 +- src/organizercore.cpp | 121 ++++++++++++++++++++++++++++++++++++++++++++------ src/organizercore.h | 2 +- 3 files changed, 112 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c29e4ca4..ddc4fe23 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -875,7 +875,9 @@ void MainWindow::closeEvent(QCloseEvent* event) } } - HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(); + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); + HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); if (injected_process_still_running != INVALID_HANDLE_VALUE) { m_OrganizerCore.waitForApplication(injected_process_still_running); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 121de21a..3d2ed4ef 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -50,6 +50,7 @@ #include #include +#include #include // for _tcsicmp #include @@ -125,6 +126,27 @@ static std::wstring getProcessName(HANDLE process) return fileName; } +// Get parent PID for the given process, return 0 on failure +static DWORD getProcessParentID(DWORD pid) +{ + HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + PROCESSENTRY32 pe = { 0 }; + pe.dwSize = sizeof(PROCESSENTRY32); + + DWORD res = 0; + if (Process32First(th, &pe)) + do { + if (pe.th32ProcessID == pid) { + res = pe.th32ParentProcessID; + break; + } + } while (Process32Next(th, &pe)); + + CloseHandle(th); + + return res; +} + static void startSteam(QWidget *widget) { QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", @@ -1337,13 +1359,35 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL bool newHandle = true; bool uiunlocked = false; + DWORD currentPID = 0; + QString processName; + auto waitForChildUntil = GetTickCount64(); + if (handle != INVALID_HANDLE_VALUE) { + currentPID = GetProcessId(handle); + processName = QString::fromStdWString(getProcessName(handle)); + } + + // Certain process names we wish to "hide" for aesthetic reason: + bool waitingOnHidden = false; + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); + for (QString hide : hiddenList) + if (processName.contains(hide, Qt::CaseInsensitive)) + waitingOnHidden = true; + // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes. + // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want + // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden" + // process. For this reason we use exponential backoff and also start with a delibrately low value to improve + // the responsiveness of the initial update + DWORD64 nextHiddenCheck = GetTickCount64(); + DWORD64 nextHiddenCheckDelay = 50; + constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; DWORD res = WAIT_TIMEOUT; while (handle != INVALID_HANDLE_VALUE && (newHandle || res == WAIT_TIMEOUT || res == INPUT_EVENT)) { if (newHandle) { - QString processName = QString::fromStdWString(getProcessName(handle)); - processName += QString(" (%1)").arg(GetProcessId(handle)); + processName += QString(" (%1)").arg(currentPID); if (uilock) uilock->setProcessName(processName); qDebug() << "Waiting for" @@ -1375,17 +1419,44 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL CloseHandle(handle); handle = INVALID_HANDLE_VALUE; originalHandle = false; - // if the previous process spawned a child process and immediately exits we may miss it if we check immediately - for (int i = 0; i < 3; ++i) { + waitForChildUntil = GetTickCount64() + 800; + } + + // search for another process to wait on if either: + // 1. we just completed waiting for a process and need to find/wait for an inject child + // 2. we are currently waiting on a hidden process so periodically check if there is a non-hidden process to wait on + bool firstIteration = true; + while ((handle == INVALID_HANDLE_VALUE && GetTickCount64() <= waitForChildUntil) + || (waitingOnHidden && GetTickCount64() >= nextHiddenCheck)) + { + if (firstIteration) + firstIteration = false; + else { QThread::msleep(200); QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); } - // search if there is another usvfs process active and if so wait for it - handle = findAndOpenAUSVFSProcess(); + // search if there is another usvfs process active + handle = findAndOpenAUSVFSProcess(hiddenList, currentPID); + waitingOnHidden = false; newHandle = handle != INVALID_HANDLE_VALUE; + if (newHandle) { + currentPID = GetProcessId(handle); + processName = QString::fromStdWString(getProcessName(handle)); + for (QString hide : hiddenList) + if (processName.contains(hide, Qt::CaseInsensitive)) + waitingOnHidden = true; + } + if (waitingOnHidden) { + nextHiddenCheck = GetTickCount64() + nextHiddenCheckDelay; + nextHiddenCheckDelay = std::min(nextHiddenCheckDelay * 2, (DWORD64) 2000); + } + else { + nextHiddenCheck = GetTickCount64(); + nextHiddenCheckDelay = 200; + } } } @@ -1402,9 +1473,10 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL return res == WAIT_OBJECT_0; } -HANDLE OrganizerCore::findAndOpenAUSVFSProcess() { - // in theory a querySize of 1 is probably enough since the MO process doesn't seem to be returned by GetVFSProcessList - constexpr size_t querySize = 2; // just to be on the safe side +HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid) { + // for practical reasons a querySize of 1 is probably enough, we use a larger query as a heuristics + // to find a more "aesthetic injected processes (attempting to comply to hiddenList and preferedParentPid) + constexpr size_t querySize = 100; DWORD pids[querySize]; size_t found = querySize; if (!::GetVFSProcessList(&found, pids)) { @@ -1412,17 +1484,40 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess() { return INVALID_HANDLE_VALUE; } + HANDLE best_match = INVALID_HANDLE_VALUE; + bool best_match_hidden = true; for (size_t i = 0; i < found; ++i) { if (pids[i] == GetCurrentProcessId()) continue; // obviously don't wait for MO process + HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); - if (handle != INVALID_HANDLE_VALUE) - return handle; - else + if (handle == INVALID_HANDLE_VALUE) { qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError(); + continue; + } + + QString pname = QString::fromStdWString(getProcessName(handle)); + bool phidden = false; + for (auto hide : hiddenList) + if (pname.contains(hide, Qt::CaseInsensitive)) + phidden = true; + + bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid; + + if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { + if (best_match != INVALID_HANDLE_VALUE) + CloseHandle(best_match); + best_match = handle; + best_match_hidden = phidden; + } + else + CloseHandle(handle); + + if (!phidden && pprefered) + return best_match; } - return INVALID_HANDLE_VALUE; + return best_match; } bool OrganizerCore::onAboutToRun( diff --git a/src/organizercore.h b/src/organizercore.h index f98e9d0a..8ef8baaa 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -204,7 +204,7 @@ public: HANDLE runShortcut(const MOShortcut& shortcut); HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile); bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - HANDLE findAndOpenAUSVFSProcess(); + HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); bool onModInstalled(const std::function &func); bool onAboutToRun(const std::function &func); bool onFinishedRun(const std::function &func); -- cgit v1.3.1