From 3d8d6eca5ec966acba1f99f0655f4cf922f39e71 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 5 Nov 2015 15:56:50 +0100 Subject: [PATCH] + implement mechanism to allow only a single application instance --- src/App/Application.cpp | 67 +++++++++++----- src/App/Application.h | 2 + src/Gui/Application.cpp | 38 +++++---- src/Gui/GuiApplication.cpp | 157 +++++++++++++++++++++++++++++++++++++ src/Gui/GuiApplication.h | 27 ++++++- src/Gui/MainWindow.cpp | 17 ++++ src/Gui/MainWindow.h | 4 + 7 files changed, 276 insertions(+), 36 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 868235fc0..01a4cfb4f 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -1285,49 +1285,57 @@ void Application::initApplication(void) Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADInit")); } -void Application::processCmdLineFiles(void) +std::list Application::getCmdLineFiles() { - Base::Console().Log("Init: Processing command line files\n"); + std::list files; // cycling through all the open files unsigned short count = 0; count = atoi(mConfig["OpenFileCount"].c_str()); std::string File; - if (count == 0 && mConfig["RunMode"] == "Exit") - mConfig["RunMode"] = "Cmd"; - for (unsigned short i=0; i& files) +{ + Base::Console().Log("Init: Processing command line files\n"); + for (std::list::const_iterator it = files.begin(); it != files.end(); ++it) { + Base::FileInfo file(*it); + + Base::Console().Log("Init: Processing file: %s\n",file.filePath().c_str()); - std::string Ext = File.extension(); - Base::Console().Log("Init: Processing file: %s\n",File.filePath().c_str()); try { - - if (File.hasExtension("fcstd") || File.hasExtension("std")) { + if (file.hasExtension("fcstd") || file.hasExtension("std")) { // try to open - Application::_pcSingleton->openDocument(File.filePath().c_str()); + Application::_pcSingleton->openDocument(file.filePath().c_str()); } - else if (File.hasExtension("fcscript")||File.hasExtension("fcmacro")) { - Base::Interpreter().runFile(File.filePath().c_str(), true); + else if (file.hasExtension("fcscript") || file.hasExtension("fcmacro")) { + Base::Interpreter().runFile(file.filePath().c_str(), true); } - else if (File.hasExtension("py")) { + else if (file.hasExtension("py")) { try { - Base::Interpreter().loadModule(File.fileNamePure().c_str()); + Base::Interpreter().loadModule(file.fileNamePure().c_str()); } catch(const PyException&) { // if module load not work, just try run the script (run in __main__) - Base::Interpreter().runFile(File.filePath().c_str(),true); + Base::Interpreter().runFile(file.filePath().c_str(),true); } } else { - std::vector mods = App::GetApplication().getImportModules(Ext.c_str()); + std::string ext = file.extension(); + std::vector mods = App::GetApplication().getImportModules(ext.c_str()); if (!mods.empty()) { - std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(File.filePath().c_str()); + std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(file.filePath().c_str()); Base::Interpreter().loadModule(mods.front().c_str()); Base::Interpreter().runStringArg("import %s",mods.front().c_str()); Base::Interpreter().runStringArg("%s.open(u\"%s\")",mods.front().c_str(), @@ -1335,7 +1343,7 @@ void Application::processCmdLineFiles(void) Base::Console().Log("Command line open: %s.open(u\"%s\")\n",mods.front().c_str(),escapedstr.c_str()); } else { - Console().Warning("File format not supported: %s \n", File.filePath().c_str()); + Console().Warning("File format not supported: %s \n", file.filePath().c_str()); } } } @@ -1343,12 +1351,24 @@ void Application::processCmdLineFiles(void) throw; // re-throw to main() function } catch (const Base::Exception& e) { - Console().Error("Exception while processing file: %s [%s]\n", File.filePath().c_str(), e.what()); + Console().Error("Exception while processing file: %s [%s]\n", file.filePath().c_str(), e.what()); } catch (...) { - Console().Error("Unknown exception while processing file: %s \n", File.filePath().c_str()); + Console().Error("Unknown exception while processing file: %s \n", file.filePath().c_str()); } } +} + +void Application::processCmdLineFiles(void) +{ + // process files passed to command line + std::list files = getCmdLineFiles(); + processFiles(files); + + if (files.empty()) { + if (mConfig["RunMode"] == "Exit") + mConfig["RunMode"] = "Cmd"; + } const std::map& cfg = Application::Config(); std::map::const_iterator it = cfg.find("SaveFile"); @@ -1586,6 +1606,7 @@ void Application::ParseOptions(int ac, char ** av) ("run-test,t", value() ,"Test level") ("module-path,M", value< vector >()->composing(),"Additional module paths") ("python-path,P", value< vector >()->composing(),"Additional python paths") + ("single-instance", "Allow to run a single instance of the application") ; @@ -1806,6 +1827,10 @@ void Application::ParseOptions(int ac, char ** av) }; } + if (vm.count("single-instance")) { + mConfig["SingleInstance"] = "1"; + } + if (vm.count("dump-config")) { std::stringstream str; for (std::map::iterator it=mConfig.begin(); it != mConfig.end(); ++it) { diff --git a/src/App/Application.h b/src/App/Application.h index 6b21841a8..e87c20c8a 100644 --- a/src/App/Application.h +++ b/src/App/Application.h @@ -216,6 +216,8 @@ public: static void destruct(void); static void destructObserver(void); static void processCmdLineFiles(void); + static std::list getCmdLineFiles(); + static void processFiles(const std::list&); static void runApplication(void); friend Application &GetApplication(void); static std::map &Config(void){return mConfig;} diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index fb6f881dc..bafc23e98 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -1538,15 +1538,34 @@ void Application::initTypes(void) void Application::runApplication(void) { + const std::map& cfg = App::Application::Config(); + std::map::const_iterator it; + // A new QApplication Base::Console().Log("Init: Creating Gui::Application and QApplication\n"); // if application not yet created by the splasher int argc = App::Application::GetARGC(); int systemExit = 1000; - GUIApplication mainApp(argc, App::Application::GetARGV(), systemExit); + GUISingleApplication mainApp(argc, App::Application::GetARGV(), systemExit); + + // check if a single or multiple instances can run + it = cfg.find("SingleInstance"); + if (it != cfg.end() && mainApp.isRunning()) { + // send the file names to be opened to the server application so that this + // opens them + std::list files = App::Application::getCmdLineFiles(); + for (std::list::iterator jt = files.begin(); jt != files.end(); ++jt) { + QByteArray msg(jt->c_str(), static_cast(jt->size())); + msg.prepend("OpenFile:"); + if (!mainApp.sendMessage(msg)) { + qWarning("Failed to send message to server"); + break; + } + } + return; + } + // set application icon and window title - const std::map& cfg = App::Application::Config(); - std::map::const_iterator it; it = cfg.find("Application"); if (it != cfg.end()) { mainApp.setApplicationName(QString::fromUtf8(it->second.c_str())); @@ -1615,6 +1634,8 @@ void Application::runApplication(void) Application app(true); MainWindow mw; mw.setWindowTitle(mainApp.applicationName()); + QObject::connect(&mainApp, SIGNAL(messageReceived(const QList &)), + &mw, SLOT(processMessages(const QList &))); ParameterGrp::handle hDocGrp = WindowParameter::getDefaultParameter()->GetGroup("Document"); int timeout = hDocGrp->GetInt("AutoSaveTimeout", 15); // 15 min @@ -1741,19 +1762,8 @@ void Application::runApplication(void) Instance->d->startingUp = false; -#if 0 - // processing all command line files - App::Application::processCmdLineFiles(); - - // Create new document? - ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Document"); - if (hGrp->GetBool("CreateNewDoc", false)) { - App::GetApplication().newDocument(); - } -#else // gets called once we start the event loop QTimer::singleShot(0, &mw, SLOT(delayedStartup())); -#endif // run the Application event loop Base::Console().Log("Init: Entering event loop\n"); diff --git a/src/Gui/GuiApplication.cpp b/src/Gui/GuiApplication.cpp index 2ab1ea408..5cd96d8b0 100644 --- a/src/Gui/GuiApplication.cpp +++ b/src/Gui/GuiApplication.cpp @@ -26,7 +26,24 @@ #ifndef _PreComp_ # include # include +# include +# include +# include +# include # include +# include +#endif + +#include +#include + +#if defined(Q_OS_WIN) +# include +#endif +#if defined(Q_OS_UNIX) +# include +# include +# include #endif #include "GuiApplication.h" @@ -46,6 +63,10 @@ GUIApplication::GUIApplication(int & argc, char ** argv, int exitcode) { } +GUIApplication::~GUIApplication() +{ +} + bool GUIApplication::notify (QObject * receiver, QEvent * event) { if (!receiver && event) { @@ -123,4 +144,140 @@ void GUIApplication::commitData(QSessionManager &manager) } } +// ---------------------------------------------------------------------------- + +class GUISingleApplication::Private { +public: + Private(GUISingleApplication *q_ptr) + : q_ptr(q_ptr) + , timer(new QTimer(q_ptr)) + , server(0) + , running(false) + { + timer->setSingleShot(true); + std::string exeName = App::GetApplication().getExecutableName(); + serverName = QString::fromStdString(exeName); + } + + ~Private() + { + if (server) + server->close(); + delete server; + } + + void setupConnection() + { + QLocalSocket socket; + socket.connectToServer(serverName); + if (socket.waitForConnected(1000)) { + this->running = true; + } + else { + startServer(); + } + } + + void startServer() + { + // Start a QLocalServer to listen for connections + server = new QLocalServer(); + QObject::connect(server, SIGNAL(newConnection()), + q_ptr, SLOT(receiveConnection())); + // first attempt + if (!server->listen(serverName)) { + if (server->serverError() == QAbstractSocket::AddressInUseError) { + // second attempt + server->removeServer(serverName); + server->listen(serverName); + } + } + if (server->isListening()) { + Base::Console().Log("Local server '%s' started\n", qPrintable(serverName)); + } + else { + Base::Console().Log("Local server '%s' failed to start\n", qPrintable(serverName)); + } + } + + GUISingleApplication *q_ptr; + QTimer *timer; + QLocalServer *server; + QString serverName; + QList messages; + bool running; +}; + +GUISingleApplication::GUISingleApplication(int & argc, char ** argv, int exitcode) + : GUIApplication(argc, argv, exitcode), + d_ptr(new Private(this)) +{ + d_ptr->setupConnection(); + connect(d_ptr->timer, SIGNAL(timeout()), this, SLOT(processMessages())); +} + +GUISingleApplication::~GUISingleApplication() +{ +} + +bool GUISingleApplication::isRunning() const +{ + return d_ptr->running; +} + +bool GUISingleApplication::sendMessage(const QByteArray &message, int timeout) +{ + QLocalSocket socket; + bool connected = false; + for(int i = 0; i < 2; i++) { + socket.connectToServer(d_ptr->serverName); + connected = socket.waitForConnected(timeout/2); + if (connected || i > 0) + break; + int ms = 250; +#if defined(Q_OS_WIN) + Sleep(DWORD(ms)); +#else + usleep(ms*1000); +#endif + } + if (!connected) + return false; + + QDataStream ds(&socket); + ds << message; + socket.waitForBytesWritten(timeout); + return true; +} + +void GUISingleApplication::receiveConnection() +{ + QLocalSocket *socket = d_ptr->server->nextPendingConnection(); + if (!socket) + return; + + connect(socket, SIGNAL(disconnected()), + socket, SLOT(deleteLater())); + if (socket->waitForReadyRead()) { + QDataStream in(socket); + if (!in.atEnd()) { + d_ptr->timer->stop(); + QByteArray message; + in >> message; + Base::Console().Log("Received message: %s\n", message.constData()); + d_ptr->messages.push_back(message); + d_ptr->timer->start(1000); + } + } + + socket->disconnectFromServer(); +} + +void GUISingleApplication::processMessages() +{ + QList msg = d_ptr->messages; + d_ptr->messages.clear(); + Q_EMIT messageReceived(msg); +} + #include "moc_GuiApplication.cpp" diff --git a/src/Gui/GuiApplication.h b/src/Gui/GuiApplication.h index 29bb91445..69f9b7e69 100644 --- a/src/Gui/GuiApplication.h +++ b/src/Gui/GuiApplication.h @@ -25,6 +25,7 @@ #define GUI_APPLICATION_H #include "GuiApplicationNativeEventAware.h" +#include class QSessionManager; @@ -38,7 +39,8 @@ class GUIApplication : public GUIApplicationNativeEventAware Q_OBJECT public: - GUIApplication(int & argc, char ** argv, int exitcode); + explicit GUIApplication(int & argc, char ** argv, int exitcode); + virtual ~GUIApplication(); /** * Make forwarding events exception-safe and get more detailed information @@ -51,6 +53,29 @@ private: int systemExit; }; +class GUISingleApplication : public GUIApplication +{ + Q_OBJECT + +public: + explicit GUISingleApplication(int & argc, char ** argv, int exitcode); + virtual ~GUISingleApplication(); + + bool isRunning() const; + bool sendMessage(const QByteArray &message, int timeout = 5000); + +private Q_SLOTS: + void receiveConnection(); + void processMessages(); + +Q_SIGNALS: + void messageReceived(const QList &); + +private: + class Private; + QScopedPointer d_ptr; +}; + } #endif // GUI_APPLICATION_H diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 69d45305f..6d4767aa1 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -999,6 +999,23 @@ void MainWindow::showMainWindow() #endif } +void MainWindow::processMessages(const QList & msg) +{ + // handle all the messages to open files + try { + WaitCursor wc; + std::list files; + QByteArray action("OpenFile:"); + for (QList::const_iterator it = msg.begin(); it != msg.end(); ++it) { + if (it->startsWith(action)) + files.push_back(std::string(it->mid(action.size()).constData())); + } + App::Application::processFiles(files); + } + catch (const Base::SystemExitException&) { + } +} + void MainWindow::delayedStartup() { // processing all command line files diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h index 49e4d841a..1c0fc6904 100644 --- a/src/Gui/MainWindow.h +++ b/src/Gui/MainWindow.h @@ -262,6 +262,10 @@ private Q_SLOTS: * \internal */ void delayedStartup(); + /** + * \internal + */ + void processMessages(const QList &); Q_SIGNALS: void timeEvent();