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() {}
66 void lyxerrConnect() {}
67 void lyxerrDisconnect() {}
69 void warning(QString const &, QString const &) {}
70 void toggleWarning(QString const &, QString const &, QString const &) {}
71 void error(QString const &, QString const &) {}
72 void information(QString const &, QString const &) {}
76 static ProgressInterface* progress_instance = 0;
78 void ProgressInterface::setInstance(ProgressInterface* p)
80 progress_instance = p;
84 ProgressInterface* ProgressInterface::instance()
86 if (!progress_instance) {
87 static ProgressDummy dummy;
90 return progress_instance;
98 int Systemcall::startscript(Starttype how, string const & what,
99 bool /*process_events*/)
101 string command = what;
103 if (how == DontWait) {
104 switch (os::shell()) {
109 command = "start /min " + command;
114 return ::system(command.c_str());
122 * This is a parser that (mostly) mimics the behavior of a posix shell but
123 * its output is tailored for being processed by QProcess.
125 * The escape character is the backslash.
126 * A backslash that is not quoted preserves the literal value of the following
127 * character, with the exception of a double-quote '"'. If a double-quote
128 * follows a backslash, it will be replaced by three consecutive double-quotes
129 * (this is how the QProcess parser recognizes a '"' as a simple character
130 * instead of a quoting character). Thus, for example:
136 * Characters enclosed in single-quotes ('') have their literal value preserved.
137 * A single-quote cannot occur within single-quotes. Indeed, a backslash cannot
138 * be used to escape a single-quote in a single-quoted string. In other words,
139 * anything enclosed in single-quotes is passed as is, but the single-quotes
140 * themselves are eliminated. Thus, for example:
147 * Characters enclosed in double-quotes ("") have their literal value preserved,
148 * with the exception of the backslash. The backslash retains its special
149 * meaning as an escape character only when followed by a double-quote.
150 * Contrarily to the behavior of a posix shell, the double-quotes themselves
151 * are *not* eliminated. Thus, for example:
156 string const parsecmd(string const & inputcmd, string & outfile)
158 bool in_single_quote = false;
159 bool in_double_quote = false;
160 bool escaped = false;
163 for (size_t i = 0; i < inputcmd.length(); ++i) {
164 char c = inputcmd[i];
166 if (in_double_quote || escaped) {
167 if (in_double_quote && escaped)
171 in_single_quote = !in_single_quote;
175 if (in_single_quote) {
185 in_double_quote = !in_double_quote;
187 } else if (c == '\\' && !escaped) {
189 } else if (c == '>' && !(in_double_quote || escaped)) {
190 outfile = trim(inputcmd.substr(i + 1), " \"");
193 if (escaped && in_double_quote)
207 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
210 QString cmd = toqstr(parsecmd(what, outfile));
212 SystemcallPrivate d(outfile);
216 if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
217 LYXERR0("Systemcall: '" << cmd << "' did not start!");
218 LYXERR0("error " << d.errorMessage());
222 if (how == DontWait) {
223 QProcess* released = d.releaseProcess();
224 (void) released; // TODO who deletes it?
228 if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
229 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
230 LYXERR0("error " << d.errorMessage());
231 LYXERR0("status " << d.exitStatusMessage());
235 int const exit_code = d.exitCode();
237 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
244 SystemcallPrivate::SystemcallPrivate(const std::string& of) :
245 process_(new QProcess),
249 terminal_out_exists_(os::is_terminal(os::STDOUT)),
250 terminal_err_exists_(os::is_terminal(os::STDERR)),
251 process_events_(false)
253 if (!out_file_.empty()) {
254 // Check whether we have to simply throw away the output.
255 if (out_file_ != os::nulldev())
256 process_->setStandardOutputFile(toqstr(out_file_));
259 connect(process_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
260 connect(process_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
261 connect(process_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
262 connect(process_, SIGNAL(started()), this, SLOT(processStarted()));
263 connect(process_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
268 void SystemcallPrivate::startProcess(const QString& cmd)
272 state = SystemcallPrivate::Starting;
273 process_->start(cmd_);
278 void SystemcallPrivate::processEvents()
280 if(process_events_) {
281 QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/);
286 void SystemcallPrivate::waitAndProcessEvents()
288 Sleep::millisec(100);
293 bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int timeout)
298 process_events = process_events;
300 // Block GUI while waiting,
301 // relay on QProcess' wait functions
302 if (!process_events) {
303 if (waitwhile == Starting)
304 return process_->waitForStarted(timeout);
305 if (waitwhile == Running)
306 return process_->waitForFinished(timeout);
310 // process events while waiting, no timeout
312 while (state == waitwhile && state != Error) {
313 waitAndProcessEvents();
315 return state != Error;
318 // process events while waiting whith timeout
321 while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
322 waitAndProcessEvents();
324 return (state != Error) && (timer.elapsed() < timeout);
328 SystemcallPrivate::~SystemcallPrivate()
333 out_data_[out_index_] = '\0';
335 if (terminal_out_exists_)
340 err_data_[err_index_] = '\0';
342 if (terminal_err_exists_)
351 void SystemcallPrivate::flush()
354 // If the output has been redirected, we write it all at once.
355 // Even if we are not running in a terminal, the output could go
356 // to some log file, for example ~/.xsession-errors on *nix.
358 QString data = QString::fromLocal8Bit(process_->readAllStandardOutput().data());
359 ProgressInterface::instance()->appendMessage(data);
360 if (!terminal_out_exists_ && out_file_.empty())
361 cout << fromqstr(data);
363 data = QString::fromLocal8Bit(process_->readAllStandardError().data());
364 ProgressInterface::instance()->appendError(data);
365 if (!terminal_err_exists_)
366 cerr << fromqstr(data);
371 void SystemcallPrivate::stdOut()
375 process_->setReadChannel(QProcess::StandardOutput);
376 while (process_->getChar(&c)) {
377 out_data_[out_index_++] = c;
378 if (c == '\n' || out_index_ + 1 == max_buffer_size_) {
379 out_data_[out_index_] = '\0';
381 ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(out_data_));
382 if (terminal_out_exists_)
390 void SystemcallPrivate::stdErr()
394 process_->setReadChannel(QProcess::StandardError);
395 while (process_->getChar(&c)) {
396 err_data_[err_index_++] = c;
397 if (c == '\n' || err_index_ + 1 == max_buffer_size_) {
398 err_data_[err_index_] = '\0';
400 ProgressInterface::instance()->appendError(QString::fromLocal8Bit(err_data_));
401 if (terminal_err_exists_)
409 void SystemcallPrivate::processStarted()
411 if (state != Running) {
413 ProgressInterface::instance()->processStarted(cmd_);
418 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
420 if (state != Finished) {
422 ProgressInterface::instance()->processFinished(cmd_);
427 void SystemcallPrivate::processError(QProcess::ProcessError)
430 ProgressInterface::instance()->appendError(errorMessage());
434 QString SystemcallPrivate::errorMessage() const
437 return "No QProcess available";
440 switch (process_->error()) {
441 case QProcess::FailedToStart:
442 message = "The process failed to start. Either the invoked program is missing, "
443 "or you may have insufficient permissions to invoke the program.";
445 case QProcess::Crashed:
446 message = "The process crashed some time after starting successfully.";
448 case QProcess::Timedout:
449 message = "The process timed out. It might be restarted automatically.";
451 case QProcess::WriteError:
452 message = "An error occurred when attempting to write to the process-> For example, "
453 "the process may not be running, or it may have closed its input channel.";
455 case QProcess::ReadError:
456 message = "An error occurred when attempting to read from the process-> For example, "
457 "the process may not be running.";
459 case QProcess::UnknownError:
461 message = "An unknown error occured.";
468 QString SystemcallPrivate::exitStatusMessage() const
471 return "No QProcess available";
474 switch (process_->exitStatus()) {
475 case QProcess::NormalExit:
476 message = "The process exited normally.";
478 case QProcess::CrashExit:
479 message = "The process crashed.";
482 message = "Unknown exit state.";
489 int SystemcallPrivate::exitCode()
494 return process_->exitCode();
498 QProcess* SystemcallPrivate::releaseProcess()
500 QProcess* released = process_;
506 void SystemcallPrivate::killProcess()
508 killProcess(process_);
512 void SystemcallPrivate::killProcess(QProcess * p)
516 p->closeReadChannel(QProcess::StandardOutput);
517 p->closeReadChannel(QProcess::StandardError);
525 #include "moc_SystemcallPrivate.cpp"
528 } // namespace support