single instance handled
This commit is contained in:
parent
a9f179cab3
commit
e3b0610b65
|
@ -3,6 +3,7 @@
|
|||
#include "DI.h"
|
||||
#include "../Core/Engine.h"
|
||||
#include "../Core/StatusSyntaxHighlighter.h"
|
||||
#include "../Core/SingleInstance.h"
|
||||
#include "../Common/Utils.h"
|
||||
#include "../Global/LocalAppSettings.h"
|
||||
#include "../Global/LocalAccountSettings.h"
|
||||
|
@ -30,6 +31,14 @@ AppController::AppController()
|
|||
Utils::ensureDirectories();
|
||||
}
|
||||
|
||||
void registerTypes()
|
||||
{
|
||||
// Once we fully move to c++ we should include the following line instead the line below it (it's here just to align with the current qml files).
|
||||
// qmlRegisterType<AppWindow>("AppWindow", 0 , 1, "AppWindow");
|
||||
qmlRegisterType<AppWindow>("DotherSide", 0 , 1, "StatusWindow");
|
||||
qmlRegisterType<StatusSyntaxHighlighterHelper>("DotherSide", 0, 1, "StatusSyntaxHighlighter");
|
||||
}
|
||||
|
||||
void registerResources()
|
||||
{
|
||||
Engine::instance()->addImportPath("qrc:/./StatusQ/src");
|
||||
|
@ -51,10 +60,7 @@ int AppController::exec(int& argc, char** argv)
|
|||
{
|
||||
int code;
|
||||
|
||||
// Once we fully move to c++ we should include the following line instead the line below it (it's here just to align with the current qml files).
|
||||
// qmlRegisterType<AppWindow>("AppWindow", 0 , 1, "AppWindow");
|
||||
qmlRegisterType<AppWindow>("DotherSide", 0 , 1, "StatusWindow");
|
||||
qmlRegisterType<StatusSyntaxHighlighterHelper>("DotherSide", 0, 1, "StatusSyntaxHighlighter");
|
||||
registerTypes();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -69,21 +75,49 @@ int AppController::exec(int& argc, char** argv)
|
|||
// app.installTranslator(&translator);
|
||||
// }
|
||||
|
||||
auto md5DataDir = QString(QCryptographicHash::hash(Utils::defaultDataDir().toLatin1(), QCryptographicHash::Md5).toHex());
|
||||
auto openUri = ""; // CLI uri should be used here ("status-im:// URI to open a chat or other")
|
||||
auto singleInstance = std::make_unique<SingleInstance>(md5DataDir, openUri);
|
||||
|
||||
if (!singleInstance->isFirstInstance())
|
||||
{
|
||||
auto err = "Terminating the app as the second instance";
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
|
||||
auto rootModule = Injector.create<Modules::ModuleBuilder>()();
|
||||
rootModule->load();
|
||||
|
||||
registerResources();
|
||||
|
||||
AppWindow* appWindow = nullptr;
|
||||
QString qmlFile = QStringLiteral("qrc:/main.qml");
|
||||
Engine::create(qmlFile);
|
||||
QObject::connect(Engine::instance(), &Engine::objectCreated, &app,
|
||||
[url = qmlFile](QObject* obj, const QUrl& objUrl) {
|
||||
if(!obj && url == objUrl.toString())
|
||||
|
||||
auto handleAppWinCreation = [url = qmlFile, &appWindow, &singleInstance](QObject* obj, const QUrl& objUrl) {
|
||||
if(url == objUrl.toString())
|
||||
{
|
||||
auto err = "Failed to create: " + url;
|
||||
throw std::runtime_error(err.toStdString());
|
||||
if(obj)
|
||||
{
|
||||
AppWindow* appWindow = qobject_cast<AppWindow*>(obj);
|
||||
QObject::connect(singleInstance.get(), &SingleInstance::secondInstanceDetected, [appWindow](){
|
||||
appWindow->makeTheAppActive();
|
||||
});
|
||||
|
||||
QObject::connect(singleInstance.get(), &SingleInstance::eventReceived, [](const QString& eventStr){
|
||||
qInfo() << "Received event: " << eventStr;
|
||||
// We need to handle it here.
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto err = "Failed to create: " + url;
|
||||
throw std::runtime_error(err.toStdString());
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Engine::create(qmlFile);
|
||||
QObject::connect(Engine::instance(), &Engine::objectCreated, &app, handleAppWinCreation);
|
||||
|
||||
code = app.exec();
|
||||
}
|
||||
|
|
|
@ -35,6 +35,13 @@ bool AppWindow::isFullScreen() const
|
|||
return m_isFullScreen;
|
||||
}
|
||||
|
||||
void AppWindow::makeTheAppActive()
|
||||
{
|
||||
show();
|
||||
raise();
|
||||
requestActivate();
|
||||
}
|
||||
|
||||
void AppWindow::removeTitleBar()
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace Status
|
|||
Q_INVOKABLE void toggleFullScreen();
|
||||
|
||||
bool isFullScreen() const;
|
||||
void makeTheAppActive();
|
||||
|
||||
Q_INVOKABLE void updatePosition() {
|
||||
auto point = QPoint(screen()->geometry().center().x() - geometry().width() / 2,
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
#include "SingleInstance.h"
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
using namespace Status;
|
||||
|
||||
namespace {
|
||||
const int ReadWriteTimeoutMs = 1000;
|
||||
}
|
||||
|
||||
SingleInstance::SingleInstance(const QString &uniqueName, const QString &eventStr, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_localServer(new QLocalServer(this))
|
||||
{
|
||||
QString socketName = uniqueName;
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
socketName = QString("/tmp/%1").arg(socketName);
|
||||
#endif
|
||||
|
||||
QLocalSocket localSocket;
|
||||
localSocket.connectToServer(socketName);
|
||||
|
||||
// the first instance start will be delayed by this timeout (ms) to ensure there are no other instances.
|
||||
// note: this is an ad-hoc timeout value selected based on prior experience.
|
||||
if (!localSocket.waitForConnected(100)) {
|
||||
connect(m_localServer, &QLocalServer::newConnection, this, &SingleInstance::handleNewConnection);
|
||||
// on *nix a crashed process will leave /tmp/xyz file preventing to start a new server.
|
||||
// therefore, if we were unable to connect, then we assume the server died and we need to clean up.
|
||||
// p.s. on Windows, this function does nothing.
|
||||
QLocalServer::removeServer(socketName);
|
||||
if (!m_localServer->listen(socketName)) {
|
||||
qWarning() << "QLocalServer::listen(" << socketName << ") failed";
|
||||
}
|
||||
} else if (!eventStr.isEmpty()) {
|
||||
localSocket.write(eventStr.toUtf8() + '\n');
|
||||
localSocket.waitForBytesWritten(ReadWriteTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
SingleInstance::~SingleInstance()
|
||||
{
|
||||
if (m_localServer->isListening()) {
|
||||
m_localServer->close();
|
||||
}
|
||||
}
|
||||
|
||||
bool SingleInstance::isFirstInstance() const
|
||||
{
|
||||
return m_localServer->isListening();
|
||||
}
|
||||
|
||||
void SingleInstance::handleNewConnection()
|
||||
{
|
||||
emit secondInstanceDetected();
|
||||
|
||||
auto socket = m_localServer->nextPendingConnection();
|
||||
if (socket->waitForReadyRead(ReadWriteTimeoutMs) && socket->canReadLine()) {
|
||||
auto event = socket->readLine();
|
||||
emit eventReceived(QString::fromUtf8(event));
|
||||
}
|
||||
|
||||
socket->deleteLater();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
class QLocalServer;
|
||||
|
||||
namespace Status {
|
||||
|
||||
class SingleInstance : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// uniqueName - the name of named pipe
|
||||
// eventStr - optional event to send if another instance is detected
|
||||
explicit SingleInstance(const QString& uniqueName, const QString& eventStr, QObject* parent = nullptr);
|
||||
~SingleInstance() override;
|
||||
|
||||
bool isFirstInstance() const;
|
||||
|
||||
signals:
|
||||
void secondInstanceDetected();
|
||||
void eventReceived(const QString& eventStr);
|
||||
|
||||
private slots:
|
||||
void handleNewConnection();
|
||||
|
||||
private:
|
||||
QLocalServer* m_localServer;
|
||||
};
|
||||
}
|
|
@ -174,7 +174,10 @@ StatusWindow {
|
|||
}
|
||||
|
||||
Connections {
|
||||
target: singleInstance
|
||||
// This handling should be part of backend code, but because of compatibility with the current Nim App
|
||||
// since c++ and Nim app are sharing the same/identical qml code we are still not allowed to remove this
|
||||
// completely, and that's why we have this `target` set to null in case of c++ app.
|
||||
target: !Constants.isCppApp? singleInstance : null
|
||||
|
||||
onSecondInstanceDetected: {
|
||||
console.log("User attempted to run the second instance of the application")
|
||||
|
|
Loading…
Reference in New Issue