summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-11-11 14:14:01 -0500
committerGitHub <noreply@github.com>2019-11-11 14:14:01 -0500
commitd6c3bad01a85c1a5b1a3bdd86efc85efdd4c5f29 (patch)
treefddae0025e65bd1d5064bf8ad6fa73a23aa750e9 /src
parent7b3c5dcbb3b5d520d166eb5b51f669968f57302e (diff)
parentdecd5c1828f495be4e230c9fc6fb79dd9bfdfb81 (diff)
Merge pull request #887 from isanae/spawning-and-waiting
Spawning, waiting and locking
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt23
-rw-r--r--src/editexecutablesdialog.cpp3
-rw-r--r--src/env.cpp31
-rw-r--r--src/env.h17
-rw-r--r--src/envmodule.cpp185
-rw-r--r--src/envmodule.h40
-rw-r--r--src/envsecurity.cpp1
-rw-r--r--src/envwindows.cpp1
-rw-r--r--src/ilockedwaitingforprocess.h13
-rw-r--r--src/iuserinterface.h6
-rw-r--r--src/lockeddialog.cpp71
-rw-r--r--src/lockeddialog.h57
-rw-r--r--src/lockeddialog.ui98
-rw-r--r--src/lockeddialogbase.cpp73
-rw-r--r--src/lockeddialogbase.h62
-rw-r--r--src/main.cpp21
-rw-r--r--src/mainwindow.cpp191
-rw-r--r--src/mainwindow.h8
-rw-r--r--src/modinfodialogconflicts.cpp6
-rw-r--r--src/organizercore.cpp753
-rw-r--r--src/organizercore.h65
-rw-r--r--src/organizerproxy.cpp58
-rw-r--r--src/pch.h47
-rw-r--r--src/plugincontainer.cpp1
-rw-r--r--src/processrunner.cpp791
-rw-r--r--src/processrunner.h172
-rw-r--r--src/spawn.cpp279
-rw-r--r--src/spawn.h24
-rw-r--r--src/uilocker.cpp548
-rw-r--r--src/uilocker.h95
-rw-r--r--src/usvfsconnector.cpp47
-rw-r--r--src/usvfsconnector.h2
-rw-r--r--src/waitingonclosedialog.cpp71
-rw-r--r--src/waitingonclosedialog.h56
-rw-r--r--src/waitingonclosedialog.ui119
35 files changed, 2440 insertions, 1595 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 180422ef..d935981d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -82,9 +82,6 @@ SET(organizer_SRCS
main.cpp
loghighlighter.cpp
loglist.cpp
- lockeddialogbase.cpp
- lockeddialog.cpp
- waitingonclosedialog.cpp
loadmechanism.cpp
installationmanager.cpp
filedialogmemory.cpp
@@ -143,6 +140,8 @@ SET(organizer_SRCS
envwindows.cpp
colortable.cpp
sanitychecks.cpp
+ processrunner.cpp
+ uilocker.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -204,9 +203,6 @@ SET(organizer_HDRS
mainwindow.h
loghighlighter.h
loglist.h
- lockeddialogbase.h
- lockeddialog.h
- waitingonclosedialog.h
loadmechanism.h
installationmanager.h
filedialogmemory.h
@@ -244,7 +240,6 @@ SET(organizer_HDRS
viewmarkingscrollbar.h
plugincontainer.h
organizercore.h
- ilockedwaitingforprocess.h
iuserinterface.h
instancemanager.h
usvfsconnector.h
@@ -266,6 +261,8 @@ SET(organizer_HDRS
envshortcut.h
envwindows.h
colortable.h
+ processrunner.h
+ uilocker.h
shared/windows_error.h
shared/error_report.h
@@ -289,8 +286,6 @@ SET(organizer_UIS
modinfodialog.ui
messagedialog.ui
mainwindow.ui
- lockeddialog.ui
- waitingonclosedialog.ui
editexecutablesdialog.ui
credentialsdialog.ui
categoriesdialog.ui
@@ -348,6 +343,8 @@ set(core
organizercore
organizerproxy
apiuseraccount
+ processrunner
+ uilocker
)
set(dialogs
@@ -392,12 +389,6 @@ set(executables
editexecutablesdialog
)
-set(locking
- ilockedwaitingforprocess
- lockeddialog
- lockeddialogbase
-)
-
set(modinfo
modinfo
modinfobackup
@@ -494,7 +485,7 @@ set(widgets
)
set(src_filters
- application core browser dialogs downloads env executables locking modinfo
+ application core browser dialogs downloads env executables modinfo
modinfo\\dialog modlist plugins previews profiles settings settingsdialog
utilities widgets
)
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp
index 8535b7a7..32b31357 100644
--- a/src/editexecutablesdialog.cpp
+++ b/src/editexecutablesdialog.cpp
@@ -24,6 +24,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modlist.h"
#include "forcedloaddialog.h"
#include "organizercore.h"
+#include "spawn.h"
#include <QMessageBox>
#include <Shellapi.h>
@@ -800,7 +801,7 @@ QFileInfo EditExecutablesDialog::browseBinary(const QString& initial)
void EditExecutablesDialog::setJarBinary(const QFileInfo& binary)
{
- auto java = OrganizerCore::findJavaInstallation(binary.absoluteFilePath());
+ auto java = spawn::findJavaInstallation(binary.absoluteFilePath());
if (java.isEmpty()) {
QMessageBox::information(
diff --git a/src/env.cpp b/src/env.cpp
index 78b5dc96..507607d1 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -190,19 +190,36 @@ QString setPath(const QString& s)
QString get(const QString& name)
{
- std::wstring s(4000, L' ');
+ std::size_t bufferSize = 4000;
+ auto buffer = std::make_unique<wchar_t[]>(bufferSize);
DWORD realSize = ::GetEnvironmentVariableW(
- name.toStdWString().c_str(), s.data(), static_cast<DWORD>(s.size()));
+ name.toStdWString().c_str(),
+ buffer.get(), static_cast<DWORD>(bufferSize));
- if (realSize > s.size()) {
- s.resize(realSize);
+ if (realSize > bufferSize) {
+ bufferSize = realSize;
+ buffer = std::make_unique<wchar_t[]>(bufferSize);
- ::GetEnvironmentVariableW(
- name.toStdWString().c_str(), s.data(), static_cast<DWORD>(s.size()));
+ realSize = ::GetEnvironmentVariableW(
+ name.toStdWString().c_str(),
+ buffer.get(), static_cast<DWORD>(bufferSize));
}
- return QString::fromStdWString(s);
+ if (realSize == 0) {
+ const auto e = ::GetLastError();
+
+ // don't log if not found
+ if (e != ERROR_ENVVAR_NOT_FOUND) {
+ log::error(
+ "failed to get environment variable '{}', {}",
+ name, formatSystemMessage(e));
+ }
+
+ return {};
+ }
+
+ return QString::fromWCharArray(buffer.get(), realSize);
}
QString set(const QString& n, const QString& v)
diff --git a/src/env.h b/src/env.h
index 1760c7fe..f95d1013 100644
--- a/src/env.h
+++ b/src/env.h
@@ -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 3f1f8912..09593e61 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()
{
@@ -408,7 +460,8 @@ std::vector<Module> getLoadedModules()
}
-std::vector<Process> getRunningProcesses()
+template <class F>
+void forEachRunningProcess(F&& f)
{
HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
@@ -416,7 +469,7 @@ std::vector<Process> getRunningProcesses()
{
const auto e = GetLastError();
log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e));
- return {};
+ return;
}
PROCESSENTRY32 entry = {};
@@ -427,16 +480,14 @@ std::vector<Process> getRunningProcesses()
if (!Process32First(snapshot.get(), &entry)) {
const auto e = GetLastError();
log::error("Process32First() failed, {}", formatSystemMessage(e));
- return {};
+ return;
}
- std::vector<Process> v;
-
for (;;)
{
- v.push_back(Process(
- entry.th32ProcessID,
- QString::fromStdWString(entry.szExeFile)));
+ if (!f(entry)) {
+ break;
+ }
// next process
if (!Process32Next(snapshot.get(), &entry))
@@ -450,8 +501,120 @@ std::vector<Process> getRunningProcesses()
break;
}
}
+}
+
+std::vector<Process> getRunningProcesses()
+{
+ std::vector<Process> v;
+
+ forEachRunningProcess([&](auto&& entry) {
+ v.push_back(Process(
+ entry.th32ProcessID,
+ entry.th32ParentProcessID,
+ QString::fromStdWString(entry.szExeFile)));
+
+ return true;
+ });
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) {
+ 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";
+
+ if (process == 0 || process == INVALID_HANDLE_VALUE) {
+ return badName;
+ }
+
+ const DWORD bufferSize = MAX_PATH;
+ wchar_t buffer[bufferSize + 1] = {};
+
+ const auto realSize = ::GetProcessImageFileNameW(process, buffer, bufferSize);
+
+ if (realSize == 0) {
+ const auto e = ::GetLastError();
+ log::error("GetProcessImageFileNameW() failed, {}", formatSystemMessage(e));
+ return badName;
+ }
+
+ auto s = QString::fromWCharArray(buffer, realSize);
+
+ const auto lastSlash = s.lastIndexOf("\\");
+ if (lastSlash != -1) {
+ s = s.mid(lastSlash + 1);
+ }
+
+ return s;
+}
+
+DWORD getProcessParentID(DWORD pid)
+{
+ DWORD ppid = 0;
+
+ forEachRunningProcess([&](auto&& entry) {
+ if (entry.th32ProcessID == pid) {
+ ppid = entry.th32ParentProcessID;
+ return false;
+ }
+
+ return true;
+ });
+
+ return ppid;
+}
+
+
+DWORD getProcessParentID(HANDLE handle)
+{
+ return getProcessParentID(GetProcessId(handle));
+}
+
} // namespace
diff --git a/src/envmodule.h b/src/envmodule.h
index deb7520f..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,44 @@ 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);
+DWORD getProcessParentID(HANDLE handle);
+
} // namespace env
#endif // ENV_MODULE_H
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
deleted file mode 100644
index 9475ddb9..00000000
--- a/src/ilockedwaitingforprocess.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#ifndef ILOCKEDWAITINGFORPROCESS_H
-#define ILOCKEDWAITINGFORPROCESS_H
-
-class QString;
-
-class ILockedWaitingForProcess
-{
-public:
- virtual bool unlockForced() const = 0;
- virtual void setProcessName(QString const &) = 0;
-};
-
-#endif // ILOCKEDWAITINGFORPROCESS_H
diff --git a/src/iuserinterface.h b/src/iuserinterface.h
index a309ed9b..aa48194f 100644
--- a/src/iuserinterface.h
+++ b/src/iuserinterface.h
@@ -3,13 +3,12 @@
#include "modinfodialogfwd.h"
-#include "ilockedwaitingforprocess.h"
#include <iplugintool.h>
#include <ipluginmodpage.h>
#include <delayedfilewriter.h>
-
#include <QMenu>
+
class IUserInterface
{
public:
@@ -31,8 +30,7 @@ public:
virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0;
- virtual ILockedWaitingForProcess* lock() = 0;
- virtual void unlock() = 0;
+ virtual QWidget* qtWidget() = 0;
};
#endif // IUSERINTERFACE_H
diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp
deleted file mode 100644
index 143d5838..00000000
--- a/src/lockeddialog.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "lockeddialog.h"
-#include "ui_lockeddialog.h"
-
-#include <QPoint>
-#include <QResizeEvent>
-#include <QWidget>
-#include <Qt> // for Qt::FramelessWindowHint, etc
-
-LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton)
- : LockedDialogBase(parent, !unlockByButton)
- , ui(new Ui::LockedDialog)
-{
- ui->setupUi(this);
-
- // Supposedly the Qt::CustomizeWindowHint should use a customized window
- // allowing us to select if there is a close button. In practice this doesn't
- // seem to work. We will ignore pressing the close button if unlockByButton == true
- Qt::WindowFlags flags =
- this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint;
- if (m_allowClose)
- flags |= Qt::WindowCloseButtonHint;
- this->setWindowFlags(flags);
-
- if (!unlockByButton)
- {
- ui->unlockButton->hide();
- ui->verticalLayout->addItem(
- new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding));
- }
-}
-
-LockedDialog::~LockedDialog()
-{
- delete ui;
-}
-
-
-void LockedDialog::setProcessName(const QString &name)
-{
- ui->processLabel->setText(name);
-}
-
-void LockedDialog::on_unlockButton_clicked()
-{
- unlock();
-}
-
-void LockedDialog::unlock() {
- LockedDialogBase::unlock();
- ui->label->setText("unlocking may take a few seconds");
- ui->unlockButton->setEnabled(false);
-}
diff --git a/src/lockeddialog.h b/src/lockeddialog.h
deleted file mode 100644
index 36c16429..00000000
--- a/src/lockeddialog.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#pragma once
-
-#include "lockeddialogbase.h"
-
-namespace Ui {
- class LockedDialog;
-}
-
-/**
- * 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
- **/
-class LockedDialog : public LockedDialogBase
-{
- Q_OBJECT
-
-public:
- explicit LockedDialog(QWidget *parent = 0, bool unlockByButton = false);
- ~LockedDialog();
-
- void setProcessName(const QString &name) override;
-
-protected:
-
- void unlock() override;
-
-private slots:
-
- void on_unlockButton_clicked();
-
-private:
-
- Ui::LockedDialog *ui;
-};
diff --git a/src/lockeddialog.ui b/src/lockeddialog.ui
deleted file mode 100644
index 0ec2e467..00000000
--- a/src/lockeddialog.ui
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>LockedDialog</class>
- <widget class="QDialog" name="LockedDialog">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>317</width>
- <height>151</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Running virtualized processes</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0">
- <item>
- <widget class="QLabel" name="label">
- <property name="toolTip">
- <string>This dialog should disappear automatically if the application/game is done. Click unlock if it didn't.</string>
- </property>
- <property name="text">
- <string>MO is locked while the executable is running.</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer1">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <height>10</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="processLabel">
- <property name="font">
- <font>
- <italic>true</italic>
- </font>
- </property>
- <property name="styleSheet">
- <string notr="true">color: grey;</string>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <height>10</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="unlockButton">
- <property name="text">
- <string>Unlock</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer3">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <height>10</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp
deleted file mode 100644
index b18f7429..00000000
--- a/src/lockeddialogbase.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "lockeddialogbase.h"
-
-#include <QPoint>
-#include <QResizeEvent>
-#include <QWidget>
-#include <Qt> // for Qt::FramelessWindowHint, etc
-
-LockedDialogBase::LockedDialogBase(QWidget *parent, bool allowClose)
- : QDialog(parent)
- , m_Unlocked(false)
- , m_Canceled(false)
- , m_allowClose(allowClose)
-{
- if (parent != nullptr) {
- QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2));
- position.rx() -= this->width() / 2;
- position.ry() -= this->height() / 2;
- move(position);
- }
-}
-
-void LockedDialogBase::resizeEvent(QResizeEvent *event)
-{
- QWidget *par = parentWidget();
- if (par != nullptr) {
- QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2));
- position.rx() -= event->size().width() / 2;
- position.ry() -= event->size().height() / 2;
- move(position);
- }
-}
-
-void LockedDialogBase::reject()
-{
- if (m_allowClose)
- unlock();
-}
-
-bool LockedDialogBase::unlockForced() const {
- return m_Unlocked;
-}
-
-bool LockedDialogBase::canceled() const {
- return m_Canceled;
-}
-
-void LockedDialogBase::unlock() {
- m_Unlocked = true;
-}
-
-void LockedDialogBase::cancel() {
- m_Canceled = true;
-}
-
diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h
deleted file mode 100644
index 3c974a38..00000000
--- a/src/lockeddialogbase.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#pragma once
-
-#include "ilockedwaitingforprocess.h"
-#include <QDialog> // for QDialog
-#include <QObject> // for Q_OBJECT, slots
-#include <QString> // for QString
-
-class QResizeEvent;
-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
- **/
-class LockedDialogBase : public QDialog, public ILockedWaitingForProcess
-{
- Q_OBJECT
-
-public:
- explicit LockedDialogBase(QWidget *parent, bool allowClose);
-
- bool unlockForced() const override;
-
- virtual bool canceled() const;
-
-protected:
-
- virtual void resizeEvent(QResizeEvent *event);
-
- virtual void reject();
-
- virtual void unlock();
-
- virtual void cancel();
-
- bool m_Unlocked;
- bool m_Canceled;
- bool m_allowClose;
-};
diff --git a/src/main.cpp b/src/main.cpp
index 776c3775..02347ee3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -632,7 +632,11 @@ int runApplication(MOApplication &application, SingleInstance &instance,
if (MOShortcut shortcut{ arguments.at(1) }) {
if (shortcut.hasExecutable()) {
try {
- organizer.runShortcut(shortcut);
+ organizer.processRunner()
+ .setFromShortcut(shortcut)
+ .setWaitForCompletion()
+ .run();
+
return 0;
}
catch (const std::exception &e) {
@@ -649,14 +653,21 @@ int runApplication(MOApplication &application, SingleInstance &instance,
else {
QString exeName = arguments.at(1);
log::debug("starting {} from command line", exeName);
+
arguments.removeFirst(); // remove application name (ModOrganizer.exe)
arguments.removeFirst(); // remove binary name
- // pass the remaining parameters to the binary
- try {
- organizer.startApplication(exeName, arguments, QString(), QString());
+
+ try
+ {
+ // pass the remaining parameters to the binary
+ organizer.processRunner()
+ .setFromFileOrExecutable(exeName, arguments)
+ .setWaitForCompletion()
+ .run();
return 0;
}
- catch (const std::exception &e) {
+ catch (const std::exception &e)
+ {
reportError(
QObject::tr("failed to start application: %1").arg(e.what()));
return 1;
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 12ed40b3..9453f07c 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -57,8 +57,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "downloadlistwidget.h"
#include "messagedialog.h"
#include "installationmanager.h"
-#include "lockeddialog.h"
-#include "waitingonclosedialog.h"
#include "downloadlistsortproxy.h"
#include "motddialog.h"
#include "filedialogmemory.h"
@@ -411,7 +409,7 @@ MainWindow::MainWindow(Settings &settings
m_Tutorial.expose("modList", m_OrganizerCore.modList());
m_Tutorial.expose("espList", m_OrganizerCore.pluginList());
- m_OrganizerCore.setUserInterface(this, this);
+ m_OrganizerCore.setUserInterface(this);
for (const QString &fileName : m_PluginContainer.pluginFileNames()) {
installTranslator(QFileInfo(fileName).baseName());
}
@@ -595,7 +593,7 @@ MainWindow::~MainWindow()
cleanup();
m_PluginContainer.setUserInterface(nullptr, nullptr);
- m_OrganizerCore.setUserInterface(nullptr, nullptr);
+ m_OrganizerCore.setUserInterface(nullptr);
m_IntegratedBrowser.close();
delete ui;
} catch (std::exception &e) {
@@ -1311,16 +1309,9 @@ bool MainWindow::canExit()
}
}
- std::vector<QString> 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_exitAfterWait = true;
- m_OrganizerCore.waitForApplication(injected_process_still_running);
- if (!m_exitAfterWait) { // if operation cancelled
- return false;
- }
+ const auto r = m_OrganizerCore.waitForAllUSVFSProcesses();
+ if (r == ProcessRunner::Cancelled) {
+ return false;
}
setCursor(Qt::WaitCursor);
@@ -1542,26 +1533,12 @@ void MainWindow::startExeAction()
}
action->setEnabled(false);
- const Executable& exe = *itor;
- auto& profile = *m_OrganizerCore.currentProfile();
-
- QString customOverwrite = profile.setting("custom_overwrites", exe.title()).toString();
- auto forcedLibraries = profile.determineForcedLibraries(exe.title());
-
- if (!profile.forcedLibrariesEnabled(exe.title())) {
- forcedLibraries.clear();
- }
-
- m_OrganizerCore.spawnBinary(
- exe.binaryInfo(), exe.arguments(),
- exe.workingDirectory().length() != 0
- ? exe.workingDirectory()
- : exe.binaryInfo().absolutePath(),
- exe.steamAppID(),
- customOverwrite,
- forcedLibraries);
- action->setEnabled(true);
+ Guard g([&]{ action->setEnabled(true); });
+ m_OrganizerCore.processRunner()
+ .setFromExecutable(*itor)
+ .setWaitForCompletion(ProcessRunner::Refresh)
+ .run();
}
void MainWindow::activateSelectedProfile()
@@ -2282,40 +2259,9 @@ void MainWindow::storeSettings()
s.widgets().saveIndex(ui->executablesListBox);
}
-ILockedWaitingForProcess* MainWindow::lock()
-{
- if (m_LockDialog != nullptr) {
- ++m_LockCount;
- return m_LockDialog;
- }
- if (m_exitAfterWait)
- m_LockDialog = new WaitingOnCloseDialog(this);
- else
- m_LockDialog = new LockedDialog(this, true);
- m_LockDialog->setModal(true);
- m_LockDialog->show();
- setEnabled(false);
- m_LockDialog->setEnabled(true); //What's the point otherwise?
- ++m_LockCount;
- return m_LockDialog;
-}
-
-void MainWindow::unlock()
+QWidget* MainWindow::qtWidget()
{
- //If you come through here with a null lock pointer, it's a bug!
- if (m_LockDialog == nullptr) {
- log::debug("Unlocking main window when already unlocked");
- return;
- }
- --m_LockCount;
- if (m_LockCount == 0) {
- if (m_exitAfterWait && m_LockDialog->canceled())
- m_exitAfterWait = false;
- m_LockDialog->hide();
- m_LockDialog->deleteLater();
- m_LockDialog = nullptr;
- setEnabled(true);
- }
+ return this;
}
void MainWindow::on_btnRefreshData_clicked()
@@ -2367,41 +2313,18 @@ void MainWindow::installMod(QString fileName)
void MainWindow::on_startButton_clicked()
{
- try {
- const Executable* selectedExecutable = getSelectedExecutable();
- if (!selectedExecutable) {
- return;
- }
-
- ui->startButton->setEnabled(false);
-
- auto* profile = m_OrganizerCore.currentProfile();
-
- const QString customOverwrite = profile->setting(
- "custom_overwrites", selectedExecutable->title()).toString();
-
- auto forcedLibraries = profile->determineForcedLibraries(
- selectedExecutable->title());
-
- if (!profile->forcedLibrariesEnabled(selectedExecutable->title())) {
- forcedLibraries.clear();
- }
-
- m_OrganizerCore.spawnBinary(
- selectedExecutable->binaryInfo(),
- selectedExecutable->arguments(),
- selectedExecutable->workingDirectory().length() != 0 ?
- selectedExecutable->workingDirectory() :
- selectedExecutable->binaryInfo().absolutePath(),
- selectedExecutable->steamAppID(),
- customOverwrite,
- forcedLibraries);
- } catch (...) {
- ui->startButton->setEnabled(true);
- throw;
+ const Executable* selectedExecutable = getSelectedExecutable();
+ if (!selectedExecutable) {
+ return;
}
- ui->startButton->setEnabled(true);
+ ui->startButton->setEnabled(false);
+ Guard g([&]{ ui->startButton->setEnabled(true); });
+
+ m_OrganizerCore.processRunner()
+ .setFromExecutable(*selectedExecutable)
+ .setWaitForCompletion(ProcessRunner::Refresh)
+ .run();
}
bool MainWindow::modifyExecutablesDialog(int selection)
@@ -5313,43 +5236,42 @@ void MainWindow::addAsExecutable()
return;
}
- using FileExecutionTypes = OrganizerCore::FileExecutionTypes;
-
- QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString());
- QFileInfo binaryInfo;
- QString arguments;
- FileExecutionTypes type;
+ const QFileInfo target(m_ContextItem->data(0, Qt::UserRole).toString());
+ const auto fec = spawn::getFileExecutionContext(this, target);
- if (!OrganizerCore::getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) {
- return;
- }
-
- switch (type)
+ switch (fec.type)
{
- case FileExecutionTypes::Executable: {
- QString name = QInputDialog::getText(this, tr("Enter Name"),
- tr("Please enter a name for the executable"), QLineEdit::Normal,
- targetInfo.completeBaseName());
-
- if (!name.isEmpty()) {
- //Note: If this already exists, you'll lose custom settings
- m_OrganizerCore.executablesList()->setExecutable(Executable()
- .title(name)
- .binaryInfo(binaryInfo)
- .arguments(arguments)
- .workingDirectory(targetInfo.absolutePath()));
+ case spawn::FileExecutionTypes::Executable:
+ {
+ const QString name = QInputDialog::getText(
+ this, tr("Enter Name"),
+ tr("Enter a name for the executable"),
+ QLineEdit::Normal,
+ target.completeBaseName());
- refreshExecutablesList();
- }
+ if (!name.isEmpty()) {
+ //Note: If this already exists, you'll lose custom settings
+ m_OrganizerCore.executablesList()->setExecutable(Executable()
+ .title(name)
+ .binaryInfo(fec.binary)
+ .arguments(fec.arguments)
+ .workingDirectory(target.absolutePath()));
- break;
+ refreshExecutablesList();
}
- case FileExecutionTypes::Other: // fall-through
- default: {
- QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable."));
- break;
- }
+ break;
+ }
+
+ case spawn::FileExecutionTypes::Other: // fall-through
+ default:
+ {
+ QMessageBox::information(
+ this, tr("Not an executable"),
+ tr("This is not a recognized executable."));
+
+ break;
+ }
}
}
@@ -5476,8 +5398,13 @@ void MainWindow::openDataFile()
return;
}
- QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString());
- m_OrganizerCore.executeFileVirtualized(this, targetInfo);
+ const QString path = m_ContextItem->data(0, Qt::UserRole).toString();
+ const QFileInfo targetInfo(path);
+
+ m_OrganizerCore.processRunner()
+ .setFromFile(this, targetInfo)
+ .setWaitForCompletion(ProcessRunner::Refresh)
+ .run();
}
void MainWindow::openDataOriginExplorer_clicked()
diff --git a/src/mainwindow.h b/src/mainwindow.h
index dbbd0bd9..c80287b2 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -38,7 +38,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
//when I get round to cleaning up main.cpp
class Executable;
class CategoryFactory;
-class LockedDialogBase;
class OrganizerCore;
class PluginListSortProxy;
@@ -118,8 +117,7 @@ public:
void processUpdates(Settings& settings);
- virtual ILockedWaitingForProcess* lock() override;
- virtual void unlock() override;
+ QWidget* qtWidget() override;
bool addProfile();
void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives);
@@ -380,11 +378,7 @@ private:
bool m_DidUpdateMasterList;
- LockedDialogBase *m_LockDialog { nullptr };
- uint64_t m_LockCount { 0 };
-
bool m_showArchiveData{ true };
- bool m_exitAfterWait{ false };
MOBase::DelayedFileWriter m_ArchiveListWriter;
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
index 36559a75..d37f068c 100644
--- a/src/modinfodialogconflicts.cpp
+++ b/src/modinfodialogconflicts.cpp
@@ -527,7 +527,11 @@ void ConflictsTab::openItems(QTreeView* tree)
// the menu item is only shown for a single selection, but handle all of them
// in case this changes
for_each_in_selection(tree, [&](const ConflictItem* item) {
- core().executeFileVirtualized(parentWidget(), item->fileName());
+ core().processRunner()
+ .setFromFile(parentWidget(), item->fileName())
+ .setWaitForCompletion()
+ .run();
+
return true;
});
}
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 5613e8ce..9ceb149e 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1,5 +1,4 @@
#include "organizercore.h"
-
#include "delayedfilewriter.h"
#include "guessedvalue.h"
#include "imodinterface.h"
@@ -30,10 +29,11 @@
#include "appconfig.h"
#include <report.h>
#include <questionboxmemory.h>
-#include "lockeddialog.h"
#include "instancemanager.h"
#include <scriptextender.h>
#include "previewdialog.h"
+#include "env.h"
+#include "envmodule.h"
#include <QApplication>
#include <QCoreApplication>
@@ -74,47 +74,6 @@ using namespace MOBase;
//static
CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None;
-static std::wstring getProcessName(HANDLE process)
-{
- wchar_t buffer[MAX_PATH];
- const wchar_t *fileName = L"unknown";
-
- if (process == nullptr) return fileName;
-
- if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) {
- fileName = wcsrchr(buffer, L'\\');
- if (fileName == nullptr) {
- fileName = buffer;
- }
- else {
- fileName += 1;
- }
- }
-
- 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;
-}
-
template <typename InputIterator>
QStringList toStringList(InputIterator current, InputIterator end)
{
@@ -190,7 +149,7 @@ OrganizerCore::~OrganizerCore()
m_RefresherThread.exit();
m_RefresherThread.wait();
- prepareStart();
+ saveCurrentProfile();
// profile has to be cleaned up before the modinfo-buffer is cleared
delete m_CurrentProfile;
@@ -249,44 +208,49 @@ void OrganizerCore::updateExecutablesList()
m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
}
-void OrganizerCore::setUserInterface(IUserInterface *userInterface,
- QWidget *widget)
+void OrganizerCore::setUserInterface(IUserInterface* ui)
{
storeSettings();
- m_UserInterface = userInterface;
+ m_UserInterface = ui;
+
+ QWidget* w = nullptr;
+ if (m_UserInterface) {
+ w = m_UserInterface->qtWidget();
+ }
- if (widget != nullptr) {
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), widget,
+ if (w) {
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w,
SLOT(modlistChanged(QModelIndex, int)));
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), widget,
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w,
SLOT(modlistChanged(QModelIndexList, int)));
- connect(&m_ModList, SIGNAL(showMessage(QString)), widget,
+ connect(&m_ModList, SIGNAL(showMessage(QString)), w,
SLOT(showMessage(QString)));
- connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget,
+ connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w,
SLOT(modRenamed(QString, QString)));
- connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget,
+ connect(&m_ModList, SIGNAL(modUninstalled(QString)), w,
SLOT(modRemoved(QString)));
- connect(&m_ModList, SIGNAL(removeSelectedMods()), widget,
+ connect(&m_ModList, SIGNAL(removeSelectedMods()), w,
SLOT(removeMod_clicked()));
- connect(&m_ModList, SIGNAL(clearOverwrite()), widget,
+ connect(&m_ModList, SIGNAL(clearOverwrite()), w,
SLOT(clearOverwrite()));
- connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget,
+ connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w,
SLOT(displayColumnSelection(QPoint)));
- connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget,
+ connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w,
SLOT(fileMoved(QString, QString, QString)));
- connect(&m_ModList, SIGNAL(modorder_changed()), widget,
+ connect(&m_ModList, SIGNAL(modorder_changed()), w,
SLOT(modorder_changed()));
- connect(&m_PluginList, SIGNAL(writePluginsList()), widget,
+ connect(&m_PluginList, SIGNAL(writePluginsList()), w,
SLOT(esplist_changed()));
- connect(&m_PluginList, SIGNAL(esplist_changed()), widget,
+ connect(&m_PluginList, SIGNAL(esplist_changed()), w,
SLOT(esplist_changed()));
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget,
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w,
SLOT(showMessage(QString)));
}
- m_InstallationManager.setParentWidget(widget);
- m_Updater.setUserInterface(widget);
+ m_InstallationManager.setParentWidget(w);
+ m_Updater.setUserInterface(w);
+ m_UILocker.setUserInterface(w);
checkForUpdates();
}
@@ -390,8 +354,12 @@ void OrganizerCore::downloadRequestedNXM(const QString &url)
void OrganizerCore::externalMessage(const QString &message)
{
if (MOShortcut moshortcut{ message } ) {
- if(moshortcut.hasExecutable())
- runShortcut(moshortcut);
+ if(moshortcut.hasExecutable()) {
+ processRunner()
+ .setFromShortcut(moshortcut)
+ .setWaitForCompletion(ProcessRunner::Refresh)
+ .run();
+ }
}
else if (isNxmLink(message)) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
@@ -977,110 +945,6 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const
return res;
}
-QString OrganizerCore::findJavaInstallation(const QString& jarFile)
-{
- if (!jarFile.isEmpty()) {
- // try to find java automatically based on the given jar file
- std::wstring jarFileW = jarFile.toStdWString();
-
- WCHAR buffer[MAX_PATH];
- if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) {
- DWORD binaryType = 0UL;
- if (!::GetBinaryTypeW(buffer, &binaryType)) {
- log::debug(
- "failed to determine binary type of \"{}\": {}",
- QString::fromWCharArray(buffer), ::GetLastError());
- } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) {
- return QString::fromWCharArray(buffer);
- }
- }
- }
-
- // second attempt: look to the registry
- QSettings reg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat);
- if (reg.contains("CurrentVersion")) {
- QString currentVersion = reg.value("CurrentVersion").toString();
- return reg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe");
- }
-
- // not found
- return {};
-}
-
-bool OrganizerCore::getFileExecutionContext(
- QWidget* parent, const QFileInfo &targetInfo,
- QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type)
-{
- QString extension = targetInfo.suffix();
- if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) ||
- (extension.compare("com", Qt::CaseInsensitive) == 0) ||
- (extension.compare("bat", Qt::CaseInsensitive) == 0)) {
- binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe");
- arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
- type = FileExecutionTypes::Executable;
- return true;
- } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) {
- binaryInfo = targetInfo;
- type = FileExecutionTypes::Executable;
- return true;
- } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) {
- auto java = findJavaInstallation(targetInfo.absoluteFilePath());
-
- if (java.isEmpty()) {
- java = QFileDialog::getOpenFileName(
- parent, QObject::tr("Select binary"),
- QString(), QObject::tr("Binary") + " (*.exe)");
- }
-
- if (java.isEmpty()) {
- return false;
- }
-
- binaryInfo = QFileInfo(java);
- arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
- type = FileExecutionTypes::Executable;
-
- return true;
- } else {
- type = FileExecutionTypes::Other;
- return true;
- }
-}
-
-bool OrganizerCore::executeFileVirtualized(
- QWidget* parent, const QFileInfo& targetInfo)
-{
- QFileInfo binaryInfo;
- QString arguments;
- FileExecutionTypes type;
-
- if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) {
- return false;
- }
-
- switch (type)
- {
- case FileExecutionTypes::Executable: {
- spawnBinaryDirect(
- binaryInfo, arguments, currentProfile()->name(),
- targetInfo.absolutePath(), "", "");
-
- return true;
- }
-
- case FileExecutionTypes::Other: {
- ::ShellExecuteW(nullptr, L"open",
- ToWString(targetInfo.absoluteFilePath()).c_str(),
- nullptr, nullptr, SW_SHOWNORMAL);
-
- return true;
- }
- }
-
- // nop
- return false;
-}
-
bool OrganizerCore::previewFileWithAlternatives(
QWidget* parent, QString fileName, int selectedOrigin)
{
@@ -1209,475 +1073,6 @@ bool OrganizerCore::previewFile(
return true;
}
-void OrganizerCore::spawnBinary(const QFileInfo &binary,
- const QString &arguments,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries)
-{
- DWORD processExitCode = 0;
- HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode);
- if (processHandle != INVALID_HANDLE_VALUE) {
- refreshDirectoryStructure();
- // need to remove our stored load order because it may be outdated if a foreign tool changed the
- // file time. After removing that file, refreshESPList will use the file time as the order
- if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- log::debug("removing loadorder.txt");
- QFile::remove(m_CurrentProfile->getLoadOrderFileName());
- }
- refreshDirectoryStructure();
-
- refreshESPList(true);
- savePluginList();
-
- //These callbacks should not fiddle with directoy structure and ESPs.
- m_FinishedRun(binary.absoluteFilePath(), processExitCode);
- }
-}
-
-HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
- const QString &arguments,
- const QString &profileName,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries,
- LPDWORD exitCode)
-{
- HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries);
- if (Settings::instance().interface().lockGUI() && processHandle != INVALID_HANDLE_VALUE) {
- std::unique_ptr<LockedDialog> dlg;
- ILockedWaitingForProcess* uilock = nullptr;
-
- if (m_UserInterface != nullptr) {
- uilock = m_UserInterface->lock();
- }
- else {
- // i.e. when running command line shortcuts there is no m_UserInterface
- dlg.reset(new LockedDialog);
- dlg->show();
- dlg->setEnabled(true);
- uilock = dlg.get();
- }
-
- ON_BLOCK_EXIT([&]() {
- if (m_UserInterface != nullptr) {
- m_UserInterface->unlock();
- } });
-
- DWORD ignoreExitCode;
- waitForProcessCompletion(processHandle, exitCode ? exitCode : &ignoreExitCode, uilock);
- cycleDiagnostics();
- }
-
- return processHandle;
-}
-
-
-HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
- const QString &arguments,
- const QString &profileName,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries)
-{
- spawn::SpawnParameters sp;
- sp.binary = binary;
- sp.arguments = arguments;
- sp.currentDirectory = currentDirectory;
- sp.hooked = true;
-
- prepareStart();
-
- QWidget *window = qApp->activeWindow();
- if ((window != nullptr) && (!window->isVisible())) {
- window = nullptr;
- }
-
- if (!spawn::checkBinary(window, sp)) {
- return INVALID_HANDLE_VALUE;
- }
-
- if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) {
- return INVALID_HANDLE_VALUE;
- }
-
- while (m_DirectoryUpdate) {
- ::Sleep(100);
- QCoreApplication::processEvents();
- }
-
- // need to make sure all data is saved before we start the application
- if (m_CurrentProfile != nullptr) {
- m_CurrentProfile->writeModlistNow(true);
- }
-
- // TODO: should also pass arguments
- if (m_AboutToRun(binary.absoluteFilePath())) {
- try {
- m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
- m_USVFS.updateForcedLibraries(forcedLibraries);
-
- } catch (const UsvfsConnectorException &e) {
- log::debug(e.what());
- return INVALID_HANDLE_VALUE;
- } catch (const std::exception &e) {
- QMessageBox::warning(window, tr("Error"), e.what());
- return INVALID_HANDLE_VALUE;
- }
-
- if (!spawn::checkEnvironment(window, sp)) {
- return INVALID_HANDLE_VALUE;
- }
-
- if (!spawn::checkBlacklist(window, sp, m_Settings)) {
- return INVALID_HANDLE_VALUE;
- }
-
- QString modsPath = settings().paths().mods();
-
- // Check if this a request with either an executable or a working directory under our mods folder
- // then will start the process in a virtualized "environment" with the appropriate paths fixed:
- // (i.e. mods\FNIS\path\exe => game\data\path\exe)
- QString cwdPath = currentDirectory.absolutePath();
- bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive);
- QString binPath = binary.absoluteFilePath();
- bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive);
- if (virtualizedCwd || virtualizedBin) {
- if (virtualizedCwd) {
- int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1);
- QString adjustedCwd = cwdPath.mid(cwdOffset, -1);
- cwdPath = m_GamePlugin->dataDirectory().absolutePath();
- if (cwdOffset >= 0)
- cwdPath += adjustedCwd;
-
- }
-
- if (virtualizedBin) {
- int binOffset = binPath.indexOf('/', modsPath.length() + 1);
- QString adjustedBin = binPath.mid(binOffset, -1);
- binPath = m_GamePlugin->dataDirectory().absolutePath();
- if (binOffset >= 0)
- binPath += adjustedBin;
- }
-
- QString cmdline
- = QString("launch \"%1\" \"%2\" %3")
- .arg(QDir::toNativeSeparators(cwdPath),
- QDir::toNativeSeparators(binPath), arguments);
-
- sp.binary = QFileInfo(QCoreApplication::applicationFilePath());
- sp.arguments = cmdline;
- sp.currentDirectory.setPath(QCoreApplication::applicationDirPath());
-
- return spawn::startBinary(window, sp);
- } else {
- log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath);
- return spawn::startBinary(window, sp);
- }
- } else {
- log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
- return INVALID_HANDLE_VALUE;
- }
-}
-
-HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut)
-{
- if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance())
- throw std::runtime_error(
- QString("Refusing to run executable from different instance %1:%2")
- .arg(shortcut.instance(),shortcut.executable())
- .toLocal8Bit().constData());
-
- const Executable& exe = m_ExecutablesList.get(shortcut.executable());
-
- auto forcedLibaries = m_CurrentProfile->determineForcedLibraries(shortcut.executable());
- if (!m_CurrentProfile->forcedLibrariesEnabled(shortcut.executable())) {
- forcedLibaries.clear();
- }
-
- return spawnBinaryDirect(
- exe.binaryInfo(), exe.arguments(),
- m_CurrentProfile->name(),
- exe.workingDirectory().length() != 0
- ? exe.workingDirectory()
- : exe.binaryInfo().absolutePath(),
- exe.steamAppID(),
- "",
- forcedLibaries);
-}
-
-HANDLE OrganizerCore::startApplication(const QString &executable,
- const QStringList &args,
- const QString &cwd,
- const QString &profile,
- const QString &forcedCustomOverwrite,
- bool ignoreCustomOverwrite)
-{
- QFileInfo binary;
- QString arguments = args.join(" ");
- QString currentDirectory = cwd;
- QString profileName = profile;
- if (profile.length() == 0) {
- if (m_CurrentProfile != nullptr) {
- profileName = m_CurrentProfile->name();
- } else {
- throw MyException(tr("No profile set"));
- }
- }
- QString steamAppID;
- QString customOverwrite;
- QList<ExecutableForcedLoadSetting> forcedLibraries;
- if (executable.contains('\\') || executable.contains('/')) {
- // file path
-
- binary = QFileInfo(executable);
- if (binary.isRelative()) {
- // relative path, should be relative to game directory
- binary = QFileInfo(
- managedGame()->gameDirectory().absoluteFilePath(executable));
- }
- if (cwd.length() == 0) {
- currentDirectory = binary.absolutePath();
- }
- try {
- const Executable &exe = m_ExecutablesList.getByBinary(binary);
- steamAppID = exe.steamAppID();
- customOverwrite
- = m_CurrentProfile->setting("custom_overwrites", exe.title())
- .toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
- } catch (const std::runtime_error &) {
- // nop
- }
- } else {
- // only a file name, search executables list
- try {
- const Executable &exe = m_ExecutablesList.get(executable);
- steamAppID = exe.steamAppID();
- customOverwrite
- = m_CurrentProfile->setting("custom_overwrites", exe.title())
- .toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
- if (arguments == "") {
- arguments = exe.arguments();
- }
- binary = exe.binaryInfo();
- if (cwd.length() == 0) {
- currentDirectory = exe.workingDirectory();
- }
- } catch (const std::runtime_error &) {
- log::warn("\"{}\" not set up as executable", executable);
- binary = QFileInfo(executable);
- }
- }
-
- if (!forcedCustomOverwrite.isEmpty())
- customOverwrite = forcedCustomOverwrite;
- if (ignoreCustomOverwrite)
- customOverwrite.clear();
-
- return spawnBinaryDirect(binary,
- arguments,
- profileName,
- currentDirectory,
- steamAppID,
- customOverwrite,
- forcedLibraries);
-}
-
-bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
-{
- if (!Settings::instance().interface().lockGUI())
- return true;
-
- ILockedWaitingForProcess* uilock = nullptr;
- if (m_UserInterface != nullptr) {
- uilock = m_UserInterface->lock();
- }
-
- ON_BLOCK_EXIT([&] () {
- if (m_UserInterface != nullptr) {
- m_UserInterface->unlock();
- } });
- return waitForProcessCompletion(handle, exitCode, uilock);
-}
-
-bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
-{
- bool originalHandle = true;
- 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<QString> 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) {
- processName += QString(" (%1)").arg(currentPID);
- if (uilock)
- uilock->setProcessName(processName);
-
- log::debug(
- "Waiting for {} process completion: {}",
- (originalHandle ? "spawned" : "usvfs"), processName);
-
- newHandle = false;
- }
-
- // Wait for a an event on the handle, a key press, mouse click or timeout
- res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON);
- if (res == WAIT_FAILED) {
- log::warn("Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED {}", GetLastError());
- break;
- }
-
- // keep processing events so the app doesn't appear dead
- QCoreApplication::sendPostedEvents();
- QCoreApplication::processEvents();
-
- if (uilock && uilock->unlockForced()) {
- uiunlocked = true;
- break;
- }
-
- if (res == WAIT_OBJECT_0) {
- // process we were waiting on has completed
- if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode))
- log::warn("Failed getting exit code of complete process: {}", GetLastError());
- 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
- 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
- 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;
- }
- }
- }
-
- if (res == WAIT_OBJECT_0)
- log::debug("Waiting for process completion successfull");
- else if (uiunlocked)
- log::debug("Waiting for process completion aborted by UI");
- else
- log::debug("Waiting for process completion not successfull: {}", res);
-
- if (handle != INVALID_HANDLE_VALUE)
- ::CloseHandle(handle);
-
- return res == WAIT_OBJECT_0;
-}
-
-HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector<QString>& 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)) {
- log::warn("Failed seeking USVFS processes : GetVFSProcessList failed?!");
- 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) {
- log::warn("Failed opening USVFS process {}: OpenProcess failed {}", pids[i], 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 best_match;
-}
-
bool OrganizerCore::onAboutToRun(
const std::function<bool(const QString &)> &func)
{
@@ -2315,11 +1710,12 @@ void OrganizerCore::savePluginList()
m_PluginList.saveLoadOrder(*m_DirectoryStructure);
}
-void OrganizerCore::prepareStart()
+void OrganizerCore::saveCurrentProfile()
{
if (m_CurrentProfile == nullptr) {
return;
}
+
m_CurrentProfile->writeModlist();
m_CurrentProfile->createTweakedIniFile();
saveCurrentLists();
@@ -2327,6 +1723,85 @@ void OrganizerCore::prepareStart()
storeSettings();
}
+ProcessRunner OrganizerCore::processRunner()
+{
+ return ProcessRunner(*this, m_UserInterface);
+}
+
+bool OrganizerCore::beforeRun(
+ const QFileInfo& binary, const QString& profileName,
+ const QString& customOverwrite,
+ const QList<MOBase::ExecutableForcedLoadSetting>& forcedLibraries)
+{
+ saveCurrentProfile();
+
+ while (m_DirectoryUpdate) {
+ ::Sleep(100);
+ QCoreApplication::processEvents();
+ }
+
+ // need to make sure all data is saved before we start the application
+ if (m_CurrentProfile != nullptr) {
+ m_CurrentProfile->writeModlistNow(true);
+ }
+
+ // TODO: should also pass arguments
+ if (!m_AboutToRun(binary.absoluteFilePath())) {
+ log::debug("start of \"{}\" cancelled by plugin", binary.absoluteFilePath());
+ return false;
+ }
+
+ try
+ {
+ m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
+ m_USVFS.updateForcedLibraries(forcedLibraries);
+ }
+ catch (const UsvfsConnectorException &e)
+ {
+ log::debug(e.what());
+ return false;
+ }
+ catch (const std::exception &e)
+ {
+ QWidget* w = nullptr;
+ if (m_UserInterface) {
+ w = m_UserInterface->qtWidget();
+ }
+ QMessageBox::warning(w, tr("Error"), e.what());
+ return false;
+ }
+
+ return true;
+}
+
+void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode)
+{
+ refreshDirectoryStructure();
+
+ // need to remove our stored load order because it may be outdated if a
+ // foreign tool changed the file time. After removing that file,
+ // refreshESPList will use the file time as the order
+ if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
+ log::debug("removing loadorder.txt");
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+
+ refreshDirectoryStructure();
+
+ refreshESPList(true);
+ savePluginList();
+ cycleDiagnostics();
+
+ //These callbacks should not fiddle with directoy structure and ESPs.
+ m_FinishedRun(binary.absoluteFilePath(), exitCode);
+}
+
+ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses(
+ UILocker::Reasons reason)
+{
+ return processRunner().waitForAllUSVFSProcessesWithLock(reason);
+}
+
std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
const QString &customOverwrite)
{
diff --git a/src/organizercore.h b/src/organizercore.h
index 0d0a092c..6c9edb9f 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -3,7 +3,6 @@
#include "selfupdater.h"
-#include "iuserinterface.h" //should be class IUserInterface;
#include "settings.h"
#include "modlist.h"
#include "modinfo.h"
@@ -14,6 +13,8 @@
#include "executableslist.h"
#include "usvfsconnector.h"
#include "moshortcut.h"
+#include "processrunner.h"
+#include "uilocker.h"
#include <directoryentry.h>
#include <imoinfo.h>
#include <iplugindiagnose.h>
@@ -26,6 +27,8 @@
class ModListSortProxy;
class PluginListSortProxy;
class Profile;
+class IUserInterface;
+
namespace MOBase {
template <typename T> class GuessedValue;
class IModInterface;
@@ -89,19 +92,13 @@ private:
typedef boost::signals2::signal<void (const QString&)> SignalModInstalled;
public:
- enum class FileExecutionTypes
- {
- Executable = 1,
- Other = 2
- };
-
static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); }
OrganizerCore(Settings &settings);
~OrganizerCore();
- void setUserInterface(IUserInterface *userInterface, QWidget *widget);
+ void setUserInterface(IUserInterface* ui);
void connectPlugins(PluginContainer *container);
void disconnectPlugins();
@@ -138,7 +135,17 @@ public:
bool saveCurrentLists();
- void prepareStart();
+ ProcessRunner processRunner();
+
+ bool beforeRun(
+ const QFileInfo& binary, const QString& profileName,
+ const QString& customOverwrite,
+ const QList<MOBase::ExecutableForcedLoadSetting>& forcedLibraries);
+
+ void afterRun(const QFileInfo& binary, DWORD exitCode);
+
+ ProcessRunner::Results waitForAllUSVFSProcesses(
+ UILocker::Reasons reason=UILocker::PreventExit);
void refreshESPList(bool force = false);
void refreshBSAList();
@@ -150,37 +157,9 @@ public:
void doAfterLogin(const std::function<void()> &function) { m_PostLoginTasks.append(function); }
void loggedInAction(QWidget* parent, std::function<void ()> f);
- static QString findJavaInstallation(const QString& jarFile={});
-
- static bool getFileExecutionContext(
- QWidget* parent, const QFileInfo &targetInfo,
- QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type);
-
- bool executeFileVirtualized(QWidget* parent, const QFileInfo& targetInfo);
bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1);
bool previewFile(QWidget* parent, const QString& originName, const QString& path);
- void spawnBinary(const QFileInfo &binary, const QString &arguments = "",
- const QDir &currentDirectory = QDir(),
- const QString &steamAppID = "",
- const QString &customOverwrite = "",
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries = QList<MOBase::ExecutableForcedLoadSetting>());
-
- HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments,
- const QString &profileName,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries = QList<MOBase::ExecutableForcedLoadSetting>(),
- LPDWORD exitCode = nullptr);
-
- HANDLE spawnBinaryProcess(const QFileInfo &binary, const QString &arguments,
- const QString &profileName,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries = QList<MOBase::ExecutableForcedLoadSetting>());
-
void loginSuccessfulUpdate(bool necessary);
void loginFailedUpdate(const QString &message);
@@ -234,10 +213,6 @@ public:
DownloadManager *downloadManager();
PluginList *pluginList();
ModList *modList();
- HANDLE runShortcut(const MOShortcut& shortcut);
- HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false);
- bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr);
- HANDLE findAndOpenAUSVFSProcess(const std::vector<QString>& hiddenList, DWORD preferedParentPid);
bool onModInstalled(const std::function<void (const QString &)> &func);
bool onAboutToRun(const std::function<bool (const QString &)> &func);
bool onFinishedRun(const std::function<void (const QString &, unsigned int)> &func);
@@ -288,6 +263,7 @@ signals:
private:
+ void saveCurrentProfile();
void storeSettings();
bool queryApi(QString &apiKey);
@@ -313,8 +289,6 @@ private:
const MOShared::DirectoryEntry *directoryEntry,
int createDestination);
- bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock);
-
private slots:
void directory_refreshed();
@@ -328,8 +302,7 @@ private:
static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1;
private:
-
- IUserInterface *m_UserInterface;
+ IUserInterface* m_UserInterface;
PluginContainer *m_PluginContainer;
QString m_GameName;
MOBase::IPluginGame *m_GamePlugin;
@@ -371,6 +344,8 @@ private:
MOBase::DelayedFileWriter m_PluginListsWriter;
UsvfsConnector m_USVFS;
+ UILocker m_UILocker;
+
static CrashDumpsType m_globalCrashDumpsType;
};
diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp
index d6996b3a..613de742 100644
--- a/src/organizerproxy.cpp
+++ b/src/organizerproxy.cpp
@@ -107,15 +107,65 @@ QString OrganizerProxy::pluginDataPath() const
return m_Proxied->pluginDataPath();
}
-HANDLE OrganizerProxy::startApplication(const QString &executable, const QStringList &args, const QString &cwd,
- const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite)
+HANDLE OrganizerProxy::startApplication(
+ const QString& exe, const QStringList& args, const QString &cwd,
+ const QString& profile, const QString &overwrite, bool ignoreOverwrite)
{
- return m_Proxied->startApplication(executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite);
+ log::debug(
+ "a plugin has requested to start an application:\n"
+ " . executable: '{}'\n"
+ " . args: '{}'\n"
+ " . cwd: '{}'\n"
+ " . profile: '{}'\n"
+ " . overwrite: '{}'\n"
+ " . ignore overwrite: {}",
+ exe, args.join(" "), cwd, profile, overwrite, ignoreOverwrite);
+
+ auto runner = m_Proxied->processRunner();
+
+ // don't wait for completion
+ runner
+ .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite)
+ .run();
+
+ // the plugin is in charge of closing the handle, unless waitForApplication()
+ // is called on it
+ return runner.stealProcessHandle().release();
}
bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const
{
- return m_Proxied->waitForApplication(handle, exitCode);
+ const auto pid = ::GetProcessId(handle);
+
+ log::debug(
+ "a plugin wants to wait for an application to complete, pid {}{}",
+ pid, (pid == 0 ? "unknown (probably already completed)" : ""));
+
+ auto runner = m_Proxied->processRunner();
+
+ const auto r = runner
+ .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::OutputRequired)
+ .attachToProcess(handle);
+
+ if (exitCode) {
+ *exitCode = runner.exitCode();
+ }
+
+ switch (r)
+ {
+ case ProcessRunner::Completed:
+ return true;
+
+ case ProcessRunner::Cancelled: // fall-through
+ case ProcessRunner::ForceUnlocked:
+ // this is always an error because the application should have run to
+ // completion
+ return false;
+
+ case ProcessRunner::Error: // fall-through
+ default:
+ return false;
+ }
}
bool OrganizerProxy::onAboutToRun(const std::function<bool (const QString &)> &func)
diff --git a/src/pch.h b/src/pch.h
index 01a97357..b7c5d695 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -100,9 +100,9 @@
#include <QDropEvent>
#include <QElapsedTimer>
#include <QEvent>
+#include <QFIleIconProvider>
#include <QFile>
#include <QFileDialog>
-#include <QFIleIconProvider>
#include <QFileInfo>
#include <QFileSystemModel>
#include <QFileSystemWatcher>
@@ -111,13 +111,14 @@
#include <QFontDatabase>
#include <QFuture>
#include <QFutureWatcher>
-#include <QHash>
+#include <QGraphicsDropShadowEffect>
#include <QHBoxLayout>
+#include <QHash>
#include <QHeaderView>
+#include <QIODevice>
#include <QIcon>
#include <QImageReader>
#include <QInputDialog>
-#include <QIODevice>
#include <QItemDelegate>
#include <QItemSelection>
#include <QItemSelectionModel>
@@ -126,20 +127,21 @@
#include <QJsonObject>
#include <QJsonValueRef>
#include <QKeyEvent>
-#include <QLabel>
#include <QLCDNumber>
+#include <QLabel>
#include <QLibrary>
#include <QLibraryInfo>
#include <QLineEdit>
#include <QList>
#include <QListWidget>
#include <QListWidgetItem>
-#include <QLocale>
#include <QLocalServer>
#include <QLocalSocket>
+#include <QLocale>
#include <QMainWindow>
#include <QMap>
#include <QMenu>
+#include <QMenuBar>
#include <QMessageBox>
#include <QMetaType>
#include <QMimeData>
@@ -190,53 +192,42 @@
#include <QSize>
#include <QSizePolicy>
#include <QSortFilterProxyModel>
-#include <QSplitter>
#include <QSpinBox>
#include <QSplashScreen>
+#include <QSplitter>
#include <QSslSocket>
#include <QStandardItemModel>
-#include <qstandardpaths.h>
#include <QStandardPaths>
+#include <QStatusBar>
+#include <QStorageInfo>
#include <QString>
#include <QStringList>
#include <QStringListModel>
#include <QStyle>
-#include <QStyledItemDelegate>
#include <QStyleFactory>
#include <QStyleOption>
#include <QStyleOptionSlider>
+#include <QStyledItemDelegate>
#include <QSyntaxHighlighter>
-#include <Qt>
-#include <QTableWidget>
#include <QTabWidget>
-#include <QtAlgorithms>
-#include <QtConcurrent/QtConcurrentRun>
-#include <QtCore/QAbstractItemModel>
-#include <QtCore/QObject>
-#include <QtCore/QStack>
-#include <QtDebug>
+#include <QTableWidget>
#include <QTemporaryFile>
#include <QTextBrowser>
#include <QTextCodec>
#include <QTextDocument>
#include <QTextEdit>
#include <QTextStream>
-#include <QtGlobal>
-#include <QtGui/QtGui>
#include <QThread>
#include <QTime>
#include <QTimer>
#include <QToolBar>
#include <QToolButton>
#include <QToolTip>
-#include <QtPlatformHeaders/QWindowsWindowFunctions>
-#include <QtPlugin>
#include <QTranslator>
#include <QTreeView>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QTreeWidgetItemIterator>
-#include <QtTest/QtTest>
#include <QUrl>
#include <QUrlQuery>
#include <QVariant>
@@ -255,4 +246,16 @@
#include <QWhatsThisClickedEvent>
#include <QWidget>
#include <QWidgetAction>
-#include <QStorageInfo>
+#include <Qt>
+#include <QtAlgorithms>
+#include <QtConcurrent/QtConcurrentRun>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QObject>
+#include <QtCore/QStack>
+#include <QtDebug>
+#include <QtGlobal>
+#include <QtGui/QtGui>
+#include <QtPlatformHeaders/QWindowsWindowFunctions>
+#include <QtPlugin>
+#include <QtTest/QtTest>
+#include <QStandardPaths>
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp
index c0706ba8..767d3eb8 100644
--- a/src/plugincontainer.cpp
+++ b/src/plugincontainer.cpp
@@ -3,6 +3,7 @@
#include "organizerproxy.h"
#include "report.h"
#include <ipluginproxy.h>
+#include <iuserinterface.h>
#include <idownloadmanager.h>
#include <appconfig.h>
#include <QAction>
diff --git a/src/processrunner.cpp b/src/processrunner.cpp
new file mode 100644
index 00000000..cfddbb63
--- /dev/null
+++ b/src/processrunner.cpp
@@ -0,0 +1,791 @@
+#include "processrunner.h"
+#include "organizercore.h"
+#include "instancemanager.h"
+#include "iuserinterface.h"
+#include "envmodule.h"
+#include <log.h>
+
+using namespace MOBase;
+
+void adjustForVirtualized(
+ const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings)
+{
+ const QString modsPath = settings.paths().mods();
+
+ // Check if this a request with either an executable or a working directory
+ // under our mods folder then will start the process in a virtualized
+ // "environment" with the appropriate paths fixed:
+ // (i.e. mods\FNIS\path\exe => game\data\path\exe)
+ QString cwdPath = sp.currentDirectory.absolutePath();
+ bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive);
+ QString binPath = sp.binary.absoluteFilePath();
+ bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive);
+ if (virtualizedCwd || virtualizedBin) {
+ if (virtualizedCwd) {
+ int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1);
+ QString adjustedCwd = cwdPath.mid(cwdOffset, -1);
+ cwdPath = game->dataDirectory().absolutePath();
+ if (cwdOffset >= 0)
+ cwdPath += adjustedCwd;
+
+ }
+
+ if (virtualizedBin) {
+ int binOffset = binPath.indexOf('/', modsPath.length() + 1);
+ QString adjustedBin = binPath.mid(binOffset, -1);
+ binPath = game->dataDirectory().absolutePath();
+ if (binOffset >= 0)
+ binPath += adjustedBin;
+ }
+
+ QString cmdline
+ = QString("launch \"%1\" \"%2\" %3")
+ .arg(QDir::toNativeSeparators(cwdPath),
+ QDir::toNativeSeparators(binPath), sp.arguments);
+
+ sp.binary = QFileInfo(QCoreApplication::applicationFilePath());
+ sp.arguments = cmdline;
+ sp.currentDirectory.setPath(QCoreApplication::applicationDirPath());
+ }
+}
+
+std::optional<ProcessRunner::Results> singleWait(HANDLE handle, DWORD pid)
+{
+ if (handle == INVALID_HANDLE_VALUE) {
+ return ProcessRunner::Error;
+ }
+
+ const auto res = WaitForSingleObject(handle, 50);
+
+ switch (res)
+ {
+ case WAIT_OBJECT_0:
+ {
+ log::debug("process {} completed", pid);
+ return ProcessRunner::Completed;
+ }
+
+ case WAIT_TIMEOUT:
+ {
+ // still running
+ return {};
+ }
+
+ case WAIT_FAILED: // fall-through
+ default:
+ {
+ // error
+ const auto e = ::GetLastError();
+ log::error("failed waiting for {}, {}", pid, formatSystemMessage(e));
+ return ProcessRunner::Error;
+ }
+ }
+}
+
+enum class Interest
+{
+ None = 0,
+ Weak,
+ Strong
+};
+
+QString toString(Interest i)
+{
+ switch (i)
+ {
+ case Interest::Weak:
+ return "weak";
+
+ case Interest::Strong:
+ return "strong";
+
+ case Interest::None: // fall-through
+ default:
+ return "no";
+ }
+}
+
+
+// returns a process that's in the hidden list, or the top-level process if
+// they're all hidden; returns an invalid process if the list is empty
+//
+std::pair<env::Process, Interest> findInterestingProcessInTrees(
+ std::vector<env::Process>& processes)
+{
+ if (processes.empty()) {
+ return {{}, Interest::None};
+ }
+
+ // 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, Interest::Strong};
+ }
+
+ for (auto&& child : root.children()) {
+ if (!isHidden(child)) {
+ return {child, Interest::Strong};
+ }
+ }
+ }
+
+ // everything is hidden, just pick the first one
+ return {processes[0], Interest::Weak};
+}
+
+// gets the most interesting process in the list
+//
+std::pair<env::Process, Interest> getInterestingProcess(
+ const std::vector<HANDLE>& initialProcesses)
+{
+ if (initialProcesses.empty()) {
+ log::debug("nothing to wait for");
+ return {{}, Interest::None};
+ }
+
+ std::vector<env::Process> processes;
+
+ // getting process trees for all processes
+ for (auto&& h : initialProcesses) {
+ auto tree = env::getProcessTree(h);
+ if (tree.isValid()) {
+ processes.push_back(tree);
+ }
+ }
+
+ if (processes.empty()) {
+ // if the initial list wasn't empty but this one is, it means all the
+ // processes were already completed
+ log::debug("processes are already completed");
+ return {{}, Interest::None};
+ }
+
+ const auto interest = findInterestingProcessInTrees(processes);
+ if (!interest.first.isValid()) {
+ // this shouldn't happen
+ log::debug("no interesting process to wait for");
+ return {{}, Interest::None};
+ }
+
+ return interest;
+}
+
+const std::chrono::milliseconds Infinite(-1);
+
+// waits for completion, times out after `wait` if not Infinite
+//
+std::optional<ProcessRunner::Results> timedWait(
+ HANDLE handle, DWORD pid, UILocker::Session& ls,
+ std::chrono::milliseconds wait)
+{
+ using namespace std::chrono;
+
+ high_resolution_clock::time_point start;
+ if (wait != Infinite) {
+ start = high_resolution_clock::now();
+ }
+
+ for (;;) {
+ // wait for a very short while, allows for processing events below
+ const auto r = singleWait(handle, pid);
+
+ if (r) {
+ // the process has either completed or an error was returned
+ return *r;
+ }
+
+ // the process is still running
+
+ // check the lock widget
+ switch (ls.result())
+ {
+ case UILocker::StillLocked:
+ {
+ break;
+ }
+
+ case UILocker::ForceUnlocked:
+ {
+ log::debug("waiting for {} force unlocked by user", pid);
+ return ProcessRunner::ForceUnlocked;
+ }
+
+ case UILocker::Cancelled:
+ {
+ log::debug("waiting for {} cancelled by user", pid);
+ return ProcessRunner::Cancelled;
+ }
+
+ case UILocker::NoResult: // fall-through
+ default:
+ {
+ // shouldn't happen
+ log::debug(
+ "unexpected result {} while waiting for {}",
+ static_cast<int>(ls.result()), pid);
+
+ return ProcessRunner::Error;
+ }
+ }
+
+ if (wait != Infinite) {
+ // check if enough time has elapsed
+ const auto now = high_resolution_clock::now();
+ if (duration_cast<milliseconds>(now - start) >= wait) {
+ // if so, return an empty result
+ return {};
+ }
+ }
+ }
+}
+
+ProcessRunner::Results waitForProcessesThreadImpl(
+ const std::vector<HANDLE>& initialProcesses, UILocker::Session& ls)
+{
+ using namespace std::chrono;
+
+ if (initialProcesses.empty()) {
+ // shouldn't happen
+ return ProcessRunner::Completed;
+ }
+
+ DWORD currentPID = 0;
+
+ // if the interesting process that was found is weak (such as ModOrganizer.exe
+ // when starting a program from within the Data directory), start with a short
+ // wait and check for more interesting children
+ milliseconds wait(50);
+
+ for (;;) {
+ auto [p, interest] = getInterestingProcess(initialProcesses);
+ if (!p.isValid()) {
+ // nothing to wait on
+ return ProcessRunner::Completed;
+ }
+
+ // update the lock widget
+ ls.setInfo(p.pid(), p.name());
+
+ // open the process
+ auto interestingHandle = p.openHandleForWait();
+ if (!interestingHandle) {
+ return ProcessRunner::Error;
+ }
+
+ if (p.pid() != currentPID) {
+ // log any change in the process being waited for
+ currentPID = p.pid();
+
+ log::debug(
+ "waiting for completion on {} ({}), {} interest",
+ p.name(), p.pid(), toString(interest));
+ }
+
+ if (interest == Interest::Strong) {
+ // don't bother with short wait, this is a good process to wait for
+ wait = Infinite;
+ }
+
+ const auto r = timedWait(interestingHandle.get(), p.pid(), ls, wait);
+ if (r) {
+ // the process has completed or returned an error
+ return *r;
+ }
+
+ // exponentially increase the wait time between checks for interesting
+ // processes
+ wait = std::min(wait * 2, milliseconds(2000));
+ }
+}
+
+void waitForProcessesThread(
+ ProcessRunner::Results& result,
+ const std::vector<HANDLE>& initialProcesses, UILocker::Session& ls)
+{
+ result = waitForProcessesThreadImpl(initialProcesses, ls);
+ ls.unlock();
+}
+
+ProcessRunner::Results waitForProcesses(
+ const std::vector<HANDLE>& initialProcesses, UILocker::Session& ls)
+{
+ auto results = ProcessRunner::Running;
+
+ auto* t = QThread::create(
+ waitForProcessesThread, std::ref(results), initialProcesses, std::ref(ls));
+
+ QEventLoop events;
+ QObject::connect(t, &QThread::finished, [&]{
+ events.quit();
+ });
+
+ t->start();
+ events.exec();
+
+ delete t;
+
+ return results;
+}
+
+ProcessRunner::Results waitForProcess(
+ HANDLE initialProcess, LPDWORD exitCode, UILocker::Session& ls)
+{
+ std::vector<HANDLE> processes = {initialProcess};
+
+ const auto r = waitForProcesses(processes, ls);
+
+ // as long as it's not running anymore, try to get the exit code
+ if (exitCode && r != ProcessRunner::Running) {
+ if (!::GetExitCodeProcess(initialProcess, exitCode)) {
+ const auto e = ::GetLastError();
+ log::warn(
+ "failed to get exit code of process, {}",
+ formatSystemMessage(e));
+ }
+ }
+
+ return r;
+}
+
+
+ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) :
+ m_core(core), m_ui(ui), m_lockReason(UILocker::NoReason),
+ m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1)
+{
+ // all processes started in ProcessRunner are hooked
+ m_sp.hooked = true;
+}
+
+ProcessRunner& ProcessRunner::setBinary(const QFileInfo &binary)
+{
+ m_sp.binary = binary;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setArguments(const QString& arguments)
+{
+ m_sp.arguments = arguments;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setCurrentDirectory(const QDir& directory)
+{
+ m_sp.currentDirectory = directory;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setSteamID(const QString& steamID)
+{
+ m_sp.steamAppID = steamID;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setCustomOverwrite(const QString& customOverwrite)
+{
+ m_customOverwrite = customOverwrite;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setForcedLibraries(const ForcedLibraries& forcedLibraries)
+{
+ m_forcedLibraries = forcedLibraries;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setProfileName(const QString& profileName)
+{
+ m_profileName = profileName;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setWaitForCompletion(
+ WaitFlags flags, UILocker::Reasons reason)
+{
+ m_waitFlags = flags;
+ m_lockReason = reason;
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targetInfo)
+{
+ if (!parent && m_ui) {
+ parent = m_ui->qtWidget();
+ }
+
+ // if the file is a .exe, start it directory; if it's anything else, ask the
+ // shell to start it
+
+ const auto fec = spawn::getFileExecutionContext(parent, targetInfo);
+
+ switch (fec.type)
+ {
+ case spawn::FileExecutionTypes::Executable:
+ {
+ setBinary(fec.binary);
+ setArguments(fec.arguments);
+ setCurrentDirectory(targetInfo.absoluteDir());
+ break;
+ }
+
+ case spawn::FileExecutionTypes::Other: // fall-through
+ default:
+ {
+ m_shellOpen = targetInfo.absoluteFilePath();
+ break;
+ }
+ }
+
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe)
+{
+ const auto* profile = m_core.currentProfile();
+ if (!profile) {
+ throw MyException(QObject::tr("No profile set"));
+ }
+
+ const QString customOverwrite = profile->setting(
+ "custom_overwrites", exe.title()).toString();
+
+ ForcedLibraries forcedLibraries;
+ if (profile->forcedLibrariesEnabled(exe.title())) {
+ forcedLibraries = profile->determineForcedLibraries(exe.title());
+ }
+
+ QDir currentDirectory = exe.workingDirectory();
+ if (currentDirectory.isEmpty()) {
+ currentDirectory.setPath(exe.binaryInfo().absolutePath());
+ }
+
+ setBinary(exe.binaryInfo());
+ setArguments(exe.arguments());
+ setCurrentDirectory(currentDirectory);
+ setSteamID(exe.steamAppID());
+ setCustomOverwrite(customOverwrite);
+ setForcedLibraries(forcedLibraries);
+
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut)
+{
+ const auto currentInstance = InstanceManager::instance().currentInstance();
+
+ if (shortcut.hasInstance() && shortcut.instance() != currentInstance) {
+ throw std::runtime_error(
+ QString("Refusing to run executable from different instance %1:%2")
+ .arg(shortcut.instance(),shortcut.executable())
+ .toLocal8Bit().constData());
+ }
+
+ const Executable& exe = m_core.executablesList()->get(shortcut.executable());
+ setFromExecutable(exe);
+
+ return *this;
+}
+
+ProcessRunner& ProcessRunner::setFromFileOrExecutable(
+ const QString &executable,
+ const QStringList &args,
+ const QString &cwd,
+ const QString &profileOverride,
+ const QString &forcedCustomOverwrite,
+ bool ignoreCustomOverwrite)
+{
+ const auto* profile = m_core.currentProfile();
+ if (!profile) {
+ throw MyException(QObject::tr("No profile set"));
+ }
+
+ setBinary(executable);
+ setArguments(args.join(" "));
+ setCurrentDirectory(cwd);
+ setProfileName(profileOverride);
+
+ if (executable.contains('\\') || executable.contains('/')) {
+ // file path
+
+ if (m_sp.binary.isRelative()) {
+ // relative path, should be relative to game directory
+ setBinary(m_core.managedGame()->gameDirectory().absoluteFilePath(executable));
+ }
+
+ if (cwd == "") {
+ setCurrentDirectory(m_sp.binary.absolutePath());
+ }
+
+ try {
+ const Executable& exe = m_core.executablesList()->getByBinary(m_sp.binary);
+
+ setSteamID(exe.steamAppID());
+ setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString());
+
+ if (profile->forcedLibrariesEnabled(exe.title())) {
+ setForcedLibraries(profile->determineForcedLibraries(exe.title()));
+ }
+ } catch (const std::runtime_error &) {
+ // nop
+ }
+ } else {
+ // only a file name, search executables list
+ try {
+ const Executable &exe = m_core.executablesList()->get(executable);
+
+ setSteamID(exe.steamAppID());
+ setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString());
+
+ if (profile->forcedLibrariesEnabled(exe.title())) {
+ setForcedLibraries(profile->determineForcedLibraries(exe.title()));
+ }
+
+ if (args.isEmpty()) {
+ setArguments(exe.arguments());
+ }
+
+ setBinary(exe.binaryInfo());
+
+ if (cwd == "") {
+ setCurrentDirectory(exe.workingDirectory());
+ }
+ } catch (const std::runtime_error &) {
+ log::warn("\"{}\" not set up as executable", executable);
+ }
+ }
+
+ if (ignoreCustomOverwrite) {
+ setCustomOverwrite("");
+ } else if (!forcedCustomOverwrite.isEmpty()) {
+ setCustomOverwrite(forcedCustomOverwrite);
+ }
+
+ return *this;
+}
+
+ProcessRunner::Results ProcessRunner::run()
+{
+ std::optional<Results> r;
+
+ if (!m_shellOpen.isEmpty()) {
+ r = runShell();
+ } else {
+ r = runBinary();
+ }
+
+ if (r) {
+ // early result: something went wrong and the process cannot be waited for
+ return *r;
+ }
+
+ return postRun();
+}
+
+std::optional<ProcessRunner::Results> ProcessRunner::runShell()
+{
+ log::debug("executing from shell: '{}'", m_shellOpen);
+
+ auto r = shell::Open(m_shellOpen);
+ if (!r.success()) {
+ return Error;
+ }
+
+ m_handle.reset(r.stealProcessHandle());
+
+ // not all files will return a valid handle even if opening them was
+ // successful, such as inproc handlers (like the photo viewer); in this
+ // case it's impossible to determine the status, so just say it's still
+ // running
+ if (m_handle.get() == INVALID_HANDLE_VALUE) {
+ log::debug("shell didn't report an error, but no handle is available");
+ return Running;
+ }
+
+ return {};
+}
+
+std::optional<ProcessRunner::Results> ProcessRunner::runBinary()
+{
+ if (m_profileName.isEmpty()) {
+ // get the current profile name if it wasn't overridden
+ const auto* profile = m_core.currentProfile();
+ if (!profile) {
+ throw MyException(QObject::tr("No profile set"));
+ }
+
+ m_profileName = profile->name();
+ }
+
+ // saves profile, sets up usvfs, notifies plugins, etc.; can return false if
+ // a plugin doesn't want the program to run (such as when checkFNIS fails to
+ // run FNIS and the user clicks cancel)
+ if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) {
+ return Error;
+ }
+
+ // parent widget used for any dialog popped up while checking for things
+ QWidget* parent = (m_ui ? m_ui->qtWidget() : nullptr);
+
+ const auto* game = m_core.managedGame();
+ auto& settings = m_core.settings();
+
+ // start steam if needed
+ if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) {
+ return Error;
+ }
+
+ // warn if the executable is on the blacklist
+ if (!checkBlacklist(parent, m_sp, settings)) {
+ return Error;
+ }
+
+ // if the executable is inside the mods folder another instance of
+ // ModOrganizer.exe is spawned instead to launch it
+ adjustForVirtualized(game, m_sp, settings);
+
+ // run the binary
+ m_handle.reset(startBinary(parent, m_sp));
+ if (m_handle.get() == INVALID_HANDLE_VALUE) {
+ return Error;
+ }
+
+ return {};
+}
+
+ProcessRunner::Results ProcessRunner::postRun()
+{
+ const bool mustWait = (m_waitFlags & ForceWait);
+
+ if (mustWait && m_lockReason == UILocker::NoReason) {
+ // never lock the ui without an escape hatch for the user
+ log::debug(
+ "the ForceWait flag is set but the lock reason wasn't, "
+ "defaulting to LockUI");
+
+ m_lockReason = UILocker::LockUI;
+ }
+
+ if (mustWait) {
+ if (!m_core.settings().interface().lockGUI()) {
+ // at least tell the user what's going on
+ log::debug(
+ "locking is disabled, but the output of the application is required; "
+ "overriding this setting and locking the ui");
+ }
+ } else {
+ // no force wait
+
+ if (m_lockReason == UILocker::NoReason) {
+ // no locking requested
+ return Running;
+ }
+
+ if (!m_core.settings().interface().lockGUI()) {
+ // disabling locking is like clicking on unlock immediately
+ log::debug("not waiting for process because locking is disabled");
+ return ForceUnlocked;
+ }
+ }
+
+ auto r = Error;
+
+ withLock([&](auto& ls) {
+ r = waitForProcess(m_handle.get(), &m_exitCode, ls);
+ });
+
+ if (r == Completed && (m_waitFlags & Refresh)) {
+ // afterRun() is only called with the Refresh flag; it refreshes the
+ // directory structure and notifies plugins
+ //
+ // refreshing is not always required and can actually cause problems:
+ //
+ // 1) running shortcuts doesn't need refreshing because MO closes right
+ // after
+ //
+ // 2) the mod info dialog is not set up to deal with refreshes, so that
+ // it will crash because the old DirectoryEntry's are still being used
+ // in the list
+ //
+ m_core.afterRun(m_sp.binary, m_exitCode);
+ }
+
+ return r;
+}
+
+ProcessRunner::Results ProcessRunner::attachToProcess(HANDLE h)
+{
+ m_handle.reset(h);
+ return postRun();
+}
+
+DWORD ProcessRunner::exitCode() const
+{
+ return m_exitCode;
+}
+
+HANDLE ProcessRunner::getProcessHandle() const
+{
+ return m_handle.get();
+}
+
+env::HandlePtr ProcessRunner::stealProcessHandle()
+{
+ auto h = m_handle.release();
+ m_handle.reset(INVALID_HANDLE_VALUE);
+ return env::HandlePtr(h);
+}
+
+ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock(
+ UILocker::Reasons reason)
+{
+ m_lockReason = reason;
+
+ if (!m_core.settings().interface().lockGUI()) {
+ // disabling locking is like clicking on unlock immediately
+ return ForceUnlocked;
+ }
+
+ auto r = Error;
+
+ withLock([&](auto& ls) {
+ for (;;) {
+ const auto processes = getRunningUSVFSProcesses();
+ if (processes.empty()) {
+ break;
+ }
+
+ r = waitForProcesses(processes, ls);
+
+ if (r != Completed) {
+ // error, cancelled, or unlocked
+ return;
+ }
+
+ // this process is completed, check for others
+ }
+
+ r = Completed;
+ });
+
+ return r;
+}
+
+void ProcessRunner::withLock(std::function<void (UILocker::Session&)> f)
+{
+ auto ls = UILocker::instance().lock(m_lockReason);
+ f(*ls);
+}
diff --git a/src/processrunner.h b/src/processrunner.h
new file mode 100644
index 00000000..c61d6b70
--- /dev/null
+++ b/src/processrunner.h
@@ -0,0 +1,172 @@
+#ifndef PROCESSRUNNER_H
+#define PROCESSRUNNER_H
+
+#include "spawn.h"
+#include "uilocker.h"
+#include "envmodule.h"
+#include <executableinfo.h>
+
+class OrganizerCore;
+class IUserInterface;
+class Executable;
+class MOShortcut;
+
+// handles spawning a process and waiting for it, including setting up the lock
+// widget if required
+//
+class ProcessRunner
+{
+public:
+ enum Results
+ {
+ // the process is still running
+ Running = 1,
+
+ // the process has run to completion
+ Completed,
+
+ // the process couldn't be started or waited for
+ Error,
+
+ // the user has clicked the cancel button in the lock widget
+ Cancelled,
+
+ // the user has clicked the unlock button in the lock widget
+ ForceUnlocked
+ };
+
+ enum WaitFlag
+ {
+ NoFlags = 0x00,
+
+ // the ui will be refreshed once the process has completed
+ Refresh = 0x01,
+
+ // the process will be waited for even if locking is disabled
+ ForceWait = 0x02
+ };
+
+ using WaitFlags = QFlags<WaitFlag>;
+ using ForcedLibraries = QList<MOBase::ExecutableForcedLoadSetting>;
+
+ ProcessRunner(OrganizerCore& core, IUserInterface* ui);
+
+ // move only
+ ProcessRunner(ProcessRunner&&) = default;
+ ProcessRunner& operator=(const ProcessRunner&) = delete;
+ ProcessRunner(const ProcessRunner&) = delete;
+ ProcessRunner& operator=(ProcessRunner&&) = delete;
+
+ ProcessRunner& setBinary(const QFileInfo &binary);
+ ProcessRunner& setArguments(const QString& arguments);
+ ProcessRunner& setCurrentDirectory(const QDir& directory);
+ ProcessRunner& setSteamID(const QString& steamID);
+ ProcessRunner& setCustomOverwrite(const QString& customOverwrite);
+ ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries);
+ ProcessRunner& setProfileName(const QString& profileName);
+ ProcessRunner& setWaitForCompletion(
+ WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI);
+
+ // if the target is an executable file, runs that; for anything else, calls
+ // ShellExecute() on it
+ //
+ ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo);
+
+ ProcessRunner& setFromExecutable(const Executable& exe);
+ ProcessRunner& setFromShortcut(const MOShortcut& shortcut);
+
+ // this is a messy one that's used for running an arbitrary file from the
+ // command line, or by plugins (see OrganizerProxy::startApplication())
+ //
+ // 1) if `executable` contains a path separator, it's treated as a binary on
+ // disk and will be launched with given settings; it's also looked up in
+ // the list of configured executables, which sets the steam ID and forced
+ // libraries
+ //
+ // 2) if `executable` has no path separators, it's treated purely as an
+ // executable, but its arguments, current directory and custom overwrite
+ // can also be overridden
+ //
+ // if the executable is not found in the list, the binary is run solely
+ // based on the parameters given
+ //
+ ProcessRunner& setFromFileOrExecutable(
+ const QString &executable,
+ const QStringList &args,
+ const QString &cwd={},
+ const QString &profile={},
+ const QString &forcedCustomOverwrite={},
+ bool ignoreCustomOverwrite=false);
+
+ // spawns the process and waits for it if required
+ //
+ Results run();
+
+ // takes ownership of the given handle and waits for it if required
+ //
+ Results attachToProcess(HANDLE h);
+
+ // exit code of the process, will return -1 if the process wasn't waited for
+ //
+ DWORD exitCode() const;
+
+ // this may be INVALID_HANDLE_VALUE if:
+ //
+ // 1) no process was started, or
+ // 2) the process was started successfully, but the system didn't return a
+ // handle for it; this can happen for inproc handlers, for example, such
+ // the photo viewer
+ //
+ // note that the handle is still owned by this ProcessRunner and will be
+ // closed when destroyed; see stealProcessHandle()
+ //
+ HANDLE getProcessHandle() const;
+
+ // releases ownership of the process handle; if this is called after the
+ // process is completed, exitCode() will still return the correct value
+ //
+ env::HandlePtr stealProcessHandle();
+
+ // waits for all usvfs processes spawned by this instance of MO; returns
+ // immediately with ForceUnlocked if locking is disabled
+ //
+ // strictly speaking, this shouldn't be here, as it has nothing to do with
+ // running a process, but it uses the same internal stuff as when running a
+ // process
+ //
+ Results waitForAllUSVFSProcessesWithLock(UILocker::Reasons reason);
+
+private:
+ OrganizerCore& m_core;
+ IUserInterface* m_ui;
+ spawn::SpawnParameters m_sp;
+ QString m_customOverwrite;
+ ForcedLibraries m_forcedLibraries;
+ QString m_profileName;
+ UILocker::Reasons m_lockReason;
+ WaitFlags m_waitFlags;
+ QString m_shellOpen;
+ env::HandlePtr m_handle;
+ DWORD m_exitCode;
+
+
+ // runs the command in m_shellOpen; returns empty if it can be waited for
+ //
+ std::optional<Results> runShell();
+
+ // runs the binary; returns empty if it can be waited for
+ //
+ std::optional<Results> runBinary();
+
+ // waits for process completion if required
+ //
+ Results postRun();
+
+ // creates the lock widget and calls f()
+ //
+ void withLock(std::function<void (UILocker::Session&)> f);
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags);
+
+#endif // PROCESSRUNNER_H
diff --git a/src/spawn.cpp b/src/spawn.cpp
index a34230b2..f95846c8 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -440,7 +440,26 @@ QMessageBox::StandardButton confirmBlacklisted(
namespace spawn
{
-DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle)
+void logSpawning(const SpawnParameters& sp, const QString& realCmd)
+{
+ log::debug(
+ "spawning binary:\n"
+ " . exe: '{}'\n"
+ " . args: '{}'\n"
+ " . cwd: '{}'\n"
+ " . steam id: '{}'\n"
+ " . hooked: {}\n"
+ " . stdout: {}\n"
+ " . stderr: {}\n"
+ " . real cmd: '{}'",
+ sp.binary.absoluteFilePath(), sp.arguments,
+ sp.currentDirectory.absolutePath(), sp.steamAppID, sp.hooked,
+ (sp.stdOut == INVALID_HANDLE_VALUE ? "no" : "yes"),
+ (sp.stdErr == INVALID_HANDLE_VALUE ? "no" : "yes"),
+ realCmd);
+}
+
+DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle)
{
BOOL inheritHandles = FALSE;
@@ -460,30 +479,33 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand
si.dwFlags |= STARTF_USESTDHANDLES;
}
- const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString();
- const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString();
+ const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath());
+ const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath());
- std::wstring commandLine = L"\"" + bin + L"\"";
- if (sp.arguments[0] != L'\0') {
- commandLine += L" " + sp.arguments.toStdWString();
+ QString commandLine = "\"" + bin + "\"";
+ if (!sp.arguments.isEmpty()) {
+ commandLine += " " + sp.arguments;
}
- QString moPath = QCoreApplication::applicationDirPath();
+ const QString moPath = QCoreApplication::applicationDirPath();
const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath));
- PROCESS_INFORMATION pi;
+ PROCESS_INFORMATION pi = {};
BOOL success = FALSE;
+ logSpawning(sp, commandLine);
+
+ const auto wcommandLine = commandLine.toStdWString();
+ const auto wcwd = cwd.toStdWString();
+
if (sp.hooked) {
success = ::CreateProcessHooked(
- nullptr, const_cast<wchar_t*>(commandLine.c_str()), nullptr, nullptr,
- inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
- cwd.c_str(), &si, &pi);
+ nullptr, const_cast<wchar_t*>(wcommandLine.c_str()), nullptr, nullptr,
+ inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi);
} else {
success = ::CreateProcess(
- nullptr, const_cast<wchar_t*>(commandLine.c_str()), nullptr, nullptr,
- inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
- cwd.c_str(), &si, &pi);
+ nullptr, const_cast<wchar_t*>(wcommandLine.c_str()), nullptr, nullptr,
+ inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi);
}
const auto e = GetLastError();
@@ -494,7 +516,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand
}
processHandle = pi.hProcess;
- threadHandle = pi.hThread;
+ ::CloseHandle(pi.hThread);
return ERROR_SUCCESS;
}
@@ -533,16 +555,6 @@ void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp)
restartAsAdmin(parent);
}
-bool checkBinary(QWidget* parent, const SpawnParameters& sp)
-{
- if (!sp.binary.exists()) {
- dialogs::spawnFailed(parent, sp, ERROR_FILE_NOT_FOUND);
- return false;
- }
-
- return true;
-}
-
struct SteamStatus
{
bool running=false;
@@ -618,8 +630,8 @@ bool startSteam(QWidget* parent)
(password.isEmpty() ? "no" : "yes"));
HANDLE ph = INVALID_HANDLE_VALUE;
- HANDLE th = INVALID_HANDLE_VALUE;
- const auto e = spawn(sp, ph, th);
+ const auto e = spawn(sp, ph);
+ ::CloseHandle(ph);
if (e != ERROR_SUCCESS) {
// make sure username and passwords are not shown
@@ -730,31 +742,6 @@ bool checkSteam(
return true;
}
-bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
-{
- // check if the Windows Event Logging service is running; for some reason,
- // this seems to be critical to the successful running of usvfs.
- const auto serviceName = "EventLog";
-
- const auto s = env::getService(serviceName);
-
- if (!s.isValid()) {
- log::error(
- "cannot determine the status of the {} service, continuing",
- serviceName);
-
- return true;
- }
-
- if (s.status() == env::Service::Status::Running) {
- log::debug("{}", s.toString());
- return true;
- }
-
- log::error("{}", s.toString());
- return dialogs::eventLogNotRunning(parent, s, sp);
-}
-
bool checkBlacklist(
QWidget* parent, const SpawnParameters& sp, Settings& settings)
{
@@ -771,18 +758,16 @@ bool checkBlacklist(
}
}
-
HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
{
- HANDLE processHandle, threadHandle;
- const auto e = spawn(sp, processHandle, threadHandle);
+ HANDLE handle = INVALID_HANDLE_VALUE;
+ const auto e = spawn::spawn(sp, handle);
switch (e)
{
case ERROR_SUCCESS:
{
- ::CloseHandle(threadHandle);
- return processHandle;
+ return handle;
}
case ERROR_ELEVATION_REQUIRED:
@@ -799,6 +784,184 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
}
}
+QString getExecutableForJarFile(const QString& jarFile)
+{
+ const std::wstring jarFileW = jarFile.toStdWString();
+
+ WCHAR buffer[MAX_PATH];
+
+ const auto hinst = ::FindExecutableW(jarFileW.c_str(), nullptr, buffer);
+ const auto r = static_cast<int>(reinterpret_cast<std::uintptr_t>(hinst));
+
+ // anything <= 32 signals failure
+ if (r <= 32) {
+ log::warn(
+ "failed to find executable associated with file '{}', {}",
+ jarFile, shell::formatError(r));
+
+ return {};
+ }
+
+ DWORD binaryType = 0;
+
+ if (!::GetBinaryTypeW(buffer, &binaryType)) {
+ const auto e = ::GetLastError();
+
+ log::warn(
+ "failed to determine binary type of '{}', {}",
+ QString::fromWCharArray(buffer), formatSystemMessage(e));
+
+ return {};
+ }
+
+ if (binaryType != SCS_32BIT_BINARY && binaryType != SCS_64BIT_BINARY) {
+ log::warn(
+ "unexpected binary type {} for file '{}'",
+ binaryType, QString::fromWCharArray(buffer));
+
+ return {};
+ }
+
+ return QString::fromWCharArray(buffer);
+}
+
+QString getJavaHome()
+{
+ const QString key = "HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment";
+ const QString value = "CurrentVersion";
+
+ QSettings reg(key, QSettings::NativeFormat);
+
+ if (!reg.contains(value)) {
+ log::warn("key '{}\\{}' doesn't exist", key, value);
+ return {};
+ }
+
+ const QString currentVersion = reg.value("CurrentVersion").toString();
+ const QString javaHome = QString("%1/JavaHome").arg(currentVersion);
+
+ if (!reg.contains(javaHome)) {
+ log::warn(
+ "java version '{}' was found at '{}\\{}', but '{}\\{}' doesn't exist",
+ currentVersion, key, value, key, javaHome);
+
+ return {};
+ }
+
+ const auto path = reg.value(javaHome).toString();
+ return path + "\\bin\\javaw.exe";
+}
+
+QString findJavaInstallation(const QString& jarFile)
+{
+ // try to find java automatically based on the given jar file
+ if (!jarFile.isEmpty()) {
+ const auto s = getExecutableForJarFile(jarFile);
+ if (!s.isEmpty()) {
+ return s;
+ }
+ }
+
+ // second attempt: look to the registry
+ const auto s = getJavaHome();
+ if (!s.isEmpty()) {
+ return s;
+ }
+
+ // not found
+ return {};
+}
+
+bool isBatchFile(const QFileInfo& target)
+{
+ const auto batchExtensions = {"cmd", "bat"};
+
+ const QString extension = target.suffix();
+ for (auto&& e : batchExtensions) {
+ if (extension.compare(e, Qt::CaseInsensitive) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool isExeFile(const QFileInfo& target)
+{
+ return (target.suffix().compare("exe", Qt::CaseInsensitive) == 0);
+}
+
+bool isJavaFile(const QFileInfo& target)
+{
+ return (target.suffix().compare("jar", Qt::CaseInsensitive) == 0);
+}
+
+QFileInfo getCmdPath()
+{
+ const auto p = env::get("COMSPEC");
+ if (!p.isEmpty()) {
+ return p;
+ }
+
+ QString systemDirectory;
+
+ const std::size_t buffer_size = 1000;
+ wchar_t buffer[buffer_size + 1] = {};
+
+ const auto length = ::GetSystemDirectoryW(buffer, buffer_size);
+ if (length != 0) {
+ systemDirectory = QString::fromWCharArray(buffer, length);
+
+ if (!systemDirectory.endsWith("\\")) {
+ systemDirectory += "\\";
+ }
+ } else {
+ systemDirectory = "C:\\Windows\\System32\\";
+ }
+
+ return systemDirectory + "cmd.exe";
+}
+
+FileExecutionContext getFileExecutionContext(
+ QWidget* parent, const QFileInfo& target)
+{
+ if (isExeFile(target)) {
+ return {
+ target,
+ "",
+ FileExecutionTypes::Executable
+ };
+ }
+
+ if (isBatchFile(target)) {
+ return {
+ getCmdPath(),
+ QString("/C \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())),
+ FileExecutionTypes::Executable
+ };
+ }
+
+ if (isJavaFile(target)) {
+ auto java = findJavaInstallation(target.absoluteFilePath());
+
+ if (java.isEmpty()) {
+ java = QFileDialog::getOpenFileName(
+ parent, QObject::tr("Select binary"),
+ QString(), QObject::tr("Binary") + " (*.exe)");
+ }
+
+ if (!java.isEmpty()) {
+ return {
+ QFileInfo(java),
+ QString("-jar \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())),
+ FileExecutionTypes::Executable
+ };
+ }
+ }
+
+ return {{}, {}, FileExecutionTypes::Other};
+}
+
} // namespace
@@ -817,7 +980,7 @@ bool helperExec(
{
SHELLEXECUTEINFOW execInfo = {};
- ULONG flags = SEE_MASK_FLAG_NO_UI ;
+ ULONG flags = SEE_MASK_FLAG_NO_UI;
if (!async)
flags |= SEE_MASK_NOCLOSEPROCESS;
diff --git a/src/spawn.h b/src/spawn.h
index 31b44739..a615b5ff 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -46,20 +46,17 @@ struct SpawnParameters
QFileInfo binary;
QString arguments;
QDir currentDirectory;
+ QString steamAppID;
bool hooked = false;
HANDLE stdOut = INVALID_HANDLE_VALUE;
HANDLE stdErr = INVALID_HANDLE_VALUE;
};
-bool checkBinary(QWidget* parent, const SpawnParameters& sp);
-
bool checkSteam(
QWidget* parent, const SpawnParameters& sp,
const QDir& gameDirectory, const QString &steamAppID, const Settings& settings);
-bool checkEnvironment(QWidget* parent, const SpawnParameters& sp);
-
bool checkBlacklist(
QWidget* parent, const SpawnParameters& sp, Settings& settings);
@@ -69,6 +66,25 @@ bool checkBlacklist(
**/
HANDLE startBinary(QWidget* parent, const SpawnParameters& sp);
+
+enum class FileExecutionTypes
+{
+ Executable = 1,
+ Other
+};
+
+struct FileExecutionContext
+{
+ QFileInfo binary;
+ QString arguments;
+ FileExecutionTypes type;
+};
+
+QString findJavaInstallation(const QString& jarFile);
+
+FileExecutionContext getFileExecutionContext(
+ QWidget* parent, const QFileInfo& target);
+
} // namespace
diff --git a/src/uilocker.cpp b/src/uilocker.cpp
new file mode 100644
index 00000000..01794d6b
--- /dev/null
+++ b/src/uilocker.cpp
@@ -0,0 +1,548 @@
+#include "uilocker.h"
+#include "mainwindow.h"
+#include <QGraphicsDropShadowEffect>
+#include <QMenuBar>
+#include <QStatusBar>
+
+class UILockerInterface
+{
+public:
+ UILockerInterface(QWidget* mainUI) :
+ m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr),
+ m_buttons(nullptr), m_reason(UILocker::NoReason)
+ {
+ m_timer.reset(new QTimer);
+ QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); });
+ m_timer->start(200);
+
+ set();
+ }
+
+ ~UILockerInterface()
+ {
+ }
+
+ void checkTarget()
+ {
+ if (set()) {
+ update(m_reason);
+ }
+ }
+
+ bool set()
+ {
+ QWidget* newTarget = nullptr;
+
+ newTarget = m_mainUI;
+ if (auto* w = QApplication::activeModalWidget()) {
+ newTarget = w;
+ }
+
+ if (newTarget == m_target) {
+ return false;
+ }
+
+ m_target = newTarget;
+
+ QFrame* center = nullptr;
+
+ if (m_target) {
+ center = createOverlay(m_target);
+ } else {
+ center = createDialog();
+ }
+
+ createMessageLabel();
+ createInfoLabel();
+ createButtonsPanel();
+
+ center->layout()->addWidget(m_message);
+ center->layout()->addWidget(m_info);
+ center->layout()->addWidget(m_buttons);
+
+ m_topLevel->setFocusPolicy(Qt::TabFocus);
+ m_topLevel->setFocus();
+ m_topLevel->show();
+ m_topLevel->setEnabled(true);
+
+ m_topLevel->raise();
+ m_topLevel->activateWindow();
+
+ return true;
+ }
+
+ void update(UILocker::Reasons reason)
+ {
+ m_reason = reason;
+ updateMessage(reason);
+ updateButtons(reason);
+ setInfo(m_labels);
+ }
+
+ void setInfo(const QStringList& labels)
+ {
+ const int MaxLabels = 2;
+
+ m_labels = labels;
+
+ QString s;
+
+ if (labels.size() > MaxLabels) {
+ s = labels.mid(0, MaxLabels).join(", ") + "...";
+ } else {
+ s = labels.join(", ");
+ }
+
+ m_info->setText(s);
+ }
+
+ QWidget* topLevel()
+ {
+ return m_topLevel.get();
+ }
+
+private:
+ class Filter : public QObject
+ {
+ public:
+ std::function<void ()> resized;
+ std::function<void ()> closed;
+
+ protected:
+ bool eventFilter(QObject* o, QEvent* e) override
+ {
+ if (e->type() == QEvent::Resize) {
+ if (resized) {
+ resized();
+ }
+ } else if (e->type() == QEvent::Close) {
+ if (closed) {
+ closed();
+ }
+ }
+
+ return QObject::eventFilter(o, e);
+ }
+ };
+
+
+ std::unique_ptr<QTimer> m_timer;
+ QWidget* m_mainUI;
+ QWidget* m_target;
+ std::unique_ptr<QWidget> m_topLevel;
+ QLabel* m_message;
+ QLabel* m_info;
+ QStringList m_labels;
+ QWidget* m_buttons;
+ std::unique_ptr<Filter> m_filter;
+ UILocker::Reasons m_reason;
+
+
+ bool hasMainUI() const
+ {
+ return (m_target != nullptr);
+ }
+
+ QWidget* createTransparentWidget(QWidget* parent=nullptr)
+ {
+ auto* w = new QWidget(parent);
+
+ w->setWindowOpacity(0);
+ w->setAttribute(Qt::WA_NoSystemBackground);
+ w->setAttribute(Qt::WA_TranslucentBackground);
+
+ return w;
+ }
+
+ QFrame* createOverlay(QWidget* mainUI)
+ {
+ m_topLevel.reset(createTransparentWidget(mainUI));
+ m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint);
+ m_topLevel->setGeometry(mainUI->rect());
+
+ m_filter.reset(new Filter);
+ m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); };
+ m_filter->closed = [=]{ checkTarget(); };
+
+ mainUI->installEventFilter(m_filter.get());
+
+ return createFrame();
+ }
+
+ QFrame* createDialog()
+ {
+ m_topLevel.reset(new QDialog);
+
+ return createFrame();
+ }
+
+ QFrame* createFrame()
+ {
+ auto* frame = new QFrame;
+ auto* ly = new QVBoxLayout(frame);
+
+ if (hasMainUI()) {
+ frame->setFrameStyle(QFrame::StyledPanel);
+ frame->setLineWidth(1);
+ frame->setAutoFillBackground(true);
+
+ auto* shadow = new QGraphicsDropShadowEffect;
+ shadow->setBlurRadius(50);
+ shadow->setOffset(0);
+ shadow->setColor(QColor(0, 0, 0, 100));
+ frame->setGraphicsEffect(shadow);
+ } else {
+ ly->setContentsMargins(0, 0, 0, 0);
+ }
+
+ auto* grid = new QGridLayout(m_topLevel.get());
+ grid->addWidget(createTransparentWidget(), 0, 1);
+ grid->addWidget(createTransparentWidget(), 2, 1);
+ grid->addWidget(createTransparentWidget(), 1, 0);
+ grid->addWidget(createTransparentWidget(), 1, 2);
+ grid->addWidget(frame, 1, 1);
+
+ if (!hasMainUI()) {
+ grid->setContentsMargins(0, 0, 0, 0);
+ }
+
+ grid->setRowStretch(0, 1);
+ grid->setRowStretch(2, 1);
+ grid->setColumnStretch(0, 1);
+ grid->setColumnStretch(2, 1);
+
+ return frame;
+ }
+
+ void createMessageLabel()
+ {
+ m_message = new QLabel;
+ m_message->setAlignment(Qt::AlignCenter | Qt::AlignHCenter);
+ }
+
+ void createInfoLabel()
+ {
+ m_info = new QLabel(" ");
+ m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter);
+ }
+
+ void createButtonsPanel()
+ {
+ m_buttons = new QWidget;
+ m_buttons->setLayout(new QHBoxLayout);
+ }
+
+
+ void updateMessage(UILocker::Reasons reason)
+ {
+ switch (reason)
+ {
+ case UILocker::LockUI:
+ {
+ QString s;
+
+ if (hasMainUI()) {
+ s = QObject::tr(
+ "Mod Organizer is locked while the application is running.");
+ } else {
+ s = QObject::tr("Mod Organizer is currently running an application.");
+ }
+
+ m_message->setText(s);
+
+ break;
+ }
+
+ case UILocker::OutputRequired:
+ {
+ m_message->setText(QObject::tr(
+ "The application must run to completion because its output is "
+ "required."));
+
+ break;
+ }
+
+ case UILocker::PreventExit:
+ {
+ m_message->setText(QObject::tr(
+ "Mod Organizer is waiting on application to close before exiting."));
+
+ break;
+ }
+ }
+ }
+
+ void updateButtons(UILocker::Reasons reason)
+ {
+ MOBase::deleteChildWidgets(m_buttons);
+ auto* ly = m_buttons->layout();
+
+ switch (reason)
+ {
+ case UILocker::LockUI: // fall-through
+ case UILocker::OutputRequired:
+ {
+ auto* unlock = new QPushButton(QObject::tr("Unlock"));
+
+ QObject::connect(unlock, &QPushButton::clicked, [&]{
+ UILocker::instance().onForceUnlock();
+ });
+
+ ly->addWidget(unlock);
+
+ break;
+ }
+
+ case UILocker::PreventExit:
+ {
+ auto* exit = new QPushButton(QObject::tr("Exit Now"));
+ QObject::connect(exit, &QPushButton::clicked, [&]{
+ UILocker::instance().onForceUnlock();
+ });
+
+ ly->addWidget(exit);
+
+ auto* cancel = new QPushButton(QObject::tr("Cancel"));
+ QObject::connect(cancel, &QPushButton::clicked, [&]{
+ UILocker::instance().onCancel();
+ });
+
+ ly->addWidget(cancel);
+
+ break;
+ }
+ }
+ }
+};
+
+UILocker::Session::~Session()
+{
+ unlock();
+}
+
+void UILocker::Session::unlock()
+{
+ QMetaObject::invokeMethod(qApp, [this]{
+ UILocker::instance().unlock(this);
+ });
+}
+
+void UILocker::Session::setInfo(DWORD pid, const QString& name)
+{
+ {
+ std::scoped_lock lock(m_mutex);
+ m_pid = pid;
+ m_name = name;
+ }
+
+ QMetaObject::invokeMethod(qApp, [this]{
+ UILocker::instance().updateLabel();
+ });
+}
+
+DWORD UILocker::Session::pid() const
+{
+ std::scoped_lock lock(m_mutex);
+ return m_pid;
+}
+
+const QString& UILocker::Session::name() const
+{
+ std::scoped_lock lock(m_mutex);
+ return m_name;
+}
+
+UILocker::Results UILocker::Session::result() const
+{
+ return UILocker::instance().result();
+}
+
+
+static UILocker* g_instance = nullptr;
+
+
+UILocker::UILocker()
+ : m_parent(nullptr), m_result(NoResult)
+{
+ Q_ASSERT(!g_instance);
+ g_instance = this;
+}
+
+UILocker::~UILocker()
+{
+ const auto v = m_sessions;
+
+ for (auto& wp : v) {
+ if (auto s=wp.lock()) {
+ unlock(s.get());
+ }
+ }
+}
+
+UILocker& UILocker::instance()
+{
+ Q_ASSERT(g_instance);
+ return *g_instance;
+}
+
+void UILocker::setUserInterface(QWidget* parent)
+{
+ m_parent = parent;
+}
+
+std::shared_ptr<UILocker::Session> UILocker::lock(Reasons reason)
+{
+ m_result = StillLocked;
+ createUi(reason);
+
+ auto ls = std::make_shared<Session>();
+ m_sessions.push_back(ls);
+
+ updateLabel();
+
+ return ls;
+}
+
+void UILocker::unlock(Session* s)
+{
+ auto itor = m_sessions.begin();
+ for (;;) {
+ if (itor == m_sessions.end()) {
+ break;
+ }
+
+ if (auto ss=itor->lock()) {
+ if (ss.get() == s) {
+ itor = m_sessions.erase(itor);
+ continue;
+ }
+ } else {
+ itor = m_sessions.erase(itor);
+ continue;
+ }
+
+ ++itor;
+ }
+
+ if (m_sessions.empty()) {
+ m_ui.reset();
+ enableAll();
+ } else {
+ updateLabel();
+ }
+}
+
+void UILocker::unlockCurrent()
+{
+ if (m_sessions.empty()) {
+ return;
+ }
+
+ auto s = m_sessions.back().lock();
+ if (!s) {
+ m_sessions.pop_back();
+ return;
+ }
+
+ unlock(s.get());
+}
+
+void UILocker::updateLabel()
+{
+ QStringList labels;
+
+ for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) {
+ if (auto ss=itor->lock()) {
+ labels.push_back(QString("%1 (%2)").arg(ss->name()).arg(ss->pid()));
+ }
+ }
+
+ m_ui->setInfo(labels);
+}
+
+UILocker::Results UILocker::result() const
+{
+ return m_result;
+}
+
+void UILocker::createUi(Reasons reason)
+{
+ if (!m_ui) {
+ m_ui.reset(new UILockerInterface(m_parent));
+ }
+
+ m_ui->update(reason);
+
+ disableAll();
+}
+
+void UILocker::onForceUnlock()
+{
+ m_result = ForceUnlocked;
+ unlockCurrent();
+}
+
+void UILocker::onCancel()
+{
+ m_result = Cancelled;
+ unlockCurrent();
+}
+
+template <class T>
+QList<T> findChildrenImmediate(QWidget* parent)
+{
+ return parent->findChildren<T>(QString(), Qt::FindDirectChildrenOnly);
+}
+
+void UILocker::disableAll()
+{
+ const auto topLevels = QApplication::topLevelWidgets();
+
+ for (auto* w : topLevels) {
+ if (auto* mw=dynamic_cast<QMainWindow*>(w)) {
+ disable(mw->centralWidget());
+ disable(mw->menuBar());
+ disable(mw->statusBar());
+
+ for (auto* tb : findChildrenImmediate<QToolBar*>(w)) {
+ disable(tb);
+ }
+
+ for (auto* d : findChildrenImmediate<QDockWidget*>(w)) {
+ disable(d);
+ }
+ }
+
+ if (auto* d=dynamic_cast<QDialog*>(w)) {
+ // don't disable stuff if this dialog is the overlay, which happens when
+ // there's no ui
+ if (d != m_ui->topLevel()) {
+ // no central widget, just disable the children, except for the overlay
+ for (auto* child : findChildrenImmediate<QWidget*>(d)) {
+ if (child != m_ui->topLevel()) {
+ disable(child);
+ }
+ }
+ }
+ }
+ }
+}
+
+void UILocker::enableAll()
+{
+ for (auto w : m_disabled) {
+ if (w) {
+ w->setEnabled(true);
+ }
+ }
+
+ m_disabled.clear();
+}
+
+void UILocker::disable(QWidget* w)
+{
+ if (w->isEnabled()) {
+ w->setEnabled(false);
+ m_disabled.push_back(w);
+ }
+}
diff --git a/src/uilocker.h b/src/uilocker.h
new file mode 100644
index 00000000..d8c22999
--- /dev/null
+++ b/src/uilocker.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <QMainWindow>
+#include <mutex>
+
+class UILockerInterface;
+
+class UILocker
+{
+ friend class UILockerInterface;
+
+public:
+ // reason to show the widget
+ //
+ enum Reasons
+ {
+ NoReason = 0,
+
+ // lock the ui
+ LockUI,
+
+ // because the output is required
+ OutputRequired,
+
+ // to prevent exiting until all processes are completed
+ PreventExit
+ };
+
+ // returned by result()
+ //
+ enum Results
+ {
+ NoResult = 0,
+
+ // the widget is still up
+ StillLocked,
+
+ // force unlock was clicked
+ ForceUnlocked,
+
+ // cancel was clicked
+ Cancelled
+ };
+
+
+ class Session
+ {
+ public:
+ ~Session();
+
+ void unlock();
+ void setInfo(DWORD pid, const QString& name);
+ Results result() const;
+
+ DWORD pid() const;
+ const QString& name() const;
+
+ private:
+ mutable std::mutex m_mutex;
+ DWORD m_pid;
+ QString m_name;
+ };
+
+
+ UILocker();
+ ~UILocker();
+
+ static UILocker& instance();
+
+ void setUserInterface(QWidget* parent);
+
+ std::shared_ptr<Session> lock(Reasons reason);
+
+ Results result() const;
+
+private:
+ QWidget* m_parent;
+ std::unique_ptr<UILockerInterface> m_ui;
+ std::vector<std::weak_ptr<Session>> m_sessions;
+ std::atomic<Results> m_result;
+ std::vector<QPointer<QWidget>> m_disabled;
+
+ void createUi(Reasons reason);
+
+ void unlockCurrent();
+ void unlock(Session* s);
+ void updateLabel();
+
+ void onForceUnlock();
+ void onCancel();
+
+ void disableAll();
+ void enableAll();
+ void disable(QWidget* w);
+};
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp
index 311c6dd3..3dba3efc 100644
--- a/src/usvfsconnector.cpp
+++ b/src/usvfsconnector.cpp
@@ -20,6 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "usvfsconnector.h"
#include "settings.h"
#include "organizercore.h"
+#include "envmodule.h"
#include "shared/util.h"
#include <memory>
#include <sstream>
@@ -256,3 +257,49 @@ void UsvfsConnector::updateForcedLibraries(const QList<MOBase::ExecutableForcedL
}
}
}
+
+
+std::vector<HANDLE> getRunningUSVFSProcesses()
+{
+ std::vector<DWORD> pids;
+
+ {
+ size_t count = 0;
+ DWORD* buffer = nullptr;
+ if (!::GetVFSProcessList2(&count, &buffer)) {
+ log::error("failed to get usvfs process list");
+ return {};
+ }
+
+ if (buffer) {
+ pids.assign(buffer, buffer + count);
+ std::free(buffer);
+ }
+ }
+
+ const auto thisPid = GetCurrentProcessId();
+ std::vector<HANDLE> v;
+
+ for (auto&& pid : pids) {
+ if (pid == thisPid) {
+ continue; // obviously don't wait for MO process
+ }
+
+ HANDLE handle = ::OpenProcess(
+ PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pid);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ const auto e = GetLastError();
+
+ log::warn(
+ "failed to open usvfs process {}: {}",
+ pid, formatSystemMessage(e));
+
+ continue;
+ }
+
+ v.push_back(handle);
+ }
+
+ return v;
+}
diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h
index d0071678..5982778b 100644
--- a/src/usvfsconnector.h
+++ b/src/usvfsconnector.h
@@ -103,4 +103,6 @@ private:
CrashDumpsType crashDumpsType(int type);
+std::vector<HANDLE> getRunningUSVFSProcesses();
+
#endif // USVFSCONNECTOR_H
diff --git a/src/waitingonclosedialog.cpp b/src/waitingonclosedialog.cpp
deleted file mode 100644
index 565d0a36..00000000
--- a/src/waitingonclosedialog.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "waitingonclosedialog.h"
-#include "ui_waitingonclosedialog.h"
-
-#include <QPoint>
-#include <QResizeEvent>
-#include <QWidget>
-#include <Qt> // for Qt::FramelessWindowHint, etc
-
-WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget *parent)
- : LockedDialogBase(parent,true)
- , ui(new Ui::WaitingOnCloseDialog)
-{
- ui->setupUi(this);
-
- // Supposedly the Qt::CustomizeWindowHint should use a customized window
- // allowing us to select if there is a close button. In practice this doesn't
- // seem to work. We will ignore pressing the close button if unlockByButton == true
- Qt::WindowFlags flags =
- this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint;
- if (m_allowClose)
- flags |= Qt::WindowCloseButtonHint;
- this->setWindowFlags(flags);
-}
-
-WaitingOnCloseDialog::~WaitingOnCloseDialog()
-{
- delete ui;
-}
-
-
-void WaitingOnCloseDialog::setProcessName(const QString &name)
-{
- ui->processLabel->setText(name);
-}
-
-void WaitingOnCloseDialog::on_closeButton_clicked()
-{
- unlock();
-}
-
-void WaitingOnCloseDialog::on_cancelButton_clicked()
-{
- cancel();
- unlock();
-}
-
-void WaitingOnCloseDialog::unlock() {
- LockedDialogBase::unlock();
- ui->label->setText("unlocking may take a few seconds");
- ui->closeButton->setEnabled(false);
- ui->cancelButton->setEnabled(false);
-}
diff --git a/src/waitingonclosedialog.h b/src/waitingonclosedialog.h
deleted file mode 100644
index 6650c390..00000000
--- a/src/waitingonclosedialog.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#pragma once
-
-#include "lockeddialogbase.h"
-
-namespace Ui {
- class WaitingOnCloseDialog;
-}
-
-/**
- * Similar to the LockedDialog but used for waiting on running process during
- * a process close request which requries a slightly different dialog.
- **/
-class WaitingOnCloseDialog : public LockedDialogBase
-{
- Q_OBJECT
-
-public:
- explicit WaitingOnCloseDialog(QWidget *parent = 0);
- ~WaitingOnCloseDialog();
-
- bool canceled() const { return m_Canceled; }
-
- void setProcessName(const QString &name) override;
-
-protected:
-
- void unlock() override;
-
-private slots:
-
- void on_closeButton_clicked();
- void on_cancelButton_clicked();
-
-private:
-
- Ui::WaitingOnCloseDialog *ui;
-};
diff --git a/src/waitingonclosedialog.ui b/src/waitingonclosedialog.ui
deleted file mode 100644
index 9c7818e0..00000000
--- a/src/waitingonclosedialog.ui
+++ /dev/null
@@ -1,119 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>WaitingOnCloseDialog</class>
- <widget class="QDialog" name="WaitingOnCloseDialog">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>317</width>
- <height>151</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Waiting for virtualized processes</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0">
- <item>
- <widget class="QLabel" name="label">
- <property name="toolTip">
- <string>This dialog should disappear automatically if the application/game is done.</string>
- </property>
- <property name="text">
- <string>Virtualized processes are still running, it is prefered to keep MO running until they are finished.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer1">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <height>10</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="processLabel">
- <property name="font">
- <font>
- <italic>true</italic>
- </font>
- </property>
- <property name="styleSheet">
- <string notr="true">color: grey;</string>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <height>10</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0">
- <item>
- <widget class="QPushButton" name="closeButton">
- <property name="text">
- <string>Close Now</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_6">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="cancelButton">
- <property name="text">
- <string>Cancel</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <spacer name="verticalSpacer3">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <height>10</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>