1 #include <qglobal.h>
2
3 #if QT_VERSION < 0x050000
4 #include <QApplication>
5 #else
6 #include <QGuiApplication>
7 #endif
8 #include <QXmlStreamReader>
9 #include <QFileInfo>
10 #include <QStringList>
11
12 #include <algorithm> // std::sort()
13
14 #include "processmanager.h"
15 #include "arcadesettings.h"
16 #include "tweakedqmlappviewer.h"
17 #include "macros.h"
18
19 extern ArcadeSettings *globalConfig;
20
ProcessManager(QObject * parent)21 ProcessManager::ProcessManager(QObject *parent) :
22 QObject(parent)
23 {
24 mCurrentProcessId = 0;
25 }
26
~ProcessManager()27 ProcessManager::~ProcessManager()
28 {
29 foreach (QProcess *proc, mProcessMap) {
30 proc->terminate();
31 proc->waitForFinished(500);
32 if ( proc->state() == QProcess::Running ) {
33 proc->kill();
34 proc->waitForFinished(250);
35 }
36 }
37 }
38
startEmulator(QString id)39 int ProcessManager::startEmulator(QString id)
40 {
41 if ( id.isEmpty() )
42 return -1;
43 else {
44 QProcess *proc = new QProcess(this);
45 QStringList args;
46
47 foreach (EmulatorOption emuOpt, mTemplateList) {
48 QString globalOptionKey = globalConfig->emulatorPrefix + "/Configuration/Global/" + emuOpt.name;
49 QString localOptionKey = globalConfig->emulatorPrefix + QString("/Configuration/%1/").arg(id) + emuOpt.name;
50 bool enforceDefault = globalConfig->value(globalConfig->emulatorPrefix + "/Configuration/Global/EnforceDefault/" + emuOpt.name, false).toBool() || globalConfig->value(globalConfig->emulatorPrefix + QString("/Configuration/%1/EnforceDefault/").arg(id) + emuOpt.name, false).toBool();
51
52 switch ( emuOpt.type ) {
53 case QMC2_ARCADE_EMUOPT_INT: {
54 int dv = emuOpt.dvalue.toInt();
55 int gv = globalConfig->value(globalOptionKey, dv).toInt();
56 int v = globalConfig->value(localOptionKey, gv).toInt();
57 if ( enforceDefault || v != dv )
58 args << QString("-%1").arg(emuOpt.name) << QString("%1").arg(v);
59 break;
60 }
61 case QMC2_ARCADE_EMUOPT_FLOAT: {
62 double dv = emuOpt.dvalue.toDouble();
63 double gv = globalConfig->value(globalOptionKey, dv).toDouble();
64 double v = globalConfig->value(localOptionKey, gv).toDouble();
65 if ( enforceDefault || v != dv )
66 args << QString("-%1").arg(emuOpt.name) << QString("%1").arg(v);
67 break;
68 }
69 case QMC2_ARCADE_EMUOPT_FLOAT2: {
70 QStringList subValues = emuOpt.value.split(",");
71 QStringList defaultSubValues = emuOpt.dvalue.split(",");
72 double v1, v2, dv1, dv2;
73 v1 = v2 = dv1 = dv2 = 0;
74 if ( subValues.count() > 0 )
75 v1 = subValues[0].toDouble();
76 if ( subValues.count() > 1 )
77 v2 = subValues[1].toDouble();
78 if ( defaultSubValues.count() > 0 )
79 dv1 = defaultSubValues[0].toDouble();
80 if ( defaultSubValues.count() > 1 )
81 dv2 = defaultSubValues[1].toDouble();
82 if ( enforceDefault || v1 != dv1 || v2 != dv2 )
83 args << QString("-%1").arg(emuOpt.name) << QString("%1,%2").arg(v1).arg(v2);
84 break;
85 }
86 case QMC2_ARCADE_EMUOPT_FLOAT3: {
87 QStringList subValues = emuOpt.value.split(",");
88 QStringList defaultSubValues = emuOpt.dvalue.split(",");
89 double v1, v2, v3, dv1, dv2, dv3;
90 v1 = v2 = v3 = dv1 = dv2 = dv3 = 0;
91 if ( subValues.count() > 0 )
92 v1 = subValues[0].toDouble();
93 if ( subValues.count() > 1 )
94 v2 = subValues[1].toDouble();
95 if ( subValues.count() > 2 )
96 v3 = subValues[2].toDouble();
97 if ( defaultSubValues.count() > 0 )
98 dv1 = defaultSubValues[0].toDouble();
99 if ( defaultSubValues.count() > 1 )
100 dv2 = defaultSubValues[1].toDouble();
101 if ( defaultSubValues.count() > 2 )
102 dv3 = defaultSubValues[2].toDouble();
103 if ( enforceDefault || v1 != dv1 || v2 != dv2 || v3 != dv3 )
104 args << QString("-%1").arg(emuOpt.name) << QString("%1,%2,%3").arg(v1).arg(v2).arg(v3);
105 break;
106 }
107 case QMC2_ARCADE_EMUOPT_BOOL: {
108 bool dv = (emuOpt.dvalue == "true");
109 bool gv = globalConfig->value(globalOptionKey, dv).toBool();
110 bool v = globalConfig->value(localOptionKey, gv).toBool();
111 if ( enforceDefault || v != dv ) {
112 if ( v )
113 args << QString("-%1").arg(emuOpt.name);
114 else
115 args << QString("-no%1").arg(emuOpt.name);
116 }
117 break;
118 }
119 case QMC2_ARCADE_EMUOPT_STRING:
120 default: {
121 QString dv = emuOpt.dvalue;
122 QString gv = globalConfig->value(globalOptionKey, dv).toString();
123 QString v = globalConfig->value(localOptionKey, gv).toString();
124 if ( enforceDefault || v != dv )
125 args << QString("-%1").arg(emuOpt.name) << v.replace("~", "$HOME");
126 break;
127 }
128 }
129 }
130
131 args << id;
132
133 connect(proc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
134 connect(proc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
135 connect(proc, SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOutput()));
136 connect(proc, SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardError()));
137 connect(proc, SIGNAL(started()), this, SLOT(started()));
138 connect(proc, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(stateChanged(QProcess::ProcessState)));
139
140 if ( !globalConfig->emulatorWorkingDirectory().isEmpty() )
141 proc->setWorkingDirectory(globalConfig->emulatorWorkingDirectory());
142
143 mProcessMap.insert(mCurrentProcessId, proc);
144 proc->start(globalConfig->emulatorExecutablePath(), args);
145
146 return mCurrentProcessId++;
147 }
148 }
149
createTemplateList()150 void ProcessManager::createTemplateList()
151 {
152 QString templateFilePath = QFileInfo(globalConfig->optionsTemplateFile()).absoluteFilePath();
153 QMC2_ARCADE_LOG_STR(tr("Loading configuration template from '%1'").arg(QDir::toNativeSeparators(templateFilePath)));
154 mTemplateList.clear();
155 QFile templateFile(templateFilePath);
156 if ( templateFile.open(QFile::ReadOnly) ) {
157 QXmlStreamReader xmlReader(&templateFile);
158 while ( !xmlReader.atEnd() ) {
159 xmlReader.readNext();
160 if ( xmlReader.hasError() ) {
161 QMC2_ARCADE_LOG_STR(tr("FATAL: XML error reading template: '%1' in file '%2' at line %3, column %4").
162 arg(xmlReader.errorString()).arg(QDir::toNativeSeparators(templateFilePath)).arg(xmlReader.lineNumber()).arg(xmlReader.columnNumber()));
163 } else {
164 if ( xmlReader.isStartElement() ) {
165 QString elementType = xmlReader.name().toString();
166 QXmlStreamAttributes attributes = xmlReader.attributes();
167 QString name = attributes.value("name").toString();
168 if ( elementType == "option" ) {
169 bool ignore = false;
170 QString shortName;
171 if ( attributes.hasAttribute("shortname") )
172 shortName = attributes.value("shortname").toString();
173 if ( attributes.hasAttribute("ignore") )
174 ignore = attributes.value("ignore") == "true";
175 if ( attributes.hasAttribute(QString("ignore.%1").arg(QMC2_ARCADE_OS_NAME)) )
176 ignore = attributes.value(QString("ignore.%1").arg(QMC2_ARCADE_OS_NAME)) == "true";
177 if ( !ignore ) {
178 QString type = attributes.value("type").toString();
179 QString defaultValue;
180 if ( attributes.hasAttribute(QString("default.%1").arg(QMC2_ARCADE_OS_NAME)) )
181 defaultValue = attributes.value(QString("default.%1").arg(QMC2_ARCADE_OS_NAME)).toString();
182 else
183 defaultValue = attributes.value("default").toString();
184 mTemplateList.append(EmulatorOption(name, shortName, type, defaultValue, QString()));
185 }
186 }
187 }
188 }
189 }
190 templateFile.close();
191 std::sort(mTemplateList.begin(), mTemplateList.end(), EmulatorOption::lessThan);
192 QMC2_ARCADE_LOG_STR(QString(tr("Done (loading configuration template from '%1')").arg(QDir::toNativeSeparators(templateFilePath)) + " - " + tr("%n option(s) loaded", "", mTemplateList.count())));
193 } else
194 QMC2_ARCADE_LOG_STR(tr("FATAL: Can't open the configuration template file: reason = %1").arg(fileErrorToString(templateFile.error())));
195 }
196
fileErrorToString(QFile::FileError errorCode)197 QString ProcessManager::fileErrorToString(QFile::FileError errorCode)
198 {
199 switch ( errorCode ) {
200 case QFile::NoError:
201 return tr("No error occurred");
202 case QFile::ReadError:
203 return tr("An error occurred when reading from the file");
204 case QFile::WriteError:
205 return tr("An error occurred when writing to the file");
206 case QFile::FatalError:
207 return tr("A fatal error occurred");
208 case QFile::ResourceError:
209 return tr("A resource error occurred");
210 case QFile::OpenError:
211 return tr("The file could not be opened");
212 case QFile::AbortError:
213 return tr("The operation was aborted");
214 case QFile::TimeOutError:
215 return tr("A timeout occurred");
216 case QFile::UnspecifiedError:
217 return tr("An unspecified error occurred");
218 case QFile::RemoveError:
219 return tr("The file could not be removed");
220 case QFile::RenameError:
221 return tr("The file could not be renamed");
222 case QFile::PositionError:
223 return tr("The position in the file could not be changed");
224 case QFile::ResizeError:
225 return tr("The file could not be resized");
226 case QFile::PermissionsError:
227 return tr("The file could not be accessed");
228 case QFile::CopyError:
229 return tr("The file could not be copied");
230 default:
231 return tr("An unknown error occurred");
232 }
233 }
234
processErrorToString(QProcess::ProcessError errorCode)235 QString ProcessManager::processErrorToString(QProcess::ProcessError errorCode)
236 {
237 switch ( errorCode ) {
238 case QProcess::FailedToStart:
239 return tr("The process failed to start");
240 case QProcess::Crashed:
241 return tr("The process crashed");
242 case QProcess::Timedout:
243 return tr("A timeout occurred");
244 case QProcess::WriteError:
245 return tr("An error occurred when attempting to write to the process");
246 case QProcess::ReadError:
247 return tr("An error occurred when attempting to read from the process");
248 case QProcess::UnknownError:
249 default:
250 return tr("An unknown error occurred");
251 }
252 }
253
processStateToString(QProcess::ProcessState state)254 QString ProcessManager::processStateToString(QProcess::ProcessState state)
255 {
256 switch ( state ) {
257 case QProcess::NotRunning:
258 return tr("Not running");
259 case QProcess::Starting:
260 return tr("Starting");
261 case QProcess::Running:
262 return tr("Running");
263 default:
264 return tr("Unknown");
265 }
266 }
267
error(QProcess::ProcessError errorCode)268 void ProcessManager::error(QProcess::ProcessError errorCode)
269 {
270 QProcess *proc = (QProcess *)sender();
271 int procID = mProcessMap.key(proc);
272 QMC2_ARCADE_LOG_STR(tr("Emulator #%1 error: reason = %2").arg(procID).arg(processErrorToString(errorCode)));
273 }
274
finished(int exitCode,QProcess::ExitStatus exitStatus)275 void ProcessManager::finished(int exitCode, QProcess::ExitStatus exitStatus)
276 {
277 QProcess *proc = (QProcess *)sender();
278 int procID = mProcessMap.key(proc);
279 QMC2_ARCADE_LOG_STR(tr("Emulator #%1 finished: exitCode = %2, exitStatus = %3").arg(procID).arg(exitCode).arg(exitStatus == QProcess::NormalExit ? tr("normal") : tr("crashed")));
280 mProcessMap.remove(procID);
281 }
282
readyReadStandardOutput()283 void ProcessManager::readyReadStandardOutput()
284 {
285 QProcess *proc = (QProcess *)sender();
286 int procID = mProcessMap.key(proc);
287 QString data = proc->readAllStandardOutput();
288 #if defined(QMC2_ARCADE_OS_WIN)
289 QString separator = "\r\n";
290 #else
291 QString separator = "\n";
292 #endif
293 foreach (QString line, data.split(separator)) {
294 if ( !line.isEmpty() ) {
295 QMC2_ARCADE_LOG_STR(tr("Emulator #%1 stdout: %2").arg(procID).arg(line));
296 }
297 }
298 }
299
readyReadStandardError()300 void ProcessManager::readyReadStandardError()
301 {
302 QProcess *proc = (QProcess *)sender();
303 int procID = mProcessMap.key(proc);
304 QString data = proc->readAllStandardError();
305 #if defined(QMC2_ARCADE_OS_WIN)
306 QString separator = "\r\n";
307 #else
308 QString separator = "\n";
309 #endif
310 foreach (QString line, data.split(separator)) {
311 if ( !line.isEmpty() ) {
312 QMC2_ARCADE_LOG_STR(tr("Emulator #%1 stderr: %2").arg(procID).arg(line));
313 }
314 }
315 }
316
started()317 void ProcessManager::started()
318 {
319 QProcess *proc = (QProcess *)sender();
320 int procID = mProcessMap.key(proc);
321 QMC2_ARCADE_LOG_STR(tr("Emulator #%1 started").arg(procID));
322 emit emulatorStarted(procID);
323 }
324
stateChanged(QProcess::ProcessState newState)325 void ProcessManager::stateChanged(QProcess::ProcessState newState)
326 {
327 QProcess *proc = (QProcess *)sender();
328 int procID = mProcessMap.key(proc);
329 QMC2_ARCADE_LOG_STR(tr("Emulator #%1 state changed: newState = %2").arg(procID).arg(processStateToString(newState)));
330 emit emulatorFinished(procID);
331 }
332