3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Angus Leeming
8 * \author Enrico Forestieri
9 * \author Peter Kuemmel
11 * Full author contact details are available in file CREDITS.
16 #include "support/debug.h"
17 #include "support/lstrings.h"
18 #include "support/qstring_helpers.h"
19 #include "support/Systemcall.h"
20 #include "support/SystemcallPrivate.h"
21 #include "support/os.h"
30 #include <QCoreApplication>
36 struct Sleep : QThread
38 static void millisec(unsigned long ms)
40 QThread::usleep(ms * 1000);
55 int Systemcall::startscript(Starttype how, string const & what,
56 bool /*process_events*/)
58 string command = what;
60 if (how == DontWait) {
61 switch (os::shell()) {
66 command = "start /min " + command;
71 return ::system(command.c_str());
78 string const parsecmd(string const & cmd, string & outfile)
83 for (size_t i = 0; i < cmd.length(); ++i) {
85 if ((c == '"' || c == '\'') && !escaped)
87 else if (c == '\\' && !escaped)
89 else if (c == '>' && !(inquote || escaped)) {
90 outfile = trim(cmd.substr(i + 1), " \"");
91 return trim(cmd.substr(0, i));
103 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
106 QString cmd = toqstr(parsecmd(what, outfile));
107 if (cmd.contains("'")) {
108 LYXERR0("Systemcall: '" << cmd << "' contains single quotes ', please check configuration, ' will be replaced by \"");
109 cmd = cmd.replace("'","\"");
112 SystemcallPrivate d(outfile);
116 if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
117 LYXERR0("Systemcall: '" << cmd << "' did not start!");
118 LYXERR0("error " << d.errorMessage());
122 if (how == DontWait) {
123 QProcess* released = d.releaseProcess();
124 (void) released; // TODO who deletes it?
128 if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
129 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
130 LYXERR0("error " << d.errorMessage());
131 LYXERR0("status " << d.exitStatusMessage());
135 int const exit_code = d.exitCode();
137 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
144 SystemcallPrivate::SystemcallPrivate(const std::string& of) :
145 proc_(new QProcess), outindex_(0), errindex_(0),
146 outfile(of), showout_(false), showerr_(false), process_events(false)
148 if (!outfile.empty()) {
149 // Check whether we have to simply throw away the output.
150 if (outfile != os::nulldev())
151 proc_->setStandardOutputFile(toqstr(outfile));
152 } else if (os::is_terminal(os::STDOUT))
154 if (os::is_terminal(os::STDERR))
157 connect(proc_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
158 connect(proc_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
159 connect(proc_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
160 connect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
161 connect(proc_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
166 void SystemcallPrivate::startProcess(const QString& cmd)
169 state = SystemcallPrivate::Starting;
175 void SystemcallPrivate::processEvents()
178 //static int count = 0; qDebug() << count++ << ": waitAndProcessEvents";
179 QCoreApplication::processEvents(QEventLoop::AllEvents);
184 void SystemcallPrivate::waitAndProcessEvents()
186 Sleep::millisec(100);
191 bool SystemcallPrivate::waitWhile(State waitwhile, bool proc_events, int timeout)
196 process_events = proc_events;
198 // Block GUI while waiting,
199 // relay on QProcess' wait functions
200 if (!process_events) {
201 if (waitwhile == Starting)
202 return proc_->waitForStarted(timeout);
203 if (waitwhile == Running)
204 return proc_->waitForFinished(timeout);
208 // process events while waiting, no timeout
210 while (state == waitwhile && state != Error) {
211 waitAndProcessEvents();
213 return state != Error;
216 // process events while waiting whith timeout
219 while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
220 waitAndProcessEvents();
222 return (state != Error) && (timer.elapsed() < timeout);
226 SystemcallPrivate::~SystemcallPrivate()
231 outdata_[outindex_] = '\0';
237 errdata_[errindex_] = '\0';
247 void SystemcallPrivate::flush()
250 // If the output has been redirected, we write it all at once.
251 // Even if we are not running in a terminal, the output could go
252 // to some log file, for example ~/.xsession-errors on *nix.
253 if (!os::is_terminal(os::STDOUT) && outfile.empty())
254 cout << fromqstr(QString::fromLocal8Bit(
255 proc_->readAllStandardOutput().data()));
256 if (!os::is_terminal(os::STDERR))
257 cerr << fromqstr(QString::fromLocal8Bit(
258 proc_->readAllStandardError().data()));
263 void SystemcallPrivate::stdOut()
265 if (proc_ && showout_) {
267 proc_->setReadChannel(QProcess::StandardOutput);
268 while (proc_->getChar(&c)) {
269 outdata_[outindex_++] = c;
270 if (c == '\n' || outindex_ + 1 == bufsize_) {
271 outdata_[outindex_] = '\0';
281 void SystemcallPrivate::stdErr()
283 if (proc_ && showerr_) {
285 proc_->setReadChannel(QProcess::StandardError);
286 while (proc_->getChar(&c)) {
287 errdata_[errindex_++] = c;
288 if (c == '\n' || errindex_ + 1 == bufsize_) {
289 errdata_[errindex_] = '\0';
299 void SystemcallPrivate::processStarted()
302 // why do we get two started signals?
303 //disconnect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
307 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
313 void SystemcallPrivate::processError(QProcess::ProcessError)
319 QString SystemcallPrivate::errorMessage() const
322 return "No QProcess available";
325 switch (proc_->error()) {
326 case QProcess::FailedToStart:
327 message = "The process failed to start. Either the invoked program is missing, "
328 "or you may have insufficient permissions to invoke the program.";
330 case QProcess::Crashed:
331 message = "The process crashed some time after starting successfully.";
333 case QProcess::Timedout:
334 message = "The process timed out. It might be restarted automatically.";
336 case QProcess::WriteError:
337 message = "An error occurred when attempting to write to the process-> For example, "
338 "the process may not be running, or it may have closed its input channel.";
340 case QProcess::ReadError:
341 message = "An error occurred when attempting to read from the process-> For example, "
342 "the process may not be running.";
344 case QProcess::UnknownError:
346 message = "An unknown error occured.";
353 QString SystemcallPrivate::exitStatusMessage() const
356 return "No QProcess available";
359 switch (proc_->exitStatus()) {
360 case QProcess::NormalExit:
361 message = "The process exited normally.";
363 case QProcess::CrashExit:
364 message = "The process crashed.";
367 message = "Unknown exit state.";
374 int SystemcallPrivate::exitCode()
379 return proc_->exitCode();
383 QProcess* SystemcallPrivate::releaseProcess()
385 QProcess* released = proc_;
391 void SystemcallPrivate::killProcess()
397 void SystemcallPrivate::killProcess(QProcess * p)
401 p->closeReadChannel(QProcess::StandardOutput);
402 p->closeReadChannel(QProcess::StandardError);
410 #include "moc_SystemcallPrivate.cpp"
413 } // namespace support