The "single instance" patch.

By default, if the lyxpipe is set up and working, loading of documents
is deferred to an already running instance. Note that an already running
instance is only used for loading, such that export from command line
still works as usual.

The default behavior can be changed through a preference setting, and,
whatever the default is, it can be overridden by command line options.
Unticking the "Single instance" check box in the preferences, LyX behaves
exactly as before the introduction of this feature.


git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@36278 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Enrico Forestieri 2010-11-13 11:55:05 +00:00
parent 112246ae9f
commit 22ffc93467
9 changed files with 181 additions and 4 deletions

View File

@ -83,6 +83,15 @@ else other than "\fBall\fR", "\fBmain\fR" or "\fBnone\fR", the behavior is as
if "\fBall\fR" was specified, but what follows is left on the command line for if "\fBall\fR" was specified, but what follows is left on the command line for
further processing. further processing.
.TP .TP
\fB \-n [\-\-no\-remote]\fP
open documents passed as arguments in a new instance, even if another
instance of LyX is already running.
.TP
\fB \-r [\-\-remote]\fP
by using the lyxpipe, ask an already running instance of LyX to open the
documents passed as arguments and then exit. If the lyxpipe is not set up or
is not working, a new instance is created and execution continues normally.
.TP
.BI -batch .BI -batch
causes LyX to run the given commands without opening a GUI window. causes LyX to run the given commands without opening a GUI window.
Thus, something like: Thus, something like:

View File

@ -90,6 +90,13 @@ namespace os = support::os;
bool use_gui = true; bool use_gui = true;
// We default to open documents in an already running instance, provided that
// the lyxpipe has been setup. This can be overridden either on the command
// line or through preference settings.
RunMode run_mode = PREFERRED;
// Tell what files can be silently overwritten during batch export. // Tell what files can be silently overwritten during batch export.
// Possible values are: NO_FILES, MAIN_FILE, ALL_FILES, UNSPECIFIED. // Possible values are: NO_FILES, MAIN_FILE, ALL_FILES, UNSPECIFIED.
// Unless specified on command line (through the -f switch) or through the // Unless specified on command line (through the -f switch) or through the
@ -366,12 +373,19 @@ int LyX::exec(int & argc, char * argv[])
return exit_status; return exit_status;
} }
// If not otherwise specified by a command line option or
// by preferences, we default to reuse a running instance.
if (run_mode == PREFERRED)
run_mode = USE_REMOTE;
// FIXME // FIXME
/* Create a CoreApplication class that will provide the main event loop /* Create a CoreApplication class that will provide the main event loop
* and the socket callback registering. With Qt4, only QtCore * and the socket callback registering. With Qt4, only QtCore
* library would be needed. * library would be needed.
* When this is done, a server_mode could be created and the following two * When this is done, a server_mode could be created and the following two
* line would be moved out from here. * line would be moved out from here.
* However, note that the first of the two lines below triggers the
* "single instance" behavior, which should occur right at this point.
*/ */
// Note: socket callback must be registered after init(argc, argv) // Note: socket callback must be registered after init(argc, argv)
// such that package().temp_dir() is properly initialized. // such that package().temp_dir() is properly initialized.
@ -380,7 +394,15 @@ int LyX::exec(int & argc, char * argv[])
FileName(package().temp_dir().absFileName() + "/lyxsocket"))); FileName(package().temp_dir().absFileName() + "/lyxsocket")));
// Start the real execution loop. // Start the real execution loop.
exit_status = pimpl_->application_->exec(); if (!theServer().deferredLoadingToOtherInstance())
exit_status = pimpl_->application_->exec();
else if (!pimpl_->files_to_load_.empty()) {
vector<string>::const_iterator it = pimpl_->files_to_load_.begin();
vector<string>::const_iterator end = pimpl_->files_to_load_.end();
lyxerr << _("The following files could not be loaded:") << endl;
for (; it != end; ++it)
lyxerr << *it << endl;
}
prepareExit(); prepareExit();
@ -1040,8 +1062,13 @@ int parse_help(string const &, string const &, string &)
" specifying whether all files, main file only, or no files,\n" " specifying whether all files, main file only, or no files,\n"
" respectively, are to be overwritten during a batch export.\n" " respectively, are to be overwritten during a batch export.\n"
" Anything else is equivalent to `all', but is not consumed.\n" " Anything else is equivalent to `all', but is not consumed.\n"
"\t-batch execute commands without launching GUI and exit.\n" "\t-n [--no-remote]\n"
"\t-version summarize version and build info\n" " open documents in a new instance\n"
"\t-r [--remote]\n"
" open documents in an already running instance\n"
" (a working lyxpipe is needed)\n"
"\t-batch execute commands without launching GUI and exit.\n"
"\t-version summarize version and build info\n"
"Check the LyX man page for more details.")) << endl; "Check the LyX man page for more details.")) << endl;
exit(0); exit(0);
return 0; return 0;
@ -1142,6 +1169,20 @@ int parse_batch(string const &, string const &, string &)
} }
int parse_noremote(string const &, string const &, string &)
{
run_mode = NEW_INSTANCE;
return 0;
}
int parse_remote(string const &, string const &, string &)
{
run_mode = USE_REMOTE;
return 0;
}
int parse_force(string const & arg, string const &, string &) int parse_force(string const & arg, string const &, string &)
{ {
if (arg == "all") { if (arg == "all") {
@ -1183,6 +1224,10 @@ void LyX::easyParse(int & argc, char * argv[])
cmdmap["-batch"] = parse_batch; cmdmap["-batch"] = parse_batch;
cmdmap["-f"] = parse_force; cmdmap["-f"] = parse_force;
cmdmap["--force-overwrite"] = parse_force; cmdmap["--force-overwrite"] = parse_force;
cmdmap["-n"] = parse_noremote;
cmdmap["--no-remote"] = parse_noremote;
cmdmap["-r"] = parse_remote;
cmdmap["--remote"] = parse_remote;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
map<string, cmd_helper>::const_iterator it map<string, cmd_helper>::const_iterator it
@ -1236,6 +1281,13 @@ void dispatch(FuncRequest const & action, DispatchResult & dr)
} }
vector<string> & theFilesToLoad()
{
LASSERT(singleton_, /**/);
return singleton_->pimpl_->files_to_load_;
}
BufferList & theBufferList() BufferList & theBufferList()
{ {
LASSERT(singleton_, /**/); LASSERT(singleton_, /**/);

View File

@ -16,6 +16,8 @@
#include "support/strfwd.h" #include "support/strfwd.h"
#include <vector>
namespace lyx { namespace lyx {
class BufferList; class BufferList;
@ -34,6 +36,12 @@ class ServerSocket;
class Session; class Session;
class SpellChecker; class SpellChecker;
enum RunMode {
NEW_INSTANCE,
USE_REMOTE,
PREFERRED
};
enum OverwriteFiles { enum OverwriteFiles {
NO_FILES, NO_FILES,
MAIN_FILE, MAIN_FILE,
@ -42,6 +50,7 @@ enum OverwriteFiles {
}; };
extern bool use_gui; extern bool use_gui;
extern RunMode run_mode;
extern OverwriteFiles force_overwrite; extern OverwriteFiles force_overwrite;
namespace frontend { namespace frontend {
@ -126,6 +135,7 @@ private:
friend FuncStatus getStatus(FuncRequest const & action); friend FuncStatus getStatus(FuncRequest const & action);
friend void dispatch(FuncRequest const & action); friend void dispatch(FuncRequest const & action);
friend void dispatch(FuncRequest const & action, DispatchResult & dr); friend void dispatch(FuncRequest const & action, DispatchResult & dr);
friend std::vector<std::string> & theFilesToLoad();
friend BufferList & theBufferList(); friend BufferList & theBufferList();
friend Server & theServer(); friend Server & theServer();
friend ServerSocket & theServerSocket(); friend ServerSocket & theServerSocket();

View File

@ -179,6 +179,7 @@ LexerKeyword lyxrcTags[] = {
{ "\\set_color", LyXRC::RC_SET_COLOR }, { "\\set_color", LyXRC::RC_SET_COLOR },
{ "\\show_banner", LyXRC::RC_SHOW_BANNER }, { "\\show_banner", LyXRC::RC_SHOW_BANNER },
{ "\\single_close_tab_button", LyXRC::RC_SINGLE_CLOSE_TAB_BUTTON }, { "\\single_close_tab_button", LyXRC::RC_SINGLE_CLOSE_TAB_BUTTON },
{ "\\single_instance", LyXRC::RC_SINGLE_INSTANCE },
{ "\\sort_layouts", LyXRC::RC_SORT_LAYOUTS }, { "\\sort_layouts", LyXRC::RC_SORT_LAYOUTS },
{ "\\spell_command", LyXRC::RC_SPELL_COMMAND }, { "\\spell_command", LyXRC::RC_SPELL_COMMAND },
{ "\\spellcheck_continuously", LyXRC::RC_SPELLCHECK_CONTINUOUSLY }, { "\\spellcheck_continuously", LyXRC::RC_SPELLCHECK_CONTINUOUSLY },
@ -341,6 +342,7 @@ void LyXRC::setDefaults()
user_email = to_utf8(support::user_email()); user_email = to_utf8(support::user_email());
open_buffers_in_tabs = true; open_buffers_in_tabs = true;
single_close_tab_button = false; single_close_tab_button = false;
single_instance = true;
forward_search_dvi = string(); forward_search_dvi = string();
forward_search_pdf = string(); forward_search_pdf = string();
export_overwrite = NO_FILES; export_overwrite = NO_FILES;
@ -1185,6 +1187,11 @@ int LyXRC::read(Lexer & lexrc)
case RC_SINGLE_CLOSE_TAB_BUTTON: case RC_SINGLE_CLOSE_TAB_BUTTON:
lexrc >> single_close_tab_button; lexrc >> single_close_tab_button;
break; break;
case RC_SINGLE_INSTANCE:
lexrc >> single_instance;
if (run_mode == PREFERRED)
run_mode = single_instance ? USE_REMOTE : NEW_INSTANCE;
break;
case RC_FORWARD_SEARCH_DVI: case RC_FORWARD_SEARCH_DVI:
if (lexrc.next(true)) if (lexrc.next(true))
forward_search_dvi = lexrc.getString(); forward_search_dvi = lexrc.getString();
@ -1951,6 +1958,15 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc, string const & name) c
} }
if (tag != RC_LAST) if (tag != RC_LAST)
break; break;
case RC_SINGLE_INSTANCE:
if (ignore_system_lyxrc ||
single_instance != system_lyxrc.single_instance) {
os << "\\single_instance "
<< convert<string>(single_instance)
<< '\n';
}
if (tag != RC_LAST)
break;
case RC_FORWARD_SEARCH_DVI: case RC_FORWARD_SEARCH_DVI:
if (ignore_system_lyxrc || if (ignore_system_lyxrc ||
forward_search_dvi != system_lyxrc.forward_search_dvi) { forward_search_dvi != system_lyxrc.forward_search_dvi) {
@ -2968,6 +2984,7 @@ void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new)
case LyXRC::RC_USE_SPELL_LIB: case LyXRC::RC_USE_SPELL_LIB:
case LyXRC::RC_VIEWDVI_PAPEROPTION: case LyXRC::RC_VIEWDVI_PAPEROPTION:
case LyXRC::RC_SINGLE_CLOSE_TAB_BUTTON: case LyXRC::RC_SINGLE_CLOSE_TAB_BUTTON:
case LyXRC::RC_SINGLE_INSTANCE:
case LyXRC::RC_SORT_LAYOUTS: case LyXRC::RC_SORT_LAYOUTS:
case LyXRC::RC_FULL_SCREEN_LIMIT: case LyXRC::RC_FULL_SCREEN_LIMIT:
case LyXRC::RC_FULL_SCREEN_SCROLLBAR: case LyXRC::RC_FULL_SCREEN_SCROLLBAR:

View File

@ -162,6 +162,7 @@ public:
RC_SET_COLOR, RC_SET_COLOR,
RC_SHOW_BANNER, RC_SHOW_BANNER,
RC_SINGLE_CLOSE_TAB_BUTTON, RC_SINGLE_CLOSE_TAB_BUTTON,
RC_SINGLE_INSTANCE,
RC_SORT_LAYOUTS, RC_SORT_LAYOUTS,
RC_SPELL_COMMAND, RC_SPELL_COMMAND,
RC_SPELLCHECK_CONTINUOUSLY, RC_SPELLCHECK_CONTINUOUSLY,
@ -502,6 +503,8 @@ public:
/// ///
bool single_close_tab_button; bool single_close_tab_button;
/// ///
bool single_instance;
///
std::string forward_search_dvi; std::string forward_search_dvi;
/// ///
std::string forward_search_pdf; std::string forward_search_pdf;

View File

@ -51,6 +51,7 @@
#include "support/debug.h" #include "support/debug.h"
#include "support/FileName.h" #include "support/FileName.h"
#include "support/filetools.h"
#include "support/lassert.h" #include "support/lassert.h"
#include "support/lstrings.h" #include "support/lstrings.h"
#include "support/os.h" #include "support/os.h"
@ -60,6 +61,7 @@
#ifdef _WIN32 #ifdef _WIN32
#include <QCoreApplication> #include <QCoreApplication>
#endif #endif
#include <QThread>
#include <cerrno> #include <cerrno>
#ifdef HAVE_SYS_STAT_H #ifdef HAVE_SYS_STAT_H
@ -140,6 +142,7 @@ LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
pipe_[i].handle = INVALID_HANDLE_VALUE; pipe_[i].handle = INVALID_HANDLE_VALUE;
} }
ready_ = false; ready_ = false;
deferred_loading_ = false;
openConnection(); openConnection();
} }
@ -515,8 +518,14 @@ void LyXComm::openConnection()
return; return;
} }
// Check whether the pipe name is being used by some other program. // Check whether the pipe name is being used by some other instance.
if (!stopserver_ && WaitNamedPipe(inPipeName().c_str(), 0)) { if (!stopserver_ && WaitNamedPipe(inPipeName().c_str(), 0)) {
// Tell the running instance to load the files
if (run_mode == USE_REMOTE && loadFilesInOtherInstance()) {
deferred_loading_ = true;
pipename_.erase();
return;
}
lyxerr << "LyXComm: Pipe " << external_path(inPipeName()) lyxerr << "LyXComm: Pipe " << external_path(inPipeName())
<< " already exists.\nMaybe another instance of LyX" << " already exists.\nMaybe another instance of LyX"
" is using it." << endl; " is using it." << endl;
@ -721,6 +730,7 @@ LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
: pipename_(pip), client_(cli), clientcb_(ccb) : pipename_(pip), client_(cli), clientcb_(ccb)
{ {
ready_ = false; ready_ = false;
deferred_loading_ = false;
openConnection(); openConnection();
} }
@ -798,6 +808,12 @@ int LyXComm::startPipe(string const & file, bool write)
if (fd >= 0) { if (fd >= 0) {
// Another LyX instance is using it. // Another LyX instance is using it.
::close(fd); ::close(fd);
// Tell the running instance to load the files
if (run_mode == USE_REMOTE && loadFilesInOtherInstance()) {
deferred_loading_ = true;
pipename_.erase();
return -1;
}
} else if (errno == ENXIO) { } else if (errno == ENXIO) {
// No process is reading from the other end. // No process is reading from the other end.
stalepipe = true; stalepipe = true;
@ -964,6 +980,48 @@ void LyXComm::send(string const & msg)
#endif // defined (HAVE_MKFIFO) #endif // defined (HAVE_MKFIFO)
namespace {
struct Sleep : QThread
{
static void millisec(unsigned long ms)
{
QThread::usleep(ms * 1000);
}
};
} // namespace anon
bool LyXComm::loadFilesInOtherInstance()
{
int pipefd;
int loaded_files = 0;
FileName const pipe(inPipeName());
vector<string>::iterator it = theFilesToLoad().begin();
while (it != theFilesToLoad().end()) {
FileName fname = fileSearch(string(), os::internal_path(*it),
"lyx", may_not_exist);
if (fname.empty()) {
++it;
continue;
}
// Wait a while to allow time for the other
// instance to reset the connection
Sleep::millisec(200);
pipefd = ::open(pipe.toFilesystemEncoding().c_str(), O_WRONLY);
if (pipefd < 0)
break;
string const cmd = "LYXCMD:pipe:file-open:" +
fname.absFileName() + '\n';
::write(pipefd, cmd.c_str(), cmd.length());
::close(pipefd);
++loaded_files;
it = theFilesToLoad().erase(it);
}
return loaded_files > 0;
}
string const LyXComm::inPipeName() const string const LyXComm::inPipeName() const
{ {

View File

@ -101,6 +101,9 @@ public:
void read_ready(DWORD); void read_ready(DWORD);
#endif #endif
/// Tell whether we asked another instance of LyX to open the files
bool deferredLoading() { return deferred_loading_; }
private: private:
/// the filename of the in pipe /// the filename of the in pipe
std::string const inPipeName() const; std::string const inPipeName() const;
@ -114,6 +117,9 @@ private:
/// Close pipes /// Close pipes
void closeConnection(); void closeConnection();
/// Load files in another running instance of LyX
bool loadFilesInOtherInstance();
#ifndef _WIN32 #ifndef _WIN32
/// start a pipe /// start a pipe
int startPipe(std::string const &, bool); int startPipe(std::string const &, bool);
@ -178,6 +184,9 @@ private:
/// The client callback function /// The client callback function
ClientCallbackfct clientcb_; ClientCallbackfct clientcb_;
/// Did we defer loading of files to another instance?
bool deferred_loading_;
}; };
@ -197,6 +206,8 @@ public:
~Server(); ~Server();
/// ///
void notifyClient(std::string const &); void notifyClient(std::string const &);
///
bool deferredLoadingToOtherInstance() { return pipes_.deferredLoading(); }
/// whilst crashing etc. /// whilst crashing etc.
void emergencyCleanup() { pipes_.emergencyCleanup(); } void emergencyCleanup() { pipes_.emergencyCleanup(); }
@ -221,6 +232,9 @@ private:
/// Implementation is in LyX.cpp /// Implementation is in LyX.cpp
Server & theServer(); Server & theServer();
/// Implementation is in LyX.cpp
extern std::vector<std::string> & theFilesToLoad();
} // namespace lyx } // namespace lyx

View File

@ -2358,6 +2358,8 @@ PrefUserInterface::PrefUserInterface(GuiPreferences * form)
TextLabel1, SLOT(setEnabled(bool))); TextLabel1, SLOT(setEnabled(bool)));
connect(openDocumentsInTabsCB, SIGNAL(clicked()), connect(openDocumentsInTabsCB, SIGNAL(clicked()),
this, SIGNAL(changed())); this, SIGNAL(changed()));
connect(singleInstanceCB, SIGNAL(clicked()),
this, SIGNAL(changed()));
#if QT_VERSION < 0x040500 #if QT_VERSION < 0x040500
singleCloseTabButtonCB->setEnabled(false); singleCloseTabButtonCB->setEnabled(false);
#endif #endif
@ -2401,6 +2403,7 @@ void PrefUserInterface::apply(LyXRC & rc) const
rc.num_lastfiles = lastfilesSB->value(); rc.num_lastfiles = lastfilesSB->value();
rc.use_tooltip = tooltipCB->isChecked(); rc.use_tooltip = tooltipCB->isChecked();
rc.open_buffers_in_tabs = openDocumentsInTabsCB->isChecked(); rc.open_buffers_in_tabs = openDocumentsInTabsCB->isChecked();
rc.single_instance = singleInstanceCB->isChecked();
rc.single_close_tab_button = singleCloseTabButtonCB->isChecked(); rc.single_close_tab_button = singleCloseTabButtonCB->isChecked();
#if QT_VERSION < 0x040500 #if QT_VERSION < 0x040500
rc.single_close_tab_button = true; rc.single_close_tab_button = true;
@ -2427,6 +2430,7 @@ void PrefUserInterface::update(LyXRC const & rc)
lastfilesSB->setValue(rc.num_lastfiles); lastfilesSB->setValue(rc.num_lastfiles);
tooltipCB->setChecked(rc.use_tooltip); tooltipCB->setChecked(rc.use_tooltip);
openDocumentsInTabsCB->setChecked(rc.open_buffers_in_tabs); openDocumentsInTabsCB->setChecked(rc.open_buffers_in_tabs);
singleInstanceCB->setChecked(rc.single_instance);
singleCloseTabButtonCB->setChecked(rc.single_close_tab_button); singleCloseTabButtonCB->setChecked(rc.single_close_tab_button);
} }

View File

@ -240,6 +240,16 @@
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="2"> <item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="singleInstanceCB">
<property name="toolTip">
<string>Whether to open documents in an already running instance of LyX.</string>
</property>
<property name="text">
<string>S&amp;ingle instance</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="singleCloseTabButtonCB"> <widget class="QCheckBox" name="singleCloseTabButtonCB">
<property name="toolTip"> <property name="toolTip">
<string>Whether to place close button on each tab or only one in the top left.</string> <string>Whether to place close button on each tab or only one in the top left.</string>