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"
22 #include "support/ProgressInterface.h"
31 #include <QCoreApplication>
37 struct Sleep : QThread
39 static void millisec(unsigned long ms)
41 QThread::usleep(ms * 1000);
54 class ProgressDummy : public ProgressInterface
59 void processStarted(QString const &) {}
60 void processFinished(QString const &) {}
61 void appendMessage(QString const &) {}
62 void appendError(QString const &) {}
63 void clearMessages() {}
65 void warning(QString const &, QString const &) {}
66 void toggleWarning(QString const &, QString const &, QString const &) {}
67 void error(QString const &, QString const &) {}
68 void information(QString const &, QString const &) {}
72 static ProgressInterface* progress_instance = 0;
74 void ProgressInterface::setInstance(ProgressInterface* p)
76 progress_instance = p;
80 ProgressInterface* ProgressInterface::instance()
82 if (!progress_instance) {
83 static ProgressDummy dummy;
86 return progress_instance;
94 int Systemcall::startscript(Starttype how, string const & what,
95 bool /*process_events*/)
97 string command = what;
99 if (how == DontWait) {
100 switch (os::shell()) {
105 command = "start /min " + command;
110 return ::system(command.c_str());
118 * This is a parser that (mostly) mimics the behavior of a posix shell but
119 * its output is tailored for being processed by QProcess.
121 * The escape character is the backslash.
122 * A backslash that is not quoted preserves the literal value of the following
123 * character, with the exception of a double-quote '"'. If a double-quote
124 * follows a backslash, it will be replaced by three consecutive double-quotes
125 * (this is how the QProcess parser recognizes a '"' as a simple character
126 * instead of a quoting character). Thus, for example:
132 * Characters enclosed in single-quotes ('') have their literal value preserved.
133 * A single-quote cannot occur within single-quotes. Indeed, a backslash cannot
134 * be used to escape a single-quote in a single-quoted string. In other words,
135 * anything enclosed in single-quotes is passed as is, but the single-quotes
136 * themselves are eliminated. Thus, for example:
143 * Characters enclosed in double-quotes ("") have their literal value preserved,
144 * with the exception of the backslash. The backslash retains its special
145 * meaning as an escape character only when followed by a double-quote.
146 * Contrarily to the behavior of a posix shell, the double-quotes themselves
147 * are *not* eliminated. Thus, for example:
152 string const parsecmd(string const & inputcmd, string & outfile)
154 bool in_single_quote = false;
155 bool in_double_quote = false;
156 bool escaped = false;
159 for (size_t i = 0; i < inputcmd.length(); ++i) {
160 char c = inputcmd[i];
162 if (in_double_quote || escaped) {
163 if (in_double_quote && escaped)
167 in_single_quote = !in_single_quote;
171 if (in_single_quote) {
181 in_double_quote = !in_double_quote;
183 } else if (c == '\\' && !escaped) {
185 } else if (c == '>' && !(in_double_quote || escaped)) {
186 outfile = trim(inputcmd.substr(i + 1), " \"");
189 if (escaped && in_double_quote)
203 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
206 QString cmd = toqstr(parsecmd(what, outfile));
208 SystemcallPrivate d(outfile);
212 if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
213 LYXERR0("Systemcall: '" << cmd << "' did not start!");
214 LYXERR0("error " << d.errorMessage());
218 if (how == DontWait) {
219 QProcess* released = d.releaseProcess();
220 (void) released; // TODO who deletes it?
224 if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
225 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
226 LYXERR0("error " << d.errorMessage());
227 LYXERR0("status " << d.exitStatusMessage());
231 int const exit_code = d.exitCode();
233 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
240 SystemcallPrivate::SystemcallPrivate(const std::string& of) :
241 proc_(new QProcess), outindex_(0), errindex_(0),
242 outfile(of), showout_(false), showerr_(false), process_events(false)
244 if (!outfile.empty()) {
245 // Check whether we have to simply throw away the output.
246 if (outfile != os::nulldev())
247 proc_->setStandardOutputFile(toqstr(outfile));
248 } else if (os::is_terminal(os::STDOUT))
250 if (os::is_terminal(os::STDERR))
253 connect(proc_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
254 connect(proc_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
255 connect(proc_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
256 connect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
257 connect(proc_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
262 void SystemcallPrivate::startProcess(const QString& cmd)
266 state = SystemcallPrivate::Starting;
272 void SystemcallPrivate::processEvents()
275 QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/);
280 void SystemcallPrivate::waitAndProcessEvents()
282 Sleep::millisec(100);
287 bool SystemcallPrivate::waitWhile(State waitwhile, bool proc_events, int timeout)
292 process_events = proc_events;
294 // Block GUI while waiting,
295 // relay on QProcess' wait functions
296 if (!process_events) {
297 if (waitwhile == Starting)
298 return proc_->waitForStarted(timeout);
299 if (waitwhile == Running)
300 return proc_->waitForFinished(timeout);
304 // process events while waiting, no timeout
306 while (state == waitwhile && state != Error) {
307 waitAndProcessEvents();
309 return state != Error;
312 // process events while waiting whith timeout
315 while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
316 waitAndProcessEvents();
318 return (state != Error) && (timer.elapsed() < timeout);
322 SystemcallPrivate::~SystemcallPrivate()
327 outdata_[outindex_] = '\0';
334 errdata_[errindex_] = '\0';
345 void SystemcallPrivate::flush()
348 // If the output has been redirected, we write it all at once.
349 // Even if we are not running in a terminal, the output could go
350 // to some log file, for example ~/.xsession-errors on *nix.
351 if (!os::is_terminal(os::STDOUT) && outfile.empty())
352 cout << fromqstr(QString::fromLocal8Bit(
353 proc_->readAllStandardOutput().data()));
354 if (!os::is_terminal(os::STDERR))
355 cerr << fromqstr(QString::fromLocal8Bit(
356 proc_->readAllStandardError().data()));
361 void SystemcallPrivate::stdOut()
365 proc_->setReadChannel(QProcess::StandardOutput);
366 while (proc_->getChar(&c)) {
367 outdata_[outindex_++] = c;
368 if (c == '\n' || outindex_ + 1 == bufsize_) {
369 outdata_[outindex_] = '\0';
376 const QString data = QString::fromLocal8Bit(outdata_);
377 if (!data.isEmpty()) {
378 // TODO No good messages from the processes. Disable batch mode?
379 //ProgressInterface::instance()->appendMessage(data);
385 void SystemcallPrivate::stdErr()
389 proc_->setReadChannel(QProcess::StandardError);
390 while (proc_->getChar(&c)) {
391 errdata_[errindex_++] = c;
392 if (c == '\n' || errindex_ + 1 == bufsize_) {
393 errdata_[errindex_] = '\0';
400 const QString data = QString::fromLocal8Bit(errdata_);
402 ProgressInterface::instance()->appendError(data);
407 void SystemcallPrivate::processStarted()
410 ProgressInterface::instance()->processStarted(cmd_);
414 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
417 ProgressInterface::instance()->processFinished(cmd_);
421 void SystemcallPrivate::processError(QProcess::ProcessError)
424 ProgressInterface::instance()->appendError(errorMessage());
428 QString SystemcallPrivate::errorMessage() const
431 return "No QProcess available";
434 switch (proc_->error()) {
435 case QProcess::FailedToStart:
436 message = "The process failed to start. Either the invoked program is missing, "
437 "or you may have insufficient permissions to invoke the program.";
439 case QProcess::Crashed:
440 message = "The process crashed some time after starting successfully.";
442 case QProcess::Timedout:
443 message = "The process timed out. It might be restarted automatically.";
445 case QProcess::WriteError:
446 message = "An error occurred when attempting to write to the process-> For example, "
447 "the process may not be running, or it may have closed its input channel.";
449 case QProcess::ReadError:
450 message = "An error occurred when attempting to read from the process-> For example, "
451 "the process may not be running.";
453 case QProcess::UnknownError:
455 message = "An unknown error occured.";
462 QString SystemcallPrivate::exitStatusMessage() const
465 return "No QProcess available";
468 switch (proc_->exitStatus()) {
469 case QProcess::NormalExit:
470 message = "The process exited normally.";
472 case QProcess::CrashExit:
473 message = "The process crashed.";
476 message = "Unknown exit state.";
483 int SystemcallPrivate::exitCode()
488 return proc_->exitCode();
492 QProcess* SystemcallPrivate::releaseProcess()
494 QProcess* released = proc_;
500 void SystemcallPrivate::killProcess()
506 void SystemcallPrivate::killProcess(QProcess * p)
510 p->closeReadChannel(QProcess::StandardOutput);
511 p->closeReadChannel(QProcess::StandardError);
519 #include "moc_SystemcallPrivate.cpp"
522 } // namespace support