aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Kampas <martin.kampas@jolla.com>2016-09-28 09:19:15 +0200
committerJuergen Bocklage-Ryannel <juergen.bocklage-ryannel@pelagicore.com>2016-10-07 07:01:02 +0000
commitdc23b1884a606cc4d34c8480deeac82599630e04 (patch)
tree502f1300b5255e3794e39337aa6784bbb4f5a25b
parente9be3efe9a53aa36d55ce24dfe3b243cbe0ea840 (diff)
Bench: Singletonize
Make bench a singleton application. Attempt to launch another instance will result in activation of the existing instance, forwarding command line arguments whenever it makes sense. Change-Id: Ia83091e9bf092df1c8557924fb37373227ef07b9 Reviewed-by: Juergen Bocklage-Ryannel <juergen.bocklage-ryannel@pelagicore.com>
-rw-r--r--src/bench/hostmodel.cpp10
-rw-r--r--src/bench/hostmodel.h1
-rw-r--r--src/bench/main.cpp304
-rw-r--r--src/bench/mainwindow.cpp32
-rw-r--r--src/bench/mainwindow.h10
-rw-r--r--src/bench/options.cpp24
-rw-r--r--src/bench/options.h11
7 files changed, 334 insertions, 58 deletions
diff --git a/src/bench/hostmodel.cpp b/src/bench/hostmodel.cpp
index a94b37e..aad68da 100644
--- a/src/bench/hostmodel.cpp
+++ b/src/bench/hostmodel.cpp
@@ -188,6 +188,16 @@ QList<Host *> HostModel::findByAutoDiscoveryId(QUuid id) const
return hosts;
}
+Host *HostModel::host(const QString &name) const
+{
+ foreach (Host *host, m_hosts) {
+ if (host->name() == name)
+ return host;
+ }
+
+ return 0;
+}
+
Host *HostModel::hostAt(int index) const
{
if (index < 0 || index >= m_hosts.count())
diff --git a/src/bench/hostmodel.h b/src/bench/hostmodel.h
index 1a1aadf..a03d00b 100644
--- a/src/bench/hostmodel.h
+++ b/src/bench/hostmodel.h
@@ -61,6 +61,7 @@ public:
QList<Host*> findByAutoDiscoveryId(QUuid id) const;
+ Host* host(const QString &name) const;
Host* hostAt(int index) const;
void restoreFromSettings(QSettings* s);
diff --git a/src/bench/main.cpp b/src/bench/main.cpp
index a111bf2..5c98d01 100644
--- a/src/bench/main.cpp
+++ b/src/bench/main.cpp
@@ -37,13 +37,120 @@
#include "mainwindow.h"
#include "qmllive_version.h"
-static void setDarkStyle(QApplication *app)
+class Application : public QApplication
+{
+ Q_OBJECT
+
+public:
+ static Application *create(int &argc, char **argv);
+
+protected:
+ Application(int &argc, char **argv);
+ static bool isMaster();
+
+ QString serverName() const;
+ void setDarkStyle();
+ void parseArguments(const QStringList &arguments, Options *options);
+};
+
+class MasterApplication : public Application
+{
+ Q_OBJECT
+
+public:
+ MasterApplication(int &argc, char **argv);
+
+private:
+ void listenForArguments();
+ void applyOptions(const Options &options);
+
+private:
+ QPointer<MainWindow> m_window;
+};
+
+class SlaveApplication : public Application
+{
+ Q_OBJECT
+
+public:
+ SlaveApplication(int &argc, char **argv);
+
+private:
+ void warnAboutIgnoredOptions(const Options &options);
+ void forwardArguments();
+};
+
+Application *Application::create(int &argc, char **argv)
+{
+ setApplicationName("QmlLiveBench");
+ setOrganizationDomain(QLatin1String(QMLLIVE_ORGANIZATION_DOMAIN));
+ setOrganizationName(QLatin1String(QMLLIVE_ORGANIZATION_NAME));
+
+ if (isMaster())
+ return new MasterApplication(argc, argv);
+ else
+ return new SlaveApplication(argc, argv);
+}
+
+Application::Application(int &argc, char **argv)
+ : QApplication(argc, argv)
+{
+ setAttribute(Qt::AA_NativeWindows, true);
+ setAttribute(Qt::AA_ImmediateWidgetCreation, true);
+
+ setDarkStyle();
+}
+
+bool Application::isMaster()
+{
+ Q_ASSERT(!applicationName().isEmpty());
+ Q_ASSERT(!organizationDomain().isEmpty() || !organizationName().isEmpty());
+
+ static QSharedMemory *lock = 0;
+ static bool retv = false;
+
+ if (lock != 0)
+ return retv;
+
+ const QString key = QString::fromLatin1("%1.%2-lock")
+ .arg(organizationDomain().isEmpty() ? organizationName() : organizationDomain())
+ .arg(applicationName());
+
+ lock = new QSharedMemory(key, qApp);
+
+#ifdef Q_OS_UNIX
+ // Ensure there is no stale shared memory segment after crash - call QSharedMemory destructor
+ { QSharedMemory(key).attach(); }
+#endif
+
+ if (lock->attach(QSharedMemory::ReadOnly)) {
+ lock->detach();
+ return retv = false;
+ }
+
+ if (!lock->create(1))
+ return retv = false;
+
+ return retv = true;
+}
+
+QString Application::serverName() const
+{
+ Q_ASSERT(!applicationName().isEmpty());
+ Q_ASSERT(!organizationDomain().isEmpty() || !organizationName().isEmpty());
+
+ return QString::fromLatin1("%1.%2-app")
+ .arg(organizationDomain().isEmpty() ? organizationName() : organizationDomain())
+ .arg(applicationName());
+}
+
+void Application::setDarkStyle()
{
QStyle *style = QStyleFactory::create("fusion");
if (!style) {
return;
}
- app->setStyle(style);
+ setStyle(style);
QPalette palette;
palette.setColor(QPalette::Window, QColor("#3D3D3D"));
@@ -58,10 +165,10 @@ static void setDarkStyle(QApplication *app)
palette.setColor(QPalette::BrightText, QColor("#D0021B"));
palette.setColor(QPalette::Highlight, QColor("#F19300"));
palette.setColor(QPalette::HighlightedText, QColor("#1C1C1C"));
- app->setPalette(palette);
+ setPalette(palette);
}
-static void parseArguments(const QStringList& arguments, Options *options)
+void Application::parseArguments(const QStringList &arguments, Options *options)
{
QCommandLineParser parser;
parser.setApplicationDescription("QmlLive reloading workbench");
@@ -78,9 +185,12 @@ static void parseArguments(const QStringList& arguments, Options *options)
parser.addOption(stayOnTopOption);
QCommandLineOption addHostOption("addhost", "add or update remote host configuration and exit", "name,address[,port]");
parser.addOption(addHostOption);
+ QCommandLineOption remoteOnlyOption("remoteonly", "talk to a running bench, do nothing if none is running.");
+ parser.addOption(remoteOnlyOption);
parser.process(arguments);
+ options->setRemoteOnly(parser.isSet(remoteOnlyOption));
options->setPluginPath(parser.value(pluginPathOption));
options->setImportPaths(parser.values(importPathOption));
options->setStayOnTop(parser.isSet(stayOnTopOption));
@@ -147,31 +257,179 @@ static void parseArguments(const QStringList& arguments, Options *options)
}
}
-int main(int argc, char** argv)
+/*
+ * class MasterApplication
+ */
+
+MasterApplication::MasterApplication(int &argc, char **argv)
+ : Application(argc, argv)
+ , m_window(new MainWindow)
+{
+ Options options;
+ parseArguments(arguments(), &options);
+
+ if (options.remoteOnly()) {
+ QTimer::singleShot(0, this, &QCoreApplication::quit);
+ return;
+ }
+
+ applyOptions(options);
+
+ if (options.hasNoninteractiveOptions()) {
+ QTimer::singleShot(0, this, &QCoreApplication::quit);
+ } else {
+ m_window->init();
+ m_window->show();
+ listenForArguments();
+ }
+}
+
+void MasterApplication::listenForArguments()
+{
+ QLocalServer *server = new QLocalServer(this);
+
+ // Remove possibly stale server socket
+ QLocalServer::removeServer(serverName());
+
+ if (!server->listen(serverName())) {
+ qWarning() << "Failed to listen on local socket: " << server->errorString();
+ return;
+ }
+
+ auto handleConnection = [this](QLocalSocket *connection) {
+ auto QLocalSocket_error = static_cast<void (QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error);
+ connect(connection, QLocalSocket_error, this, [connection]() {
+ qWarning() << "Error receiving arguments:" << connection->errorString();
+ connection->close();
+ });
+
+ connect(connection, &QLocalSocket::readyRead, this, [this, connection]() {
+ QStringList arguments;
+
+ QDataStream in(connection);
+ in.startTransaction();
+ in >> arguments;
+ if (!in.commitTransaction())
+ return;
+
+ Options options;
+ parseArguments(arguments, &options);
+ applyOptions(options);
+
+ connection->close();
+
+ if (!options.hasNoninteractiveOptions() && !options.remoteOnly())
+ m_window->activateWindow();
+ });
+
+ connect(connection, &QLocalSocket::disconnected, connection, &QObject::deleteLater);
+ };
+
+ connect(server, &QLocalServer::newConnection, this, [this, server, handleConnection]() {
+ while (QLocalSocket *connection = server->nextPendingConnection())
+ handleConnection(connection);
+ });
+}
+
+void MasterApplication::applyOptions(const Options &options)
{
- QApplication app(argc, argv);
- app.setApplicationName("QmlLiveBench");
- app.setOrganizationDomain(QLatin1String(QMLLIVE_ORGANIZATION_DOMAIN));
- app.setOrganizationName(QLatin1String(QMLLIVE_ORGANIZATION_NAME));
- app.setAttribute(Qt::AA_NativeWindows, true);
- app.setAttribute(Qt::AA_ImmediateWidgetCreation, true);
+ if (!options.workspace().isEmpty())
+ m_window->setWorkspace(QDir(options.workspace()).absolutePath());
+
+ if (!options.pluginPath().isEmpty()) {
+ if (!m_window->isInitialized())
+ m_window->setPluginPath(QDir(options.pluginPath()).absolutePath());
+ else
+ qDebug() << "Ignoring attempt to set plugin path after initialization.";
+ }
+
+ if (!options.importPaths().isEmpty()) {
+ if (!m_window->isInitialized())
+ m_window->setImportPaths(options.importPaths());
+ else
+ qDebug() << "Ignoring attempt to set import paths after initialization.";
+ }
- setDarkStyle(&app);
+ if (!options.activeDocument().isEmpty()) {
+ m_window->activateDocument(options.activeDocument());
+ }
- Options *options = Options::instance();
- parseArguments(app.arguments(), options);
+ if (options.stayOnTop()) {
+ m_window->setStaysOnTop(true);
+ }
- if (!options->hostsToAdd().isEmpty()) {
- QSettings s;
- foreach (const Options::HostOptions &host, options->hostsToAdd()) {
- HostModel::addOrUpdateHost(&s, host.name, host.address, host.port);
+ if (!options.hostsToAdd().isEmpty()) {
+ if (!m_window->isInitialized()) {
+ QSettings s;
+ foreach (const Options::HostOptions &host, options.hostsToAdd()) {
+ HostModel::addOrUpdateHost(&s, host.name, host.address, host.port);
+ }
+ } else {
+ foreach (const Options::HostOptions &hostOptions, options.hostsToAdd()) {
+ Host *host = m_window->hostModel()->host(hostOptions.name);
+ if (host == 0) {
+ host = new Host;
+ host->setName(hostOptions.name);
+ m_window->hostModel()->addHost(host);
+ }
+ host->setAddress(hostOptions.address);
+ host->setPort(hostOptions.port);
+ }
}
- return EXIT_SUCCESS;
}
+}
+
+/*
+ * class SlaveApplication
+ */
+
+SlaveApplication::SlaveApplication(int &argc, char **argv)
+ : Application(argc, argv)
+{
+ Options options;
+ parseArguments(arguments(), &options);
+
+ if (!options.remoteOnly() && !options.hasNoninteractiveOptions())
+ qInfo() << "Another instance running. Activating...";
+
+ warnAboutIgnoredOptions(options);
+ forwardArguments();
+}
- MainWindow win;
- win.init(options); // Parse and apply command line and settings file options
- win.show();
+void SlaveApplication::warnAboutIgnoredOptions(const Options &options)
+{
+ if (!options.pluginPath().isEmpty())
+ qWarning() << "Ignoring --pluginpath option";
- return app.exec();
+ if (!options.importPaths().isEmpty())
+ qWarning() << "Ignoring --importpaths option";
}
+
+void SlaveApplication::forwardArguments()
+{
+ QLocalSocket *socket = new QLocalSocket(this);
+
+ auto QLocalSocket_error = static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error);
+ connect(socket, QLocalSocket_error, this, [socket]() {
+ qCritical() << "Error forwarding arguments:" << socket->errorString();
+ exit(1);
+ });
+
+ connect(socket, &QLocalSocket::connected, this, [socket]() {
+ QDataStream out(socket);
+ out << arguments();
+ socket->disconnectFromServer();
+ });
+
+ connect(socket, &QLocalSocket::disconnected, this, &QCoreApplication::quit);
+
+ socket->connectToServer(serverName());
+}
+
+int main(int argc, char** argv)
+{
+ QScopedPointer<Application> app(Application::create(argc, argv));
+ return app->exec();
+}
+
+#include "main.moc"
diff --git a/src/bench/mainwindow.cpp b/src/bench/mainwindow.cpp
index 4de00a0..ac418c9 100644
--- a/src/bench/mainwindow.cpp
+++ b/src/bench/mainwindow.cpp
@@ -52,6 +52,7 @@
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
+ , m_initialized(false)
, m_workspace(new WorkspaceView())
, m_log(new LogView(true, this))
, m_hostManager(new HostManager(this))
@@ -209,31 +210,13 @@ void MainWindow::setupMenuBar()
about->setMenuRole(QAction::AboutRole);
}
-void MainWindow::init(Options *options)
+void MainWindow::init()
{
- Q_ASSERT(options);
- if (!options->workspace().isEmpty()) {
- setWorkspace(QDir(options->workspace()).absolutePath());
- }
- if (!options->pluginPath().isEmpty()) {
- setPluginPath(QDir(options->pluginPath()).absolutePath());
- }
- if (!options->importPaths().isEmpty()) {
- setImportPaths(options->importPaths());
- }
- if (!options->activeDocument().isEmpty()) {
- activateDocument(options->activeDocument());
- }
-
- if (options->stayOnTop()) {
- setStaysOnTop(true);
- }
-
QSettings s;
restoreGeometry(s.value("geometry").toByteArray());
//Only set the workspace if we didn't already set it by command line
- if (options->workspace().isNull()) {
- setWorkspace(s.value("workspace").toString());
+ if (m_workspacePath.isEmpty()) {
+ setWorkspace(s.value("workspace", QDir::currentPath()).toString());
}
if (s.value("http_proxy/enabled").toBool()) {
@@ -258,7 +241,7 @@ void MainWindow::init(Options *options)
updateRecentFolder();
//Only set the workspace if we didn't already set it by command line
- if (options->activeDocument().isNull()) {
+ if (m_node->activeDocument().isEmpty()) {
if (s.contains("activeDocument")) {
activateDocument(s.value("activeDocument").toString());
} else {
@@ -270,6 +253,8 @@ void MainWindow::init(Options *options)
m_hostModel->restoreFromSettings(&s);
restoreState(s.value("windowState").toByteArray());
+
+ m_initialized = true;
}
void MainWindow::writeSettings()
@@ -277,7 +262,7 @@ void MainWindow::writeSettings()
QSettings s;
s.setValue("geometry", saveGeometry());
s.setValue("windowState", saveState());
- s.setValue("workspace", m_hub->workspace());
+ s.setValue("workspace", m_workspacePath);
s.setValue("activeDocument", m_node->activeDocument().toLocalFile());
s.beginWriteArray("recentFolder");
@@ -355,6 +340,7 @@ void MainWindow::slowDownAnimations(bool enable)
void MainWindow::setWorkspace(const QString& path)
{
+ m_workspacePath = path;
m_workspace->setRootPath(path);
m_node->setWorkspace(path);
m_hub->setWorkspace(path);
diff --git a/src/bench/mainwindow.h b/src/bench/mainwindow.h
index 88cd398..7b85c21 100644
--- a/src/bench/mainwindow.h
+++ b/src/bench/mainwindow.h
@@ -62,7 +62,11 @@ public:
void setPluginPath(const QString& path);
void setImportPaths(const QStringList& pathList);
void setStaysOnTop(bool enabled);
- void init(Options *options);
+ void init();
+ bool isInitialized() const { return m_initialized; }
+
+ HostModel *hostModel() const { return m_hostModel; }
+
protected:
void closeEvent(QCloseEvent *event);
void showEvent(QShowEvent *event);
@@ -93,8 +97,12 @@ private slots:
private:
void updateRecentFolder(const QString &path = QString());
+
+private:
+ bool m_initialized;
WindowWidget *m_ww;
WorkspaceView *m_workspace;
+ QString m_workspacePath;
LogView *m_log;
QUrl m_currentSource;
QDockWidget *m_logDock;
diff --git a/src/bench/options.cpp b/src/bench/options.cpp
index 13549c1..fa0102f 100644
--- a/src/bench/options.cpp
+++ b/src/bench/options.cpp
@@ -31,20 +31,30 @@
#include "options.h"
-Options *Options::s_instance(nullptr);
-
Options::Options(QObject *parent)
: QObject(parent)
+ , m_remoteOnly(false)
+ , m_stayOnTop(false)
+{
+
+}
+bool Options::hasNoninteractiveOptions() const
{
+ if (!m_hostsToAdd.isEmpty())
+ return true;
+ return false;
}
-Options *Options::instance() {
- if (!s_instance) {
- s_instance = new Options(QCoreApplication::instance());
- }
- return s_instance;
+bool Options::remoteOnly() const
+{
+ return m_remoteOnly;
+}
+
+void Options::setRemoteOnly(bool remoteOnly)
+{
+ m_remoteOnly = remoteOnly;
}
QString Options::activeDocument() const
diff --git a/src/bench/options.h b/src/bench/options.h
index 93c6a0b..f253cbd 100644
--- a/src/bench/options.h
+++ b/src/bench/options.h
@@ -44,10 +44,13 @@ public:
int port = 10234;
};
-private:
- explicit Options(QObject *parent = 0);
public:
- static Options *instance();
+ explicit Options(QObject *parent = 0);
+
+ bool hasNoninteractiveOptions() const;
+
+ bool remoteOnly() const;
+ void setRemoteOnly(bool remoteOnly);
QString activeDocument() const;
void setActiveDocument(const QString &activeDocument);
@@ -70,7 +73,7 @@ public:
void addHostToAdd(const HostOptions &hostOptions);
private:
- static Options *s_instance;
+ bool m_remoteOnly;
QString m_activeDocument;
QString m_workspace;
QString m_pluginPath;