diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2019-10-29 10:03:59 -0400 |
|---|---|---|
| committer | isanae <14251494+isanae@users.noreply.github.com> | 2019-11-06 07:44:54 -0500 |
| commit | 8c72077febaea485200adcf1e9f615902e930def (patch) | |
| tree | 13fe30db88d40d1554343b40cb33e9dcf846db7d /src | |
| parent | 18b438cf27a552e69e984bfee63187b6471682ab (diff) | |
replaced uilock by a progress callback in spawn
waiting for process now gets the whole process tree to find an interesting process
Diffstat (limited to 'src')
| -rw-r--r-- | src/env.h | 17 | ||||
| -rw-r--r-- | src/envmodule.cpp | 107 | ||||
| -rw-r--r-- | src/envmodule.h | 35 | ||||
| -rw-r--r-- | src/envsecurity.cpp | 1 | ||||
| -rw-r--r-- | src/envwindows.cpp | 1 | ||||
| -rw-r--r-- | src/ilockedwaitingforprocess.h | 1 | ||||
| -rw-r--r-- | src/lockeddialogbase.cpp | 7 | ||||
| -rw-r--r-- | src/lockeddialogbase.h | 4 | ||||
| -rw-r--r-- | src/organizercore.cpp | 128 | ||||
| -rw-r--r-- | src/spawn.cpp | 29 | ||||
| -rw-r--r-- | src/spawn.h | 7 |
11 files changed, 246 insertions, 91 deletions
@@ -13,23 +13,6 @@ class WindowsInfo; class Metrics; -// used by HandlePtr, calls CloseHandle() as the deleter -// -struct HandleCloser -{ - using pointer = HANDLE; - - void operator()(HANDLE h) - { - if (h != INVALID_HANDLE_VALUE) { - ::CloseHandle(h); - } - } -}; - -using HandlePtr = std::unique_ptr<HANDLE, HandleCloser>; - - // used by DesktopDCPtr, calls ReleaseDC(0, dc) as the deleter // struct DesktopDCReleaser diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 160a54fa..0e2e8ec7 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -320,19 +320,61 @@ QString Module::getMD5() const } -Process::Process(DWORD pid, QString name) - : m_pid(pid), m_name(std::move(name)) +Process::Process() + : Process(0, 0, {}) { } +Process::Process(HANDLE h) + : Process(::GetProcessId(h), 0, {}) +{ +} + +Process::Process(DWORD pid, DWORD ppid, QString name) + : m_pid(pid), m_ppid(ppid), m_name(std::move(name)) +{ +} + +bool Process::isValid() const +{ + return (m_pid != 0); +} + DWORD Process::pid() const { return m_pid; } +DWORD Process::ppid() const +{ + if (!m_ppid) { + m_ppid = getProcessParentID(m_pid); + } + + return *m_ppid; +} + const QString& Process::name() const { - return m_name; + if (!m_name) { + m_name = getProcessName(m_pid); + } + + return *m_name; +} + +HandlePtr Process::openHandleForWait() const +{ + HandlePtr h(OpenProcess( + PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, m_pid)); + + if (!h) { + const auto e = GetLastError(); + log::error("can't get name of process {}, {}", m_pid, formatSystemMessage(e)); + return {}; + } + + return h; } // whether this process can be accessed; fails if the current process doesn't @@ -353,6 +395,16 @@ bool Process::canAccess() const return true; } +void Process::addChild(Process p) +{ + m_children.push_back(p); +} + +std::vector<Process>& Process::children() +{ + return m_children; +} + std::vector<Module> getLoadedModules() { @@ -458,6 +510,7 @@ std::vector<Process> getRunningProcesses() forEachRunningProcess([&](auto&& entry) { v.push_back(Process( entry.th32ProcessID, + entry.th32ParentProcessID, QString::fromStdWString(entry.szExeFile))); return true; @@ -466,6 +519,54 @@ std::vector<Process> getRunningProcesses() return v; } +void findChildren(Process& parent, const std::vector<Process>& processes) +{ + for (auto&& p : processes) { + if (p.ppid() == parent.pid()) { + Process child = p; + findChildren(child, processes); + + parent.addChild(child); + } + } +} + +Process getProcessTree(HANDLE parent) +{ + const auto parentPID = ::GetProcessId(parent); + const auto v = getRunningProcesses(); + + Process root; + for (auto&& p : v) { + if (p.pid() == parentPID) { + root = p; + break; + } + } + + if (root.pid() == 0) { + log::error("process {} is not running", parentPID); + return {}; + } + + findChildren(root, v); + + return root; +} + +QString getProcessName(DWORD pid) +{ + HandlePtr h(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + log::error("can't get name of process {}, {}", pid, formatSystemMessage(e)); + return {}; + } + + return getProcessName(h.get()); +} + QString getProcessName(HANDLE process) { const QString badName = "unknown"; diff --git a/src/envmodule.h b/src/envmodule.h index 6c0a028d..d152b840 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -7,6 +7,23 @@ namespace env { +// used by HandlePtr, calls CloseHandle() as the deleter +// +struct HandleCloser +{ + using pointer = HANDLE; + + void operator()(HANDLE h) + { + if (h != INVALID_HANDLE_VALUE) { + ::CloseHandle(h); + } + } +}; + +using HandlePtr = std::unique_ptr<HANDLE, HandleCloser>; + + // represents one module // class Module @@ -101,25 +118,39 @@ private: class Process { public: - Process(DWORD pid, QString name); + Process(); + explicit Process(HANDLE h); + Process(DWORD pid, DWORD ppid, QString name); + bool isValid() const; DWORD pid() const; + DWORD ppid() const; const QString& name() const; + HandlePtr openHandleForWait() const; + // whether this process can be accessed; fails if the current process doesn't // have the proper permissions // bool canAccess() const; + void addChild(Process p); + std::vector<Process>& children(); + private: DWORD m_pid; - QString m_name; + mutable std::optional<DWORD> m_ppid; + mutable std::optional<QString> m_name; + std::vector<Process> m_children; }; std::vector<Process> getRunningProcesses(); std::vector<Module> getLoadedModules(); +Process getProcessTree(HANDLE parent); + +QString getProcessName(DWORD pid); QString getProcessName(HANDLE process); DWORD getProcessParentID(DWORD pid); diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 786291c6..6d62728b 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -1,5 +1,6 @@ #include "envsecurity.h" #include "env.h" +#include "envmodule.h" #include <utility.h> #include <log.h> diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 3932a9b5..98e78a3e 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -1,5 +1,6 @@ #include "envwindows.h" #include "env.h" +#include "envmodule.h" #include <utility.h> #include <log.h> diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h index 9475ddb9..4d1e786f 100644 --- a/src/ilockedwaitingforprocess.h +++ b/src/ilockedwaitingforprocess.h @@ -8,6 +8,7 @@ class ILockedWaitingForProcess public: virtual bool unlockForced() const = 0; virtual void setProcessName(QString const &) = 0; + virtual void setProcessInformation(DWORD pid, const QString& name) = 0; }; #endif // ILOCKEDWAITINGFORPROCESS_H diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp index b18f7429..0876a511 100644 --- a/src/lockeddialogbase.cpp +++ b/src/lockeddialogbase.cpp @@ -18,7 +18,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ #include "lockeddialogbase.h" - +#include "envmodule.h" #include <QPoint> #include <QResizeEvent> #include <QWidget> @@ -63,6 +63,11 @@ bool LockedDialogBase::canceled() const { return m_Canceled; } +void LockedDialogBase::setProcessInformation(DWORD pid, const QString& name) +{ + setProcessName(QString("%1 (%2)").arg(name).arg(pid)); +} + void LockedDialogBase::unlock() { m_Unlocked = true; } diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h index 3c974a38..4ebad4c7 100644 --- a/src/lockeddialogbase.h +++ b/src/lockeddialogbase.h @@ -30,7 +30,7 @@ class QWidget; /** * a small borderless dialog displayed while the Mod Organizer UI is locked * The dialog contains only a label and a button to force the UI to be unlocked - * + * * The UI gets locked while running external applications since they may modify the * data on which Mod Organizer works. After the UI is unlocked (manually or after the * external application closed) MO will refresh all of its data sources @@ -46,6 +46,8 @@ public: virtual bool canceled() const; + void setProcessInformation(DWORD pid, const QString& name) override; + protected: virtual void resizeEvent(QResizeEvent *event); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2a97b998..eda64c1d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -34,6 +34,7 @@ #include "instancemanager.h" #include <scriptextender.h> #include "previewdialog.h" +#include "env.h" #include "envmodule.h" #include <QApplication> @@ -85,6 +86,45 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } +env::Process* getInterestingProcess(std::vector<env::Process>& processes) +{ + if (processes.empty()) { + return nullptr; + } + + // Certain process names we wish to "hide" for aesthetic reason: + const std::vector<QString> hiddenList = { + QFileInfo(QCoreApplication::applicationFilePath()).fileName() + }; + + auto isHidden = [&](auto&& p) { + for (auto h : hiddenList) { + if (p.name().contains(h, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }; + + + for (auto&& root : processes) { + if (!isHidden(root)) { + return &root; + } + + for (auto&& child : root.children()) { + if (!isHidden(child)) { + return &child; + } + } + } + + + // everything is hidden, just pick the first one + return &processes[0]; +} + OrganizerCore::OrganizerCore(Settings &settings) : m_MainWindow(nullptr) @@ -1367,12 +1407,31 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) bool OrganizerCore::waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) { - const auto r = spawn::waitForProcess(handle, exitCode, uilock); + const auto tree = env::getProcessTree(handle); + std::vector<env::Process> processes = {tree}; + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + return true; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + return true; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = spawn::waitForProcess( + interestingHandle.get(), exitCode, progress); switch (r) { case spawn::WaitResults::Completed: // fall-through - case spawn::WaitResults::Unlocked: + case spawn::WaitResults::Cancelled: return true; case spawn::WaitResults::Error: // fall-through @@ -1395,60 +1454,39 @@ bool OrganizerCore::waitForAllUSVFSProcessesWithLock() return r; } -HANDLE getInterestingProcess( - const std::vector<HANDLE>& handles, - const std::vector<QString>& hidden, DWORD preferedParentPid) -{ - HANDLE best_match = INVALID_HANDLE_VALUE; - bool best_match_hidden = true; - - for (auto handle : handles) { - const QString pname = env::getProcessName(handle); - - bool phidden = false; - for (auto h : hidden) - if (pname.contains(h, Qt::CaseInsensitive)) - phidden = true; - - bool pprefered = preferedParentPid && env::getProcessParentID(handle) == preferedParentPid; - - if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { - best_match = handle; - best_match_hidden = phidden; - } - - if (!phidden && pprefered) - return best_match; - } - - return best_match; -} - bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) { - // Certain process names we wish to "hide" for aesthetic reason: - std::vector<QString> hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - for (;;) { const auto handles = getRunningUSVFSProcesses(); if (handles.empty()) { break; } - const auto interesting = getInterestingProcess( - handles, hiddenList, GetCurrentProcessId()); + std::vector<env::Process> processes; + for (auto&& h : handles) { + auto p = env::getProcessTree(h); + if (p.isValid()) { + processes.emplace_back(std::move(p)); + } + } + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + break; + } if (uilock) { - const DWORD pid = ::GetProcessId(interesting); - const QString processName = QString("%1 (%2)") - .arg(env::getProcessName(interesting)) - .arg(pid); + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } - uilock->setProcessName(processName); + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + break; } - const auto r = spawn::waitForProcess(interesting, nullptr, uilock); + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = spawn::waitForProcess( + interestingHandle.get(), nullptr, progress); switch (r) { @@ -1456,7 +1494,7 @@ bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) // this process is completed, check for others break; - case spawn::WaitResults::Unlocked: + case spawn::WaitResults::Cancelled: // force unlocked log::debug("waiting for process completion aborted by UI"); return true; @@ -1468,7 +1506,7 @@ bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) } } - log::debug("Waiting for process completion successful"); + log::debug("waiting for process completion successful"); return true; } diff --git a/src/spawn.cpp b/src/spawn.cpp index 1003024f..0ea60641 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1095,32 +1095,25 @@ FileExecutionContext getFileExecutionContext( }
WaitResults waitForProcess(
- HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock)
+ HANDLE handle, DWORD* exitCode, std::function<bool ()> progress)
{
if (handle == INVALID_HANDLE_VALUE) {
return WaitResults::Error;
}
- const DWORD pid = ::GetProcessId(handle);
- const QString processName = QString("%1 (%2)")
- .arg(env::getProcessName(handle))
- .arg(pid);
-
- if (uilock)
- uilock->setProcessName(processName);
-
- log::debug(
- "waiting for process completion '{}' ({})",
- processName, pid);
+ log::debug("waiting for completion on pid {}", ::GetProcessId(handle));
std::vector<HANDLE> handles;
handles.push_back(handle);
std::vector<DWORD> exitCodes;
- const auto r = waitForProcesses(handles, exitCodes, uilock);
- if (exitCode && !exitCodes.empty()) {
- *exitCode = exitCodes[0];
+ const auto r = waitForProcesses(handles, exitCodes, progress);
+
+ if (r == WaitResults::Completed) {
+ if (exitCode && !exitCodes.empty()) {
+ *exitCode = exitCodes[0];
+ }
}
return r;
@@ -1128,7 +1121,7 @@ WaitResults waitForProcess( WaitResults waitForProcesses(
const std::vector<HANDLE>& handles, std::vector<DWORD>& exitCodes,
- ILockedWaitingForProcess* uilock)
+ std::function<bool ()> progress)
{
if (handles.empty()) {
return WaitResults::Completed;
@@ -1175,8 +1168,8 @@ WaitResults waitForProcesses( QCoreApplication::sendPostedEvents();
QCoreApplication::processEvents();
- if (uilock && uilock->unlockForced()) {
- return WaitResults::Unlocked;
+ if (progress && progress()) {
+ return WaitResults::Cancelled;
}
}
}
diff --git a/src/spawn.h b/src/spawn.h index 6b947f2f..ad50bde6 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,7 +27,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDir>
class Settings;
-class ILockedWaitingForProcess;
namespace MOBase { class IPluginGame; }
@@ -130,15 +129,15 @@ enum class WaitResults {
Completed = 1,
Error,
- Unlocked
+ Cancelled
};
WaitResults waitForProcess(
- HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock);
+ HANDLE handle, DWORD* exitCode, std::function<bool ()> progress);
WaitResults waitForProcesses(
const std::vector<HANDLE>& handles, std::vector<DWORD>& exitCodes,
- ILockedWaitingForProcess* uilock);
+ std::function<bool ()> progress);
} // namespace
|
