Add QDataStream functions to PgnGameEntry
[sloppygui.git] / projects / lib / src / engineprocess_win.cpp
blob3f9dc99593c50143fe82cd0c7d86208e2edbcb7e
1 /*
2 This file is part of Cute Chess.
4 Cute Chess is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 Cute Chess is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with Cute Chess. If not, see <http://www.gnu.org/licenses/>.
18 #include "engineprocess_win.h"
19 #include <QDir>
20 #include <QRegExp>
21 #include <QtDebug>
22 #include "pipereader_win.h"
25 EngineProcess::EngineProcess(QObject* parent)
26 : QIODevice(parent),
27 m_started(false),
28 m_finished(false),
29 m_exitCode(0),
30 m_exitStatus(EngineProcess::NormalExit),
31 m_inWrite(INVALID_HANDLE_VALUE),
32 m_outRead(INVALID_HANDLE_VALUE),
33 m_reader(0)
37 EngineProcess::~EngineProcess()
39 if (m_started)
41 qWarning("EngineProcess: Destroyed while process is still running.");
42 kill();
43 waitForFinished();
45 cleanup();
48 int EngineProcess::exitCode() const
50 return (int)m_exitCode;
53 EngineProcess::ExitStatus EngineProcess::exitStatus() const
55 return m_exitStatus;
58 qint64 EngineProcess::bytesAvailable() const
60 qint64 n = QIODevice::bytesAvailable();
62 if (!m_started)
63 return n;
64 return m_reader->bytesAvailable() + n;
67 bool EngineProcess::canReadLine() const
69 if (!m_started)
70 return QIODevice::canReadLine();
71 return m_reader->canReadLine() || QIODevice::canReadLine();
74 void EngineProcess::killHandle(HANDLE* handle)
76 if (*handle == INVALID_HANDLE_VALUE)
77 return;
78 CloseHandle(*handle);
79 *handle = INVALID_HANDLE_VALUE;
82 void EngineProcess::cleanup()
84 if (m_reader != 0)
86 if (m_reader->isRunning())
88 qWarning("EngineProcess: pipe reader was terminated");
89 m_reader->terminate();
91 delete m_reader;
92 m_reader = 0;
95 killHandle(&m_inWrite);
96 killHandle(&m_outRead);
98 killHandle(&m_processInfo.hProcess);
99 killHandle(&m_processInfo.hThread);
101 m_started = false;
104 void EngineProcess::close()
106 if (!m_started)
107 return;
109 emit aboutToClose();
110 kill();
111 waitForFinished(-1);
112 cleanup();
113 QIODevice::close();
116 bool EngineProcess::isSequential() const
118 return true;
121 void EngineProcess::setWorkingDirectory(const QString& dir)
123 m_workDir = dir;
126 static QString quoteString(QString str)
128 if (!str.contains(' '))
129 return str;
131 if (!str.startsWith('\"'))
132 str.prepend('\"');
133 if (!str.endsWith('\"'))
134 str.append('\"');
136 return str;
139 static QString commandLine(const QString& prog, const QStringList& args)
141 QString cmd = QDir::toNativeSeparators(quoteString(prog));
142 foreach (const QString& arg, args)
143 cmd += ' ' + quoteString(arg);
145 return cmd;
148 void EngineProcess::start(const QString& program,
149 const QStringList& arguments,
150 OpenMode mode)
152 if (m_started)
153 close();
155 m_started = false;
156 m_finished = false;
157 m_exitCode = 0;
158 m_exitStatus = NormalExit;
160 // Temporary handles for the child process' end of the pipes
161 HANDLE outWrite;
162 HANDLE inRead;
164 // Security attributes. Use the same one for both pipes.
165 SECURITY_ATTRIBUTES saAttr;
166 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
167 saAttr.bInheritHandle = TRUE;
168 saAttr.lpSecurityDescriptor = NULL;
170 CreatePipe(&m_outRead, &outWrite, &saAttr, 0);
171 CreatePipe(&inRead, &m_inWrite, &saAttr, 0);
173 STARTUPINFO startupInfo;
174 ZeroMemory(&startupInfo, sizeof(startupInfo));
175 startupInfo.cb = sizeof(startupInfo);
176 startupInfo.hStdError = outWrite;
177 startupInfo.hStdOutput = outWrite;
178 startupInfo.hStdInput = inRead;
179 startupInfo.dwFlags |= STARTF_USESTDHANDLES;
181 // Call DuplicateHandle with a NULL target to get non-inheritable
182 // handles for the parent process' ends of the pipes
183 DuplicateHandle(GetCurrentProcess(),
184 m_outRead, // child's stdout read end
185 GetCurrentProcess(),
186 NULL, // no target
187 0, // flags
188 FALSE, // not inheritable
189 DUPLICATE_SAME_ACCESS); // same handle access
190 DuplicateHandle(GetCurrentProcess(),
191 m_inWrite, // child's stdin write end
192 GetCurrentProcess(),
193 NULL, // no target
194 0, // flags
195 FALSE, // not inheritable
196 DUPLICATE_SAME_ACCESS); // same handle access
198 BOOL ok = FALSE;
199 QString cmd = commandLine(program, arguments);
200 QString wdir = QDir::toNativeSeparators(m_workDir);
201 ZeroMemory(&m_processInfo, sizeof(m_processInfo));
203 #ifdef UNICODE
204 ok = CreateProcessW(NULL,
205 (WCHAR*)cmd.utf16(),
206 NULL, // process attributes
207 NULL, // thread attributes
208 TRUE, // inherit handles
209 CREATE_NEW_PROCESS_GROUP, // creation flags
210 NULL, // environment
211 wdir.isEmpty() ? NULL : (WCHAR*)wdir.utf16(),
212 &startupInfo,
213 &m_processInfo);
214 #else // not UNICODE
215 ok = CreateProcessA(NULL,
216 cmd.toLocal8Bit().data(),
217 NULL, // process attributes
218 NULL, // thread attributes
219 TRUE, // inherit handles
220 CREATE_NEW_PROCESS_GROUP, // creation flags
221 NULL, // environment
222 wdir.isEmpty() ? NULL : wdir.toLocal8Bit().data(),
223 &startupInfo,
224 &m_processInfo);
225 #endif // not UNICODE
227 m_started = (bool)ok;
228 if (ok)
230 // Close the child process' ends of the pipes to make sure
231 // that ReadFile and WriteFile will return when the child
232 // terminates and closes its pipes
233 killHandle(&outWrite);
234 killHandle(&inRead);
236 // Start reading input from the child
237 m_reader = new PipeReader(m_outRead, this);
238 connect(m_reader, SIGNAL(finished()), this, SLOT(onFinished()));
239 connect(m_reader, SIGNAL(finished()), this, SIGNAL(readChannelFinished()));
240 connect(m_reader, SIGNAL(readyRead()), this, SIGNAL(readyRead()));
241 m_reader->start();
243 // Make QIODevice aware that the device is now open
244 QIODevice::open(mode);
246 else
247 cleanup();
250 void EngineProcess::start(const QString& program,
251 OpenMode mode)
253 QStringList args;
255 QRegExp rx("((?:[^\\s\"]+)|(?:\"(?:\\\\\"|[^\"])*\"))");
256 int pos = 0;
257 while ((pos = rx.indexIn(program, pos)) != -1)
259 args << rx.cap();
260 pos += rx.matchedLength();
262 if (args.isEmpty())
263 return;
265 QString prog = args.first();
266 args.removeFirst();
267 start(prog, args, mode);
270 void EngineProcess::kill()
272 if (m_started)
273 TerminateProcess(m_processInfo.hProcess, 0xf291);
276 void EngineProcess::onFinished()
278 if (!m_started || m_finished)
279 return;
281 if (GetExitCodeProcess(m_processInfo.hProcess, &m_exitCode)
282 && m_exitCode != STILL_ACTIVE)
284 m_finished = true;
285 m_exitStatus = NormalExit;
286 if (m_exitCode != 0)
287 m_exitStatus = CrashExit;
289 Q_ASSERT(m_reader == 0 || m_reader->isFinished());
290 cleanup();
291 emit finished((int)m_exitCode, m_exitStatus);
295 bool EngineProcess::waitForFinished(int msecs)
297 if (!m_started)
298 return true;
300 DWORD dwWait;
301 if (msecs == -1)
302 dwWait = INFINITE;
303 else
304 dwWait = msecs;
306 DWORD ret = WaitForSingleObject(m_processInfo.hProcess, dwWait);
307 if (ret == WAIT_OBJECT_0)
309 // The blocking ReadFile call in the pipe reader should
310 // return now that the pipes are closed. But if it doesn't
311 // happen, the pipe reader will be terminated violently
312 // after the timeout.
313 m_reader->wait(10000);
314 onFinished();
316 return true;
318 return false;
321 bool EngineProcess::waitForStarted(int msecs)
323 // Don't wait here because CreateProcess already did the waiting
324 Q_UNUSED(msecs);
325 return m_started;
328 QString EngineProcess::workingDirectory() const
330 return m_workDir;
333 qint64 EngineProcess::readData(char* data, qint64 maxSize)
335 if (!m_started)
336 return -1;
338 return m_reader->readData(data, maxSize);
341 qint64 EngineProcess::writeData(const char* data, qint64 maxSize)
343 if (!m_started)
344 return -1;
346 DWORD dwWritten = 0;
347 if (!WriteFile(m_inWrite, data, (DWORD)maxSize, &dwWritten, 0))
348 return -1;
349 return (qint64)dwWritten;