From 3ace62f90d48015fec1bd37ad216466fb666061d Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Fri, 22 Dec 2017 23:00:45 +0100 Subject: Single instance check works again - In Debug builds, startup time is calculated --- BUGS.md | 3 +- docs/manual/Contributing.md | 13 +++++++ src/browser.cpp | 37 +++++++++++++++++++ src/browser.h | 6 ++-- src/main.cpp | 52 +++++++++++++++++---------- src/mainwindow.cpp | 11 +++--- src/singleapplication.cpp | 87 ++++++++++++++++++++++++++++++++++----------- src/singleapplication.h | 22 ++++++++---- 8 files changed, 177 insertions(+), 54 deletions(-) diff --git a/BUGS.md b/BUGS.md index bcf702f..7392a8a 100644 --- a/BUGS.md +++ b/BUGS.md @@ -10,12 +10,13 @@ QUrl always seems to return true when checking if valid url. Workaround is to pr to the search. ### databases-incognito in home -Folder is empty. +https://bugreports.qt.io/browse/QTBUG-62957 ## To do list List of things to do before 1.0 release ### Instance check on startup +- review SingleApplication::SessionParam ### Auto-destruct cookies - cookie whitelist and blacklist diff --git a/docs/manual/Contributing.md b/docs/manual/Contributing.md index ff8e2b2..c6d1bcd 100644 --- a/docs/manual/Contributing.md +++ b/docs/manual/Contributing.md @@ -19,6 +19,19 @@ Requires clang. To set check levels, set the CLAZY_CHECKS environment variable to 'level0,level1', etc. +### Setting up in qbs +1. Create a profile for clazy +``` +qbs-setup-toolchains --type clang /usr/bin/clazy clazy +qbs-config profiles.clazy.cpp.cCompilerName clang +qbs-config profiles.clazy.cpp.cxxCompilerName clazy +``` +2. Create a profile for qt5-clazy +``` +qbs-setup-qt /usr/bin/qmake-qt5 qt5-clazy +qbs-config profiles.qt5-clazy.baseProfile clazy +``` + ### Setting up in QtCreator The simplest way to set it up is to use _clazy_ instead of _clang_. diff --git a/src/browser.cpp b/src/browser.cpp index 7a169e9..7d39035 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -19,6 +19,8 @@ Browser::Browser(int &argc, char *argv[]) : { setApplicationName("smolbote"); setWindowIcon(QIcon(":/icon.svg")); + + connect(this, &Browser::messageAvailable, this, &Browser::createSession); } Browser::~Browser() @@ -94,6 +96,41 @@ MainWindow *Browser::createWindow() return window; } +MainWindow *Browser::createSession(const SessionParam ¶ms) +{ + MainWindow *window = nullptr; + if(params.newWindow || m_windows.isEmpty()) { + window = createWindow(); + window->setProfile(profile(params.profile)); + } else { + // reverse-iterate through windows to check for window with the same profile + for(auto it = m_windows.rbegin(); it != m_windows.rend(); ++it) { + if((*it)->profile()->storageName() == params.profile) { + window = *it; + break; + } + } + // if none is found, create one + if(window == nullptr) { + window = createWindow(); + window->setProfile(profile(params.profile)); + } + } + + Q_CHECK_PTR(window); + + if(params.urls.isEmpty()) { + // no URLs were given + window->newTab(QUrl::fromUserInput(m_config->value("profile.homepage").value().c_str())); + } else { + for(const QUrl &url : params.urls) { + window->newTab(url); + } + } + + return window; +} + std::shared_ptr Browser::profile(const QString name) { return m_profiles[name]; diff --git a/src/browser.h b/src/browser.h index 3b8975f..52dc97e 100644 --- a/src/browser.h +++ b/src/browser.h @@ -33,14 +33,14 @@ public: std::shared_ptr profile(const QString name); // QStringList profiles(); - //MainWindow *activeWindow(); - public slots: - MainWindow* createWindow(); + MainWindow *createSession(const SessionParam ¶ms); private: Q_DISABLE_COPY(Browser) + MainWindow *createWindow(); + std::shared_ptr m_config; QVector m_windows; diff --git a/src/main.cpp b/src/main.cpp index 7433cc8..e8307c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,11 @@ #include #include "mainwindow.h" +// startup time measuring +#ifdef QT_DEBUG +#include +#endif + // read config into std::string, supports qrc inline std::string readConfig(QString path) { @@ -60,6 +65,11 @@ int main(int argc, char *argv[]) instance.setApplicationVersion("1.0.0"); #endif +#ifdef QT_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + QCommandLineParser parser; parser.setApplicationDescription("yet another Qt browser"); parser.addHelpOption(); @@ -87,15 +97,13 @@ int main(int argc, char *argv[]) profileOption.setDefaultValue(""); parser.addOption(profileOption); - QCommandLineOption newInstanceOption("new-instance", "Skip instance check at startup"); - parser.addOption(newInstanceOption); + QCommandLineOption socketOption("socket", "Set socket to use for IPC, leave blank for default, 'none' to disable.", "name"); + socketOption.setDefaultValue(""); + parser.addOption(socketOption); QCommandLineOption newWindowOption("in-new-window", "Open URL in new window"); parser.addOption(newWindowOption); - QCommandLineOption newTabOption("in-new-tab", "Open URL in new tab"); - parser.addOption(newTabOption); - parser.addPositionalArgument("URL", "URL(s) to open"); parser.process(instance); @@ -111,7 +119,24 @@ int main(int argc, char *argv[]) return 0; } + Browser::SessionParam sessionParams; + sessionParams.profile = parser.value(profileOption); + sessionParams.newWindow = parser.isSet(newWindowOption); + for(const QString &url : parser.positionalArguments()) { + sessionParams.urls.append(QUrl::fromUserInput(url)); + } + // TODO: check for other instances + // if we socket hasn't been disabled (socket is not none) + if(parser.value(socketOption) != "none") { + bool bindOk = instance.bindLocalSocket(parser.value(socketOption)); + if(bindOk) { + qDebug("Connected to local socket: %s", qUtf8Printable(instance.serverName())); + } else { + // pass arguments to new instance + return instance.sendMessage(sessionParams); + } + } std::shared_ptr config = std::make_shared(); config->readDefaultConfiguration(readConfig(parser.value(defaultConfigOption))); @@ -148,19 +173,10 @@ int main(int argc, char *argv[]) instance.setConfiguration(config); instance.loadProfiles(); - MainWindow* mainWindow = instance.createWindow(); - if(parser.isSet(profileOption)) { - mainWindow->setProfile(instance.profile(parser.value(profileOption))); - } - - if(parser.positionalArguments().isEmpty()) { - // no URLs were given - mainWindow->newTab(QUrl::fromUserInput(config->value("profile.homepage").value().c_str())); - } else { - for(const QString &url : parser.positionalArguments()) { - mainWindow->newTab(QUrl::fromUserInput(url)); - } - } + instance.createSession(sessionParams); +#ifdef QT_DEBUG + qDebug("Startup complete in %lldms", timer.elapsed()); +#endif return instance.exec(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5c3048c..66d0092 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -182,12 +182,11 @@ void MainWindow::newTab(const QUrl &url) MainWindow *MainWindow::newWindow(const QUrl &url) { Browser *instance = static_cast(qApp->instance()); - MainWindow *window = instance->createWindow(); - window->setProfile(m_profile); - if(!url.isEmpty()) { - window->newTab(url); - } - return window; + Browser::SessionParam params; + params.profile = m_profile->storageName(); + params.newWindow = true; + params.urls.append(url); + return instance->createSession(params); } void MainWindow::focusAddress() diff --git a/src/singleapplication.cpp b/src/singleapplication.cpp index 1dd96e3..e71d1be 100644 --- a/src/singleapplication.cpp +++ b/src/singleapplication.cpp @@ -10,14 +10,47 @@ #include #include #include +#include +#include + +QHash paramStructToHash(const SingleApplication::SessionParam ¶ms) +{ + QHash hashedParams; + hashedParams.insert("profile", params.profile); + hashedParams.insert("newWindow", params.newWindow); + QList urls; + for(const QUrl &url : params.urls) { + urls.append(url); + } + hashedParams.insert("urls", urls); + return hashedParams; +} + +SingleApplication::SessionParam paramStructFromHash(const QHash ¶ms) +{ + SingleApplication::SessionParam structParams; + structParams.profile = params.value("profile").toString(); + structParams.newWindow = params.value("newWindow").toBool(); + for(const QVariant &val : params.value("urls").toList()) { + structParams.urls.append(val.toUrl()); + } + return structParams; +} SingleApplication::SingleApplication(int &argc, char **argv) : QApplication(argc, argv) { +#ifdef Q_OS_UNIX + // could be a path such as "/tmp/foo" + LOCALSERVER_KEY = "smolbote_socket"; +#elif Q_OS_WIN32 + // could be a pipe path such as "\\.\pipe\foo" + LOCALSERVER_KEY = "\\.\pipe\smolbote_socket"; +#endif } SingleApplication::~SingleApplication() { - if(m_localServer) { + if(m_localServer != nullptr) { if(m_localServer->isListening()) { m_localServer->close(); QLocalServer::removeServer(LOCALSERVER_KEY); @@ -25,56 +58,70 @@ SingleApplication::~SingleApplication() } } -void SingleApplication::bindLocalSocket() +/** + * @brief SingleApplication::bindLocalSocket check for running local server by connecting to it + * @return true if no other instance, false otherwise + */ +bool SingleApplication::bindLocalSocket(const QString &name) { - m_localServer = new QLocalServer(this); - connect(m_localServer, &QLocalServer::newConnection, this, &SingleApplication::slot_receiveMessage); + // if a name has been set + if(!name.isEmpty()) { + LOCALSERVER_KEY = name; + } - // check for running local server by connecting to it QLocalSocket socket; socket.connectToServer(LOCALSERVER_KEY); + if(socket.waitForConnected(LOCALSERVER_TIMEOUT)) { - // there is another server - qWarning("Another server is running"); + // another server is running socket.close(); - return; - } else { + return false; + } + + // there is either no such socket, or the socket wasn't cleaned up + else { + m_localServer = new QLocalServer(this); + connect(m_localServer, &QLocalServer::newConnection, this, &SingleApplication::parseMessage); + // no other server QLocalServer::removeServer(LOCALSERVER_KEY); if(!m_localServer->listen(LOCALSERVER_KEY)) { - qWarning("Cannot bind local server [%s]", qUtf8Printable(LOCALSERVER_KEY)); + // for some reason, we still can't bind the socket + return false; } + return true; } } -bool SingleApplication::isRunning() +QString SingleApplication::serverName() const { - return !m_localServer->isListening(); + Q_CHECK_PTR(m_localServer); + return m_localServer->fullServerName(); } -bool SingleApplication::sendMessage(const QHash ¶ms) +int SingleApplication::sendMessage(const SessionParam ¶ms) { QLocalSocket socket; socket.connectToServer(LOCALSERVER_KEY); if(socket.waitForConnected(LOCALSERVER_TIMEOUT)) { QByteArray argumentData; QDataStream ds(&argumentData, QIODevice::WriteOnly); - ds << params; + ds << paramStructToHash(params); socket.write(argumentData); socket.waitForBytesWritten(LOCALSERVER_TIMEOUT); - return true; + return EXIT_SUCCESS; } - return false; + return EXIT_FAILURE; } -void SingleApplication::slot_receiveMessage() +void SingleApplication::parseMessage() { QLocalSocket *socket = m_localServer->nextPendingConnection(); - if(!socket) { - // null socket --> return + // null socket --> return + if(socket == nullptr) { return; } @@ -93,5 +140,5 @@ void SingleApplication::slot_receiveMessage() socket->deleteLater(); - emit messageAvailable(params); + emit messageAvailable(paramStructFromHash(params)); } diff --git a/src/singleapplication.h b/src/singleapplication.h index 053ead2..fd9ce39 100644 --- a/src/singleapplication.h +++ b/src/singleapplication.h @@ -10,29 +10,39 @@ #define SINGLEAPPLICATION_H #include +#include class QLocalServer; class SingleApplication : public QApplication { Q_OBJECT + public: + + struct SessionParam + { + QString profile; + bool newWindow; + QVector urls; + }; + explicit SingleApplication(int &argc, char **argv); ~SingleApplication(); - void bindLocalSocket(); + bool bindLocalSocket(const QString &name = QString()); + QString serverName() const; - bool isRunning(); - bool sendMessage(const QHash ¶ms); + int sendMessage(const SessionParam ¶ms); signals: - void messageAvailable(const QHash ¶ms); + void messageAvailable(const SessionParam ¶ms); private slots: - void slot_receiveMessage(); + void parseMessage(); private: const int LOCALSERVER_TIMEOUT = 500; - const QString LOCALSERVER_KEY = "smolbote_socket"; + QString LOCALSERVER_KEY; QLocalServer *m_localServer = nullptr; }; -- cgit v1.2.1