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/filetools.h"
18 #include "support/gettext.h"
19 #include "support/lstrings.h"
20 #include "support/qstring_helpers.h"
21 #include "support/Systemcall.h"
22 #include "support/SystemcallPrivate.h"
23 #include "support/os.h"
24 #include "support/ProgressInterface.h"
26 #include "frontends/Application.h"
37 #include <QCoreApplication>
43 struct Sleep : QThread
45 static void millisec(unsigned long ms)
47 QThread::usleep(ms * 1000);
60 class ProgressDummy : public ProgressInterface
65 void processStarted(QString const &) {}
66 void processFinished(QString const &) {}
67 void appendMessage(QString const &) {}
68 void appendError(QString const &) {}
69 void clearMessages() {}
72 void lyxerrConnect() {}
73 void lyxerrDisconnect() {}
75 void warning(QString const &, QString const &) {}
76 void toggleWarning(QString const &, QString const &, QString const &) {}
77 void error(QString const &, QString const &, QString const &) {}
78 void information(QString const &, QString const &) {}
79 int prompt(docstring const &, docstring const &, int default_but, int,
80 docstring const &, docstring const &) { return default_but; }
84 static ProgressInterface * progress_instance = 0;
86 void ProgressInterface::setInstance(ProgressInterface* p)
88 progress_instance = p;
92 ProgressInterface * ProgressInterface::instance()
94 if (!progress_instance) {
95 static ProgressDummy dummy;
98 return progress_instance;
106 int Systemcall::startscript(Starttype how, string const & what,
107 string const & path, string const & lpath,
108 bool /*process_events*/)
111 to_filesystem8bit(from_utf8(latexEnvCmdPrefix(path, lpath)))
114 if (how == DontWait) {
115 switch (os::shell()) {
120 command = "start /min " + command;
123 } else if (os::shell() == os::CMD_EXE)
124 command = subst(command, "cmd /d /c ", "");
126 return ::system(command.c_str());
134 * This is a parser that (mostly) mimics the behavior of a posix shell as
135 * regards quoting, but its output is tailored for being processed by QProcess.
136 * Note that shell metacharacters are not parsed.
138 * The escape character is the backslash.
139 * A backslash that is not quoted preserves the literal value of the following
140 * character, with the exception of a double-quote '"'. If a double-quote
141 * follows a backslash, it will be replaced by three consecutive double-quotes
142 * (this is how the QProcess parser recognizes a '"' as a simple character
143 * instead of a quoting character). Thus, for example:
149 * Characters enclosed in single-quotes ('') have their literal value preserved.
150 * A single-quote cannot occur within single-quotes. Indeed, a backslash cannot
151 * be used to escape a single-quote in a single-quoted string. In other words,
152 * anything enclosed in single-quotes is passed as is, but the single-quotes
153 * themselves are eliminated. Thus, for example:
160 * Characters enclosed in double-quotes ("") have their literal value preserved,
161 * with the exception of the backslash. The backslash retains its special
162 * meaning as an escape character only when followed by a double-quote.
163 * Contrarily to the behavior of a posix shell, the double-quotes themselves
164 * are *not* eliminated. Thus, for example:
169 string const parsecmd(string const & incmd, string & infile, string & outfile,
172 bool in_single_quote = false;
173 bool in_double_quote = false;
174 bool escaped = false;
175 string const python_call = "python -tt";
176 vector<string> outcmd(4);
179 if (prefixIs(incmd, python_call)) {
180 outcmd[0] = os::python();
181 start = python_call.length();
184 for (size_t i = start, o = 0; i < incmd.length(); ++i) {
187 if (in_double_quote || escaped) {
188 if (in_double_quote && escaped)
192 in_single_quote = !in_single_quote;
196 if (in_single_quote) {
202 // Don't triple double-quotes for redirection
203 // files as these won't be parsed by QProcess
204 outcmd[o] += string(o ? "\"" : "\"\"\"");
208 in_double_quote = !in_double_quote;
210 } else if (c == '\\' && !escaped) {
212 } else if (c == '>' && !(in_double_quote || escaped)) {
213 if (suffixIs(outcmd[o], " 2")) {
214 outcmd[o] = rtrim(outcmd[o], "2");
217 if (suffixIs(outcmd[o], " 1"))
218 outcmd[o] = rtrim(outcmd[o], "1");
221 } else if (c == '<' && !(in_double_quote || escaped)) {
224 if (escaped && in_double_quote)
230 infile = trim(outcmd[3], " \"");
231 outfile = trim(outcmd[1], " \"");
232 errfile = trim(outcmd[2], " \"");
233 return trim(outcmd[0]);
239 int Systemcall::startscript(Starttype how, string const & what,
240 string const & path, string const & lpath,
243 string const what_ss = commandPrep(what);
245 lyxerr << "\nRunning: " << what_ss << endl;
247 LYXERR(Debug::INFO,"Running: " << what_ss);
252 QString const cmd = QString::fromLocal8Bit(
253 parsecmd(what_ss, infile, outfile, errfile).c_str());
255 SystemcallPrivate d(infile, outfile, errfile);
256 bool do_events = process_events || how == WaitLoop;
259 // QProcess::startDetached cannot provide environment variables. When the
260 // environment variables are set using the latexEnvCmdPrefix and the process
261 // is started with QProcess::startDetached, a console window is shown every
262 // time a viewer is started. To avoid this, we fall back on Windows to the
263 // original implementation that creates a QProcess object.
264 d.startProcess(cmd, path, lpath, false);
265 if (!d.waitWhile(SystemcallPrivate::Starting, do_events, -1)) {
266 if (d.state == SystemcallPrivate::Error) {
267 LYXERR0("Systemcall: '" << cmd << "' did not start!");
268 LYXERR0("error " << d.errorMessage());
270 } else if (d.state == SystemcallPrivate::Killed) {
271 LYXERR0("Killed: " << cmd);
275 if (how == DontWait) {
280 d.startProcess(cmd, path, lpath, how == DontWait);
281 if (how == DontWait && d.state == SystemcallPrivate::Running)
284 if (d.state == SystemcallPrivate::Error
285 || !d.waitWhile(SystemcallPrivate::Starting, do_events, -1)) {
286 if (d.state == SystemcallPrivate::Error) {
287 LYXERR0("Systemcall: '" << cmd << "' did not start!");
288 LYXERR0("error " << d.errorMessage());
290 } else if (d.state == SystemcallPrivate::Killed) {
291 LYXERR0("Killed: " << cmd);
297 if (!d.waitWhile(SystemcallPrivate::Running, do_events,
298 os::timeout_min() * 60 * 1000)) {
299 if (d.state == SystemcallPrivate::Killed) {
300 LYXERR0("Killed: " << cmd);
303 LYXERR0("Systemcall: '" << cmd << "' did not finish!");
304 LYXERR0("error " << d.errorMessage());
305 LYXERR0("status " << d.exitStatusMessage());
309 int const exit_code = d.exitCode();
311 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
318 SystemcallPrivate::SystemcallPrivate(std::string const & sf, std::string const & of,
319 std::string const & ef)
320 : state(Error), process_(new QProcess), out_index_(0), err_index_(0),
321 in_file_(sf), out_file_(of), err_file_(ef), process_events_(false)
323 if (!in_file_.empty())
324 process_->setStandardInputFile(QString::fromLocal8Bit(in_file_.c_str()));
325 if (!out_file_.empty()) {
326 if (out_file_[0] == '&') {
327 if (subst(out_file_, " ", "") == "&2"
328 && err_file_[0] != '&') {
329 out_file_ = err_file_;
330 process_->setProcessChannelMode(
331 QProcess::MergedChannels);
333 if (err_file_[0] == '&') {
334 // Leave alone things such as
335 // "1>&2 2>&1". Should not be harmful,
336 // but let's give anyway a warning.
337 LYXERR0("Unsupported stdout/stderr redirect.");
340 LYXERR0("Ambiguous stdout redirect: "
343 out_file_ = os::nulldev();
346 // Check whether we have to set the output file.
347 if (out_file_ != os::nulldev()) {
348 process_->setStandardOutputFile(QString::fromLocal8Bit(
352 if (!err_file_.empty()) {
353 if (err_file_[0] == '&') {
354 if (subst(err_file_, " ", "") == "&1"
355 && out_file_[0] != '&') {
356 process_->setProcessChannelMode(
357 QProcess::MergedChannels);
359 LYXERR0("Ambiguous stderr redirect: "
362 // In MergedChannels mode stderr goes to stdout.
363 err_file_ = os::nulldev();
365 // Check whether we have to set the error file.
366 if (err_file_ != os::nulldev()) {
367 process_->setStandardErrorFile(QString::fromLocal8Bit(
372 connect(process_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
373 connect(process_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
374 #if QT_VERSION >= 0x050600
375 connect(process_, SIGNAL(errorOccurred(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
377 connect(process_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
379 connect(process_, SIGNAL(started()), this, SLOT(processStarted()));
380 connect(process_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
384 void SystemcallPrivate::startProcess(QString const & cmd, string const & path,
385 string const & lpath, bool detached)
389 state = SystemcallPrivate::Running;
390 if (!QProcess::startDetached(toqstr(latexEnvCmdPrefix(path, lpath)) + cmd_)) {
391 state = SystemcallPrivate::Error;
394 QProcess* released = releaseProcess();
396 } else if (process_) {
397 state = SystemcallPrivate::Starting;
398 process_->start(toqstr(latexEnvCmdPrefix(path, lpath)) + cmd_);
403 bool SystemcallPrivate::waitAndCheck()
405 Sleep::millisec(100);
406 if (theApp() && theApp()->cancel_export) {
407 // is there a better place to reset this?
410 theApp()->cancel_export = false;
411 LYXERR0("Export Canceled!!");
414 QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/);
421 bool queryStopCommand(QString const & cmd)
423 docstring text = bformat(_(
424 "The command\n%1$s\nhas not yet completed.\n\n"
425 "Do you want to stop it?"), qstring_to_ucs4(cmd));
426 return ProgressInterface::instance()->prompt(_("Stop command?"), text,
427 1, 1, _("&Stop it"), _("Let it &run")) == 0;
433 bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int timeout)
438 bool timedout = false;
439 process_events_ = process_events;
441 // Block GUI while waiting,
442 // relay on QProcess' wait functions
443 if (!process_events_) {
444 if (waitwhile == Starting)
445 return process_->waitForStarted(timeout);
446 if (waitwhile == Running) {
449 if (process_->waitForFinished(timeout))
451 bool stop = queryStopCommand(cmd_);
452 // The command may have finished in the meantime
453 if (process_->state() == QProcess::NotRunning)
467 // process events while waiting, no timeout
469 while (state == waitwhile && state != Error) {
470 // check for cancellation of background process
474 return state != Error;
477 // process events while waiting with timeout
480 while (state == waitwhile && state != Error && !timedout) {
481 // check for cancellation of background process
485 if (timer.elapsed() > timeout) {
486 bool stop = queryStopCommand(cmd_);
487 // The command may have finished in the meantime
488 if (process_->state() == QProcess::NotRunning)
497 return (state != Error) && !timedout;
501 SystemcallPrivate::~SystemcallPrivate()
504 out_data_[out_index_] = '\0';
510 err_data_[err_index_] = '\0';
520 void SystemcallPrivate::stdOut()
524 process_->setReadChannel(QProcess::StandardOutput);
525 while (process_->getChar(&c)) {
526 out_data_[out_index_++] = c;
527 if (c == '\n' || out_index_ + 1 == buffer_size_) {
528 out_data_[out_index_] = '\0';
530 ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(out_data_));
538 void SystemcallPrivate::stdErr()
542 process_->setReadChannel(QProcess::StandardError);
543 while (process_->getChar(&c)) {
544 err_data_[err_index_++] = c;
545 if (c == '\n' || err_index_ + 1 == buffer_size_) {
546 err_data_[err_index_] = '\0';
548 ProgressInterface::instance()->appendError(QString::fromLocal8Bit(err_data_));
556 void SystemcallPrivate::processStarted()
558 if (state != Running) {
560 ProgressInterface::instance()->processStarted(cmd_);
565 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
567 if (state != Finished) {
569 ProgressInterface::instance()->processFinished(cmd_);
574 void SystemcallPrivate::processError(QProcess::ProcessError)
577 ProgressInterface::instance()->appendError(errorMessage());
581 QString SystemcallPrivate::errorMessage() const
584 return "No QProcess available";
587 switch (process_->error()) {
588 case QProcess::FailedToStart:
589 message = "The process failed to start. Either the invoked program is missing, "
590 "or you may have insufficient permissions to invoke the program.";
592 case QProcess::Crashed:
593 message = "The process crashed some time after starting successfully.";
595 case QProcess::Timedout:
596 message = "The process timed out. It might be restarted automatically.";
598 case QProcess::WriteError:
599 message = "An error occurred when attempting to write to the process-> For example, "
600 "the process may not be running, or it may have closed its input channel.";
602 case QProcess::ReadError:
603 message = "An error occurred when attempting to read from the process-> For example, "
604 "the process may not be running.";
606 case QProcess::UnknownError:
608 message = "An unknown error occurred.";
615 QString SystemcallPrivate::exitStatusMessage() const
618 return "No QProcess available";
621 switch (process_->exitStatus()) {
622 case QProcess::NormalExit:
623 message = "The process exited normally.";
625 case QProcess::CrashExit:
626 message = "The process crashed.";
629 message = "Unknown exit state.";
636 int SystemcallPrivate::exitCode()
638 // From Qt's documentation, in regards to QProcess::exitCode(),
639 // "This value is not valid unless exitStatus() returns NormalExit"
640 if (!process_ || process_->exitStatus() != QProcess::NormalExit)
643 return process_->exitCode();
647 QProcess* SystemcallPrivate::releaseProcess()
649 QProcess* released = process_;
655 void SystemcallPrivate::killProcess()
657 killProcess(process_);
661 void SystemcallPrivate::killProcess(QProcess * p)
665 p->closeReadChannel(QProcess::StandardOutput);
666 p->closeReadChannel(QProcess::StandardError);
674 #include "moc_SystemcallPrivate.cpp"
677 } // namespace support