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());
79 * This is a parser that (mostly) mimics the behavior of a posix shell but
80 * its output is tailored for being processed by QProcess.
82 * The escape character is the backslash.
83 * A backslash that is not quoted preserves the literal value of the following
84 * character, with the exception of a double-quote '"'. If a double-quote
85 * follows a backslash, it will be replaced by three consecutive double-quotes
86 * (this is how the QProcess parser recognizes a '"' as a simple character
87 * instead of a quoting character). Thus, for example:
93 * Characters enclosed in single-quotes ('') have their literal value preserved.
94 * A single-quote cannot occur within single-quotes. Indeed, a backslash cannot
95 * be used to escape a single-quote in a single-quoted string. In other words,
96 * anything enclosed in single-quotes is passed as is, but the single-quotes
97 * themselves are eliminated. Thus, for example:
104 * Characters enclosed in double-quotes ("") have their literal value preserved,
105 * with the exception of the backslash. The backslash retains its special
106 * meaning as an escape character only when followed by a double-quote.
107 * Contrarily to the behavior of a posix shell, the double-quotes themselves
108 * are *not* eliminated. Thus, for example:
113 string const parsecmd(string const & inputcmd, string & outfile)
115 bool in_single_quote = false;
116 bool in_double_quote = false;
117 bool escaped = false;
120 for (size_t i = 0; i < inputcmd.length(); ++i) {
121 char c = inputcmd[i];
123 if (in_double_quote || escaped) {
124 if (in_double_quote && escaped)
128 in_single_quote = !in_single_quote;
132 if (in_single_quote) {
142 in_double_quote = !in_double_quote;
144 } else if (c == '\\' && !escaped) {
146 } else if (c == '>' && !(in_double_quote || escaped)) {
147 outfile = trim(inputcmd.substr(i + 1), " \"");
150 if (escaped && in_double_quote)
164 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
167 QString cmd = toqstr(parsecmd(what, outfile));
169 SystemcallPrivate d(outfile);
173 if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
174 LYXERR0("Systemcall: '" << cmd << "' did not start!");
175 LYXERR0("error " << d.errorMessage());
179 if (how == DontWait) {
180 QProcess* released = d.releaseProcess();
181 (void) released; // TODO who deletes it?
185 if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
186 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
187 LYXERR0("error " << d.errorMessage());
188 LYXERR0("status " << d.exitStatusMessage());
192 int const exit_code = d.exitCode();
194 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
201 SystemcallPrivate::SystemcallPrivate(const std::string& of) :
202 proc_(new QProcess), outindex_(0), errindex_(0),
203 outfile(of), showout_(false), showerr_(false), process_events(false)
205 if (!outfile.empty()) {
206 // Check whether we have to simply throw away the output.
207 if (outfile != os::nulldev())
208 proc_->setStandardOutputFile(toqstr(outfile));
209 } else if (os::is_terminal(os::STDOUT))
211 if (os::is_terminal(os::STDERR))
214 connect(proc_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
215 connect(proc_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
216 connect(proc_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
217 connect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
218 connect(proc_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
223 void SystemcallPrivate::startProcess(const QString& cmd)
226 state = SystemcallPrivate::Starting;
232 void SystemcallPrivate::processEvents()
235 //static int count = 0; qDebug() << count++ << ": waitAndProcessEvents";
236 QCoreApplication::processEvents(QEventLoop::AllEvents);
241 void SystemcallPrivate::waitAndProcessEvents()
243 Sleep::millisec(100);
248 bool SystemcallPrivate::waitWhile(State waitwhile, bool proc_events, int timeout)
253 process_events = proc_events;
255 // Block GUI while waiting,
256 // relay on QProcess' wait functions
257 if (!process_events) {
258 if (waitwhile == Starting)
259 return proc_->waitForStarted(timeout);
260 if (waitwhile == Running)
261 return proc_->waitForFinished(timeout);
265 // process events while waiting, no timeout
267 while (state == waitwhile && state != Error) {
268 waitAndProcessEvents();
270 return state != Error;
273 // process events while waiting whith timeout
276 while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
277 waitAndProcessEvents();
279 return (state != Error) && (timer.elapsed() < timeout);
283 SystemcallPrivate::~SystemcallPrivate()
288 outdata_[outindex_] = '\0';
294 errdata_[errindex_] = '\0';
304 void SystemcallPrivate::flush()
307 // If the output has been redirected, we write it all at once.
308 // Even if we are not running in a terminal, the output could go
309 // to some log file, for example ~/.xsession-errors on *nix.
310 if (!os::is_terminal(os::STDOUT) && outfile.empty())
311 cout << fromqstr(QString::fromLocal8Bit(
312 proc_->readAllStandardOutput().data()));
313 if (!os::is_terminal(os::STDERR))
314 cerr << fromqstr(QString::fromLocal8Bit(
315 proc_->readAllStandardError().data()));
320 void SystemcallPrivate::stdOut()
322 if (proc_ && showout_) {
324 proc_->setReadChannel(QProcess::StandardOutput);
325 while (proc_->getChar(&c)) {
326 outdata_[outindex_++] = c;
327 if (c == '\n' || outindex_ + 1 == bufsize_) {
328 outdata_[outindex_] = '\0';
338 void SystemcallPrivate::stdErr()
340 if (proc_ && showerr_) {
342 proc_->setReadChannel(QProcess::StandardError);
343 while (proc_->getChar(&c)) {
344 errdata_[errindex_++] = c;
345 if (c == '\n' || errindex_ + 1 == bufsize_) {
346 errdata_[errindex_] = '\0';
356 void SystemcallPrivate::processStarted()
359 // why do we get two started signals?
360 //disconnect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
364 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
370 void SystemcallPrivate::processError(QProcess::ProcessError)
376 QString SystemcallPrivate::errorMessage() const
379 return "No QProcess available";
382 switch (proc_->error()) {
383 case QProcess::FailedToStart:
384 message = "The process failed to start. Either the invoked program is missing, "
385 "or you may have insufficient permissions to invoke the program.";
387 case QProcess::Crashed:
388 message = "The process crashed some time after starting successfully.";
390 case QProcess::Timedout:
391 message = "The process timed out. It might be restarted automatically.";
393 case QProcess::WriteError:
394 message = "An error occurred when attempting to write to the process-> For example, "
395 "the process may not be running, or it may have closed its input channel.";
397 case QProcess::ReadError:
398 message = "An error occurred when attempting to read from the process-> For example, "
399 "the process may not be running.";
401 case QProcess::UnknownError:
403 message = "An unknown error occured.";
410 QString SystemcallPrivate::exitStatusMessage() const
413 return "No QProcess available";
416 switch (proc_->exitStatus()) {
417 case QProcess::NormalExit:
418 message = "The process exited normally.";
420 case QProcess::CrashExit:
421 message = "The process crashed.";
424 message = "Unknown exit state.";
431 int SystemcallPrivate::exitCode()
436 return proc_->exitCode();
440 QProcess* SystemcallPrivate::releaseProcess()
442 QProcess* released = proc_;
448 void SystemcallPrivate::killProcess()
454 void SystemcallPrivate::killProcess(QProcess * p)
458 p->closeReadChannel(QProcess::StandardOutput);
459 p->closeReadChannel(QProcess::StandardError);
467 #include "moc_SystemcallPrivate.cpp"
470 } // namespace support