]> git.lyx.org Git - lyx.git/commitdiff
The shell escape patch
authorEnrico Forestieri <forenr@lyx.org>
Thu, 3 Aug 2017 11:07:41 +0000 (13:07 +0200)
committerEnrico Forestieri <forenr@lyx.org>
Thu, 3 Aug 2017 11:07:41 +0000 (13:07 +0200)
Allow a LaTeX backend to run external commands after user confirmation.
This is a per document and per machine setting. The authorization has
to be given through the document settings pane, but is not recorded in
the document itself. Moving the document to either another computer or
another directory on the same computer revokes the authorization.
This can also be done by right clicking the red icon that appears in
the status bar when a document is marked as one requiring shell escape.
The patch also checks whether the user has added the -shell-escape
option to a LaTeX converter and nags the user to remove the option
(which would be used for all documents) in favor of the (per document)
support offered by LyX.

14 files changed:
src/Buffer.cpp
src/BufferParams.cpp
src/BufferParams.h
src/Converter.cpp
src/Converter.h
src/Session.cpp
src/Session.h
src/frontends/qt4/GuiDocument.cpp
src/frontends/qt4/GuiDocument.h
src/frontends/qt4/GuiView.cpp
src/frontends/qt4/GuiView.h
src/frontends/qt4/GuiWorkArea.cpp
src/frontends/qt4/GuiWorkArea_Private.h
src/frontends/qt4/ui/OutputUi.ui

index a9cc619060193b58232cf8d64d25a0a632f7c9d3..d7e827fc2aa05f427d0d187b744ec383e66801d6 100644 (file)
@@ -55,6 +55,7 @@
 #include "ParagraphParameters.h"
 #include "ParIterator.h"
 #include "PDFOptions.h"
+#include "Session.h"
 #include "SpellChecker.h"
 #include "sgml.h"
 #include "texstream.h"
@@ -980,6 +981,8 @@ int Buffer::readHeader(Lexer & lex)
                errorList.push_back(ErrorItem(_("Document header error"), s));
        }
 
+       params().shell_escape = theSession().shellescapeFiles().find(absFileName());
+
        params().makeDocumentClass();
 
        return unknown_tokens;
index 22e84778fe0313f3fc5c6e257e6dc217bb868895..c16c308c216b1f8195bb5a3175f8418346e5a368 100644 (file)
@@ -459,6 +459,7 @@ BufferParams::BufferParams()
        html_css_as_file = false;
        display_pixel_ratio = 1.0;
 
+       shell_escape = false;
        output_sync = false;
        use_refstyle = true;
        use_minted = false;
index c20601e2acfd11f7885732ff5be6dcd103d7ef8f..bc5c10d194a9c70d9af0b7ed1b8beca8817a7230 100644 (file)
@@ -535,6 +535,8 @@ public:
        std::string html_latex_end;
        ///
        bool html_css_as_file;
+       /// allow the LaTeX backend to run external programs
+       bool shell_escape;
        /// generate output usable for reverse/forward search
        bool output_sync;
        /// custom LaTeX macro from user instead our own
index 66a71c26f349935d8ec1cf348627dcf653ed1388..997789bcda3a7e37329cc2e95a196601ad96291a 100644 (file)
@@ -283,20 +283,52 @@ OutputParams::FLAVOR Converters::getFlavor(Graph::EdgePath const & path,
 }
 
 
-bool Converters::checkAuth(Converter const & conv, string const & doc_fname)
+bool Converters::checkAuth(Converter const & conv, string const & doc_fname,
+                          bool use_shell_escape)
 {
-       if (!conv.need_auth())
+       string conv_command = conv.command();
+       bool const has_shell_escape = contains(conv_command, "-shell-escape")
+                               || contains(conv_command, "-enable-write18");
+       if (conv.latex() && has_shell_escape && !use_shell_escape) {
+               docstring const shellescape_warning =
+                     bformat(_("<p>The following LaTeX backend has been "
+                       "configured to allow execution of external programs "
+                       "for any document:</p>"
+                       "<center><p><tt>%1$s</tt></p></center>"
+                       "<p>This is a dangerous configuration. Please, "
+                       "consider using the support offered by LyX for "
+                       "allowing this privilege only to documents that "
+                       "actually need it, instead.</p>"),
+                       from_utf8(conv_command));
+               frontend::Alert::error(_("Security Warning"),
+                                       shellescape_warning , false);
+       } else if (!conv.latex())
+               use_shell_escape = false;
+       if (!conv.need_auth() && !use_shell_escape)
                return true;
-       const docstring security_warning = bformat(
-             _("<p>The requested operation requires the use of a converter from "
-               "%2$s to %3$s:</p>"
+       size_t const token_pos = conv_command.find("$$");
+       bool const has_token = token_pos != string::npos;
+       string const command = use_shell_escape && !has_shell_escape
+               ? (has_token ? conv_command.insert(token_pos, "-shell-escape ")
+                            : conv_command.append(" -shell-escape"))
+               : conv_command;
+       docstring const security_warning = (use_shell_escape
+           ? bformat(_("<p>The following LaTeX backend has been requested "
+               "to allow execution of external programs:</p>"
+               "<center><p><tt>%1$s</tt></p></center>"
+               "<p>The external programs can execute arbitrary commands on "
+               "your system, including dangerous ones, if instructed to do "
+               "so by a maliciously crafted LyX document.</p>"),
+             from_utf8(command))
+           : bformat(_("<p>The requested operation requires the use of a "
+               "converter from %2$s to %3$s:</p>"
                "<blockquote><p><tt>%1$s</tt></p></blockquote>"
-               "<p>This external program can execute arbitrary commands on your "
-               "system, including dangerous ones, if instructed to do so by a "
-               "maliciously crafted .lyx document.</p>"),
-             from_utf8(conv.command()), from_utf8(conv.from()),
-             from_utf8(conv.to()));
-       if (lyxrc.use_converter_needauth_forbidden) {
+               "<p>This external program can execute arbitrary commands on "
+               "your system, including dangerous ones, if instructed to do "
+               "so by a maliciously crafted LyX document.</p>"),
+             from_utf8(command), from_utf8(conv.from()),
+             from_utf8(conv.to())));
+       if (lyxrc.use_converter_needauth_forbidden && !use_shell_escape) {
                frontend::Alert::error(
                    _("An external converter is disabled for security reasons"),
                    security_warning + _(
@@ -306,29 +338,47 @@ bool Converters::checkAuth(Converter const & conv, string const & doc_fname)
                    "Forbid needauth converters</i>.)"), false);
                return false;
        }
-       if (!lyxrc.use_converter_needauth)
+       if (!lyxrc.use_converter_needauth && !use_shell_escape)
                return true;
-       static const docstring security_title =
-               _("An external converter requires your authorization");
+       docstring const security_title = use_shell_escape
+               ? _("A LaTeX backend requires your authorization")
+               : _("An external converter requires your authorization");
        int choice;
-       const docstring security_warning2 = security_warning +
-               _("<p>Would you like to run this converter?</p>"
-                 "<p><b>Only run if you trust the origin/sender of the LyX "
-                 "document!</b></p>");
+       docstring const security_warning2 = security_warning + (use_shell_escape
+               ? _("<p>Should LaTeX backends be allowed to run external "
+                   "programs?</p><p><b>Allow them only if you trust the "
+                   "origin/sender of the LyX document!</b></p>")
+               : _("<p>Would you like to run this converter?</p>"
+                   "<p><b>Only run if you trust the origin/sender of the LyX "
+                   "document!</b></p>"));
+       docstring const no = use_shell_escape
+                               ? _("Do &not allow") : _("Do &not run");
+       docstring const yes = use_shell_escape ? _("A&llow") : _("&Run");
+       docstring const always = use_shell_escape
+                                       ? _("&Always allow for this document")
+                                       : _("&Always run for this document");
        if (!doc_fname.empty()) {
                LYXERR(Debug::FILES, "looking up: " << doc_fname);
-               std::set<std::string> & auth_files = theSession().authFiles().authFiles();
-               if (auth_files.find(doc_fname) == auth_files.end()) {
-                       choice = frontend::Alert::prompt(security_title, security_warning2,
-                               0, 0, _("Do &not run"), _("&Run"), _("&Always run for this document"));
-                       if (choice == 2)
-                               auth_files.insert(doc_fname);
+               bool authorized = use_shell_escape
+                       ? theSession().shellescapeFiles().findAuth(doc_fname)
+                       : theSession().authFiles().find(doc_fname);
+               if (!authorized) {
+                       choice = frontend::Alert::prompt(security_title,
+                                                        security_warning2,
+                                                        0, 0, no, yes, always);
+                       if (choice == 2) {
+                               if (use_shell_escape)
+                                       theSession().shellescapeFiles().insert(doc_fname, true);
+                               else
+                                       theSession().authFiles().insert(doc_fname);
+                       }
                } else {
                        choice = 1;
                }
        } else {
-               choice = frontend::Alert::prompt(security_title, security_warning2,
-                       0, 0, _("Do &not run"), _("&Run"));
+               choice = frontend::Alert::prompt(security_title,
+                                                security_warning2,
+                                                0, 0, no, yes);
        }
        return choice != 0;
 }
@@ -497,7 +547,8 @@ bool Converters::convert(Buffer const * buffer,
                        }
                }
 
-               if (!checkAuth(conv, buffer ? buffer->absFileName() : string()))
+               if (!checkAuth(conv, buffer ? buffer->absFileName() : string(),
+                              buffer && buffer->params().shell_escape))
                        return false;
 
                if (conv.latex()) {
@@ -508,6 +559,9 @@ bool Converters::convert(Buffer const * buffer,
                        command = subst(command, token_from, "");
                        command = subst(command, token_latex_encoding,
                                        buffer->params().encoding().latexName());
+                       if (buffer->params().shell_escape
+                           && !contains(command, "-shell-escape"))
+                               command += " -shell-escape ";
                        LYXERR(Debug::FILES, "Running " << command);
                        if (!runLaTeX(*buffer, command, runparams, errorList))
                                return false;
index da3136a69c69c3abcae42bb4cbbdfdf7ec117592..8f63aba5124f4f08cc33a87519299029ed514d2f 100644 (file)
@@ -193,8 +193,14 @@ public:
        /// able to execute arbitrary code, tagged with the 'needauth' option,
        /// authorization is: always denied if lyxrc.use_converter_needauth_forbidden
        /// is enabled; always allowed if the lyxrc.use_converter_needauth
-       /// is disabled; user is prompted otherwise
-       bool checkAuth(Converter const & conv, std::string const & doc_fname);
+       /// is disabled; user is prompted otherwise.
+       /// However, if use_shell_escape is true and a LaTeX backend is
+       /// going to be executed, both lyxrc.use_converter_needauth and
+       /// lyxrc.use_converter_needauth_forbidden are ignored, because in
+       /// this case the backend has to be executed and LyX will add the
+       /// -shell-escape option, so that user consent is always needed.
+       bool checkAuth(Converter const & conv, std::string const & doc_fname,
+                      bool use_shell_escape = false);
 
 private:
        ///
index f2cb8b4ca0909adbb3f06db4b83dbab572259b72..009bf39cf1ed2bdbae82bbc76903cfa3665a6335 100644 (file)
@@ -35,6 +35,7 @@ string const sec_session = "[session info]";
 string const sec_toolbars = "[toolbars]";
 string const sec_lastcommands = "[last commands]";
 string const sec_authfiles = "[auth files]";
+string const sec_shellescape = "[shell escape files]";
 
 } // namespace
 
@@ -422,6 +423,8 @@ void Session::readFile()
                        lastCommands().read(is);
                else if (tmp == sec_authfiles)
                        authFiles().read(is);
+               else if (tmp == sec_shellescape)
+                       shellescapeFiles().read(is);
 
                else
                        LYXERR(Debug::INIT, "LyX: Warning: unknown Session section: " << tmp);
@@ -442,6 +445,7 @@ void Session::writeFile() const
                lastCommands().write(os);
                bookmarks().write(os);
                authFiles().write(os);
+               shellescapeFiles().write(os);
        } else
                LYXERR(Debug::INIT, "LyX: Warning: unable to save Session: "
                       << session_file);
@@ -480,4 +484,97 @@ void AuthFilesSection::write(ostream & os) const
 }
 
 
+bool AuthFilesSection::find(string const & name) const
+{
+       if (auth_files_.find(name) != auth_files_.end())
+               return true;
+
+       return false;
+}
+
+
+void AuthFilesSection::insert(string const & name)
+{
+       auth_files_.insert(name);
+}
+
+
+void ShellEscapeSection::read(istream & is)
+{
+       string s;
+       do {
+               char c = is.peek();
+               if (c == '[')
+                       break;
+               getline(is, s);
+               c = s[0];
+               if (c == 0 || c == '#' || c == ' ' || !FileName::isAbsolute(s))
+                       continue;
+
+               // read shellescape files
+               FileName const file(s.substr(0, s.length() - 2));
+               if (file.exists() && !file.isDirectory())
+                       shellescape_files_.insert(s);
+               else
+                       LYXERR(Debug::INIT, "LyX: Warning: Ignore shellescape file: " << file);
+       } while (is.good());
+}
+
+
+void ShellEscapeSection::write(ostream & os) const
+{
+       os << '\n' << sec_shellescape << '\n';
+       copy(shellescape_files_.begin(), shellescape_files_.end(),
+            ostream_iterator<std::string>(os, "\n"));
+}
+
+
+bool ShellEscapeSection::find(string const & name) const
+{
+       if (shellescape_files_.find(name + ",0") != shellescape_files_.end())
+               return true;
+
+       return findAuth(name);
+}
+
+
+bool ShellEscapeSection::findAuth(string const & name) const
+{
+       if (shellescape_files_.find(name + ",1") != shellescape_files_.end())
+               return true;
+
+       return false;
+}
+
+
+void ShellEscapeSection::insert(string const & name, bool auth)
+{
+       set<string>::iterator it;
+       string const name0 = name + ",0";
+       string const name1 = name + ",1";
+
+       if (auth) {
+               it = shellescape_files_.find(name0);
+               if (it != shellescape_files_.end())
+                       shellescape_files_.erase(it);
+               shellescape_files_.insert(name1);
+       } else {
+               it = shellescape_files_.find(name1);
+               if (it != shellescape_files_.end())
+                       shellescape_files_.erase(it);
+               shellescape_files_.insert(name0);
+       }
+}
+
+
+void ShellEscapeSection::remove(string const & name)
+{
+       set<string>::iterator it = shellescape_files_.find(name + ",0");
+       if (it == shellescape_files_.end())
+               it = shellescape_files_.find(name + ",1");
+       if (it != shellescape_files_.end())
+               shellescape_files_.erase(it);
+}
+
+
 } // namespace lyx
index 8074b56dd9128ce7712f693b02e34ef6ebf761a2..31ffee8841bab4cb2ab98945058140ddbba2c8e9 100644 (file)
@@ -333,7 +333,10 @@ public:
        void write(std::ostream & os) const;
 
        ///
-       std::set<std::string> & authFiles() { return auth_files_; }
+       bool find(std::string const & name) const;
+
+       ///
+       void insert(std::string const & name);
 
 private:
        /// set of document files authorized for external conversion
@@ -341,6 +344,36 @@ private:
 };
 
 
+class ShellEscapeSection : SessionSection
+{
+public:
+       ///
+       explicit ShellEscapeSection() {};
+
+       ///
+       void read(std::istream & is);
+
+       ///
+       void write(std::ostream & os) const;
+
+       ///
+       bool find(std::string const & name) const;
+
+       ///
+       bool findAuth(std::string const & name) const;
+
+       ///
+       void insert(std::string const & name, bool auth = false);
+
+       ///
+       void remove(std::string const & name);
+
+private:
+       /// set of document files authorized for external conversion
+       std::set<std::string> shellescape_files_;
+};
+
+
 class Session
 {
 public:
@@ -373,6 +406,10 @@ public:
        AuthFilesSection & authFiles() { return auth_files; }
        ///
        AuthFilesSection const & authFiles() const { return auth_files; }
+       ///
+       ShellEscapeSection & shellescapeFiles() { return shellescape_files; }
+       ///
+       ShellEscapeSection const & shellescapeFiles() const { return shellescape_files; }
 
 private:
        friend class LyX;
@@ -402,6 +439,8 @@ private:
        LastCommandsSection last_commands;
        ///
        AuthFilesSection auth_files;
+       ///
+       ShellEscapeSection shellescape_files;
 };
 
 /// This is a singleton class. Get the instance.
index c21ce4f0775767f0c787e168120d955ceaad86ea..8889fdf677a127ef6f81d10a51aa14804e2d438f 100644 (file)
@@ -50,6 +50,7 @@
 #include "OutputParams.h"
 #include "PDFOptions.h"
 #include "qt_helpers.h"
+#include "Session.h"
 #include "Spacing.h"
 #include "TextClass.h"
 #include "Undo.h"
@@ -792,6 +793,8 @@ GuiDocument::GuiDocument(GuiView & lv)
        connect(outputModule->mathoutCB, SIGNAL(currentIndexChanged(int)),
                this, SLOT(change_adaptor()));
 
+       connect(outputModule->shellescapeCB, SIGNAL(stateChanged(int)),
+               this, SLOT(shellescapeChanged()));
        connect(outputModule->outputsyncCB, SIGNAL(clicked()),
                this, SLOT(change_adaptor()));
        connect(outputModule->synccustomCB, SIGNAL(editTextChanged(QString)),
@@ -1537,6 +1540,23 @@ void GuiDocument::change_adaptor()
 }
 
 
+void GuiDocument::shellescapeChanged()
+{
+       // This is treated specially as the change is automatically applied
+       // and the document isn't marked as dirty. Visual feedback is given
+       // by the appearance/disappearance of a red icon in the status bar.
+       bp_.shell_escape = outputModule->shellescapeCB->isChecked();
+       if (!bp_.shell_escape)
+           theSession().shellescapeFiles().remove(buffer().absFileName());
+       else if (!theSession().shellescapeFiles().find(buffer().absFileName()))
+           theSession().shellescapeFiles().insert(buffer().absFileName());
+       Buffer & buf = const_cast<Buffer &>(buffer());
+       buf.params().shell_escape = bp_.shell_escape;
+       BufferView * bv = const_cast<BufferView *>(bufferview());
+       bv->processUpdateFlags(Update::Force);
+}
+
+
 void GuiDocument::includeonlyClicked(QTreeWidgetItem * item, int)
 {
        if (item == 0)
@@ -3115,6 +3135,8 @@ void GuiDocument::applyView()
        bool const nontexfonts = fontModule->osFontsCB->isChecked();
        bp_.useNonTeXFonts = nontexfonts;
 
+       bp_.shell_escape = outputModule->shellescapeCB->isChecked();
+
        bp_.output_sync = outputModule->outputsyncCB->isChecked();
 
        bp_.output_sync_macro = fromqstr(outputModule->synccustomCB->currentText());
@@ -3733,6 +3755,7 @@ void GuiDocument::paramsToDialog()
                index = 0;
        outputModule->defaultFormatCO->setCurrentIndex(index);
 
+       outputModule->shellescapeCB->setChecked(bp_.shell_escape);
        outputModule->outputsyncCB->setChecked(bp_.output_sync);
        outputModule->synccustomCB->setEditText(toqstr(bp_.output_sync_macro));
 
index 2d0fc888220ede2b1ced47e58e71073057632398..5efde8eee3e289b37cec36f8ef031aa20be151c9 100644 (file)
@@ -90,6 +90,7 @@ public Q_SLOTS:
 private Q_SLOTS:
        void updateNumbering();
        void change_adaptor();
+       void shellescapeChanged();
        void includeonlyClicked(QTreeWidgetItem * item, int);
        void setListingsMessage();
        void listingsPackageChanged(int);
index d2e3ed9f2780d5e3bcbfb5e972a700d07df5b0fa..0e461a0e594b9024f1194d3624ffc6d8f37b76b5 100644 (file)
@@ -577,9 +577,25 @@ GuiView::GuiView(int id)
                busylabel, SLOT(hide()));
 
        QFontMetrics const fm(statusBar()->fontMetrics());
-       int const roheight = max(int(d.normalIconSize), fm.height());
-       QSize const rosize(roheight, roheight);
-       QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(rosize);
+       int const iconheight = max(int(d.normalIconSize), fm.height());
+       QSize const iconsize(iconheight, iconheight);
+
+       QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
+       shell_escape_ = new QLabel(statusBar());
+       shell_escape_->setPixmap(shellescape);
+       shell_escape_->setScaledContents(true);
+       shell_escape_->setAlignment(Qt::AlignCenter);
+       shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
+       shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
+                                     "external commands for this document. "
+                                     "Right click to change."));
+       SEMenu * menu = new SEMenu(this);
+       connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
+               menu, SLOT(showMenu(QPoint)));
+       shell_escape_->hide();
+       statusBar()->addPermanentWidget(shell_escape_);
+
+       QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
        read_only_ = new QLabel(statusBar());
        read_only_->setPixmap(readonly);
        read_only_->setScaledContents(true);
@@ -638,6 +654,17 @@ GuiView::~GuiView()
 }
 
 
+void GuiView::disableShellEscape()
+{
+       BufferView * bv = documentBufferView();
+       if (!bv)
+               return;
+       theSession().shellescapeFiles().remove(bv->buffer().absFileName());
+       bv->buffer().params().shell_escape = false;
+       bv->processUpdateFlags(Update::Force);
+}
+
+
 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
 {
        QVector<GuiWorkArea*> areas;
@@ -1160,6 +1187,11 @@ void GuiView::updateWindowTitle(GuiWorkArea * wa)
        // Tell Qt whether the current document is changed
        setWindowModified(!buf.isClean());
 
+       if (buf.params().shell_escape)
+               shell_escape_->show();
+       else
+               shell_escape_->hide();
+
        if (buf.hasReadonlyFlag())
                read_only_->show();
        else
@@ -4652,6 +4684,14 @@ Dialog * GuiView::build(string const & name)
 }
 
 
+SEMenu::SEMenu(QWidget * parent)
+{
+       QAction * action = addAction(qt_("Disable Shell Escape"));
+       connect(action, SIGNAL(triggered()),
+               parent, SLOT(disableShellEscape()));
+}
+
+
 } // namespace frontend
 } // namespace lyx
 
index 998fcc255b5d785e796c98ceb72ef55047eb6d29..dd6fb9bd6e2c22e40aaa72f0d7711c7fb21401d1 100644 (file)
 #include "support/strfwd.h"
 
 #include <QMainWindow>
+#include <QMenu>
 
 class QCloseEvent;
 class QDragEnterEvent;
 class QDropEvent;
 class QLabel;
-class QMenu;
 class QShowEvent;
 
 
@@ -226,6 +226,8 @@ public Q_SLOTS:
        void clearMessage();
        ///
        void updateWindowTitle(GuiWorkArea * wa);
+       ///
+       void disableShellEscape();
 
 private Q_SLOTS:
        ///
@@ -461,6 +463,8 @@ private:
        /// Request to give focus to minibuffer
        bool minibuffer_focus_;
 
+       /// Statusbar widget that shows shell-escape status
+       QLabel * shell_escape_;
        /// Statusbar widget that shows read-only status
        QLabel * read_only_;
        /// Statusbar widget that shows version control status
@@ -476,6 +480,17 @@ private:
        bool devel_mode_;
 };
 
+
+class SEMenu : public QMenu
+{
+       Q_OBJECT
+public:
+       explicit SEMenu(QWidget * parent);
+
+public Q_SLOTS:
+       void showMenu(QPoint const &) { exec(QCursor::pos()); }
+};
+
 } // namespace frontend
 } // namespace lyx
 
index 7ef872ca8bdeed31439702bd3d0f34dfe25cbd2a..fe7082da64316249c1c4b2507bce4a1ef37e79b1 100644 (file)
@@ -250,7 +250,7 @@ GuiWorkArea::Private::Private(GuiWorkArea * parent)
   cursor_visible_(false), cursor_(0),
   need_resize_(false), schedule_redraw_(false), preedit_lines_(1),
   pixel_ratio_(1.0),
-  completer_(new GuiCompleter(p, p)), dialog_mode_(false),
+  completer_(new GuiCompleter(p, p)), dialog_mode_(false), shell_escape_(false),
   read_only_(false), clean_(true), externally_modified_(false)
 {
 }
@@ -1401,11 +1401,13 @@ void GuiWorkArea::updateWindowTitle()
 {
        Buffer const & buf = bufferView().buffer();
        if (buf.fileName() != d->file_name_
+           || buf.params().shell_escape != d->shell_escape_
            || buf.hasReadonlyFlag() != d->read_only_
            || buf.lyxvc().vcstatus() != d->vc_status_
            || buf.isClean() != d->clean_
            || buf.notifiesExternalModification() != d->externally_modified_) {
                d->file_name_ = buf.fileName();
+               d->shell_escape_ = buf.params().shell_escape;
                d->read_only_ = buf.hasReadonlyFlag();
                d->vc_status_ = buf.lyxvc().vcstatus();
                d->clean_ = buf.isClean();
index 3fb0cd750fe2f88bf9d0d31018e6c69e944dc13e..76b05c0d36bb1f5327f8f8544db6ca3f7e0a8ab1 100644 (file)
@@ -192,6 +192,8 @@ struct GuiWorkArea::Private
        ///
        support::FileName file_name_;
        ///
+       bool shell_escape_;
+       ///
        bool read_only_;
        ///
        docstring vc_status_;
index 2bcf0f15cb72bda26925d3f13fe98c786eb4f48a..e3e36dab0e7c2348bd7687620d6e60ece32a0371 100644 (file)
@@ -80,7 +80,7 @@
      </layout>
     </widget>
    </item>
-   <item row="4" column="0">
+   <item row="5" column="0">
     <widget class="QGroupBox" name="savingGB">
      <property name="title">
       <string>LyX Format</string>
     </widget>
    </item>
    <item row="1" column="0">
+    <widget class="QCheckBox" name="shellescapeCB">
+     <property name="toolTip">
+      <string>Runs the LaTeX backend with the -shell-escape option (this setting is always applied immediately)</string>
+     </property>
+     <property name="text">
+      <string>&amp;Allow running external programs</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
     <widget class="QGroupBox" name="outputsyncCB">
      <property name="toolTip">
       <string>Enable forward/reverse search between editor and output (e.g., SyncTeX)</string>
      </layout>
     </widget>
    </item>
-   <item row="2" column="0">
+   <item row="3" column="0">
     <widget class="QGroupBox" name="xhtmlGB">
      <property name="title">
       <string>XHTML Output Options</string>
      </layout>
     </widget>
    </item>
-   <item row="5" column="0">
+   <item row="6" column="0">
     <spacer name="verticalSpacer">
      <property name="orientation">
       <enum>Qt::Vertical</enum>