mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-06 14:29:54 +08:00
use socket for single instance guard
This commit is contained in:
parent
8a1558f4da
commit
220fba09a9
@ -1,184 +1,225 @@
|
|||||||
#include "singleinstanceguard.h"
|
#include "singleinstanceguard.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QDataStream>
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
#include <utils/utils.h>
|
#include <utils/utils.h>
|
||||||
|
|
||||||
using namespace vnotex;
|
using namespace vnotex;
|
||||||
|
|
||||||
const QString SingleInstanceGuard::c_memKey = "vnotex_shared_memory";
|
const QString SingleInstanceGuard::c_serverName = "vnote";
|
||||||
const int SingleInstanceGuard::c_magic = 376686683;
|
|
||||||
|
|
||||||
SingleInstanceGuard::SingleInstanceGuard()
|
SingleInstanceGuard::SingleInstanceGuard()
|
||||||
: m_online(false),
|
|
||||||
m_sharedMemory(c_memKey)
|
|
||||||
{
|
{
|
||||||
|
qInfo() << "guarding is on";
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleInstanceGuard::~SingleInstanceGuard()
|
||||||
|
{
|
||||||
|
qInfo() << "guarding is off";
|
||||||
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleInstanceGuard::tryRun()
|
bool SingleInstanceGuard::tryRun()
|
||||||
{
|
{
|
||||||
m_online = false;
|
Q_ASSERT(!m_online);
|
||||||
|
|
||||||
// If we can attach to the sharedmemory, there is another instance running.
|
#if defined(Q_OS_WIN)
|
||||||
// In Linux, crashes may cause the shared memory segment remains. In this case,
|
// On Windows, multiple servers on the same name are allowed.
|
||||||
// this will attach to the old segment, then exit, freeing the old segment.
|
m_client = tryConnect();
|
||||||
if (m_sharedMemory.attach()) {
|
if (m_client) {
|
||||||
qInfo() << "another instance is running";
|
// There is one server running and we are now connected, so we could not continue.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to create it.
|
m_server = tryListen();
|
||||||
bool ret = m_sharedMemory.create(sizeof(SharedStruct));
|
if (m_server) {
|
||||||
if (ret) {
|
// We are the lucky one.
|
||||||
// We created it.
|
qInfo() << "guard succeeds to run";
|
||||||
m_sharedMemory.lock();
|
} else {
|
||||||
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
|
// We still allow the guard to run. There maybe a bug need to fix.
|
||||||
str->m_magic = c_magic;
|
qWarning() << "failed to connect to an existing instance or establish a new local server";
|
||||||
str->m_filesBufIdx = 0;
|
}
|
||||||
str->m_askedToShow = false;
|
#else
|
||||||
m_sharedMemory.unlock();
|
m_server = tryListen();
|
||||||
|
if (m_server) {
|
||||||
|
// We are the lucky one.
|
||||||
|
qInfo() << "guard succeeds to run";
|
||||||
|
} else {
|
||||||
|
// Here we are sure there is another instance running. But we still use a socket to connect to make sure.
|
||||||
|
m_client = tryConnect();
|
||||||
|
if (m_client) {
|
||||||
|
// We are sure there is another instance running.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still allow the guard to run. There maybe a bug need to fix.
|
||||||
|
qWarning() << "failed to connect to an existing instance or establish a new local server";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
setupServer();
|
||||||
|
|
||||||
m_online = true;
|
m_online = true;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
qCritical() << "fail to create shared memory segment";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleInstanceGuard::openExternalFiles(const QStringList &p_files)
|
void SingleInstanceGuard::requestOpenFiles(const QStringList &p_files)
|
||||||
{
|
{
|
||||||
if (p_files.isEmpty()) {
|
if (p_files.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_sharedMemory.isAttached()) {
|
void SingleInstanceGuard::requestShow()
|
||||||
if (!m_sharedMemory.attach()) {
|
{
|
||||||
qCritical() << "fail to attach to the shared memory segment"
|
Q_ASSERT(!m_online);
|
||||||
<< (m_sharedMemory.error() ? m_sharedMemory.errorString() : "");
|
if (!m_client || m_client->state() != QLocalSocket::ConnectedState) {
|
||||||
|
qWarning() << "failed to request show" << m_client->errorString();
|
||||||
return ;
|
return ;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int idx = 0;
|
sendRequest(m_client.data(), OpCode::Show, QString());
|
||||||
int tryCount = 100;
|
|
||||||
while (tryCount--) {
|
|
||||||
m_sharedMemory.lock();
|
|
||||||
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
|
|
||||||
V_ASSERT(str->m_magic == c_magic);
|
|
||||||
for (; idx < p_files.size(); ++idx) {
|
|
||||||
if (p_files[idx].size() + 1 > FilesBufCount) {
|
|
||||||
// Skip this long long name file.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!appendFileToBuffer(str, p_files[idx])) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_sharedMemory.unlock();
|
|
||||||
|
|
||||||
if (idx < p_files.size()) {
|
|
||||||
Utils::sleepWait(500);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleInstanceGuard::appendFileToBuffer(SharedStruct *p_str, const QString &p_file)
|
|
||||||
{
|
|
||||||
if (p_file.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int strSize = p_file.size();
|
|
||||||
if (strSize + 1 > FilesBufCount - p_str->m_filesBufIdx) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put the size first.
|
|
||||||
p_str->m_filesBuf[p_str->m_filesBufIdx++] = (ushort)strSize;
|
|
||||||
const QChar *data = p_file.constData();
|
|
||||||
for (int i = 0; i < strSize; ++i) {
|
|
||||||
p_str->m_filesBuf[p_str->m_filesBufIdx++] = data[i].unicode();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList SingleInstanceGuard::fetchFilesToOpen()
|
|
||||||
{
|
|
||||||
QStringList files;
|
|
||||||
|
|
||||||
if (!m_online) {
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(m_sharedMemory.isAttached());
|
|
||||||
m_sharedMemory.lock();
|
|
||||||
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
|
|
||||||
V_ASSERT(str->m_magic == c_magic);
|
|
||||||
Q_ASSERT(str->m_filesBufIdx <= FilesBufCount);
|
|
||||||
int idx = 0;
|
|
||||||
while (idx < str->m_filesBufIdx) {
|
|
||||||
int strSize = str->m_filesBuf[idx++];
|
|
||||||
Q_ASSERT(strSize <= str->m_filesBufIdx - idx);
|
|
||||||
QString file;
|
|
||||||
for (int i = 0; i < strSize; ++i) {
|
|
||||||
file.append(QChar(str->m_filesBuf[idx++]));
|
|
||||||
}
|
|
||||||
|
|
||||||
files.append(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
str->m_filesBufIdx = 0;
|
|
||||||
m_sharedMemory.unlock();
|
|
||||||
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleInstanceGuard::showInstance()
|
|
||||||
{
|
|
||||||
if (!m_sharedMemory.isAttached()) {
|
|
||||||
if (!m_sharedMemory.attach()) {
|
|
||||||
qCritical() << "fail to attach to the shared memory segment"
|
|
||||||
<< (m_sharedMemory.error() ? m_sharedMemory.errorString() : "");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_sharedMemory.lock();
|
|
||||||
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
|
|
||||||
V_ASSERT(str->m_magic == c_magic);
|
|
||||||
str->m_askedToShow = true;
|
|
||||||
m_sharedMemory.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleInstanceGuard::fetchAskedToShow()
|
|
||||||
{
|
|
||||||
if (!m_online) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(m_sharedMemory.isAttached());
|
|
||||||
m_sharedMemory.lock();
|
|
||||||
SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
|
|
||||||
V_ASSERT(str->m_magic == c_magic);
|
|
||||||
bool ret = str->m_askedToShow;
|
|
||||||
str->m_askedToShow = false;
|
|
||||||
m_sharedMemory.unlock();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleInstanceGuard::exit()
|
void SingleInstanceGuard::exit()
|
||||||
{
|
{
|
||||||
if (!m_online) {
|
m_online = false;
|
||||||
|
|
||||||
|
if (m_client) {
|
||||||
|
m_client->disconnectFromServer();
|
||||||
|
m_client.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_server) {
|
||||||
|
m_server->close();
|
||||||
|
m_server.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<QLocalSocket> SingleInstanceGuard::tryConnect()
|
||||||
|
{
|
||||||
|
auto socket = QSharedPointer<QLocalSocket>::create();
|
||||||
|
socket->connectToServer(c_serverName);
|
||||||
|
if (socket->waitForConnected(200)) {
|
||||||
|
// Connected.
|
||||||
|
qDebug() << "socket connected to server" << c_serverName;
|
||||||
|
return socket;
|
||||||
|
} else {
|
||||||
|
qDebug() << "socket connect timeout";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<QLocalServer> SingleInstanceGuard::tryListen()
|
||||||
|
{
|
||||||
|
auto server = QSharedPointer<QLocalServer>::create();
|
||||||
|
bool ret = server->listen(c_serverName);
|
||||||
|
if (!ret && server->serverError() == QAbstractSocket::AddressInUseError) {
|
||||||
|
// On Unix, a previous crash may leave a server running.
|
||||||
|
// Clean up and try again.
|
||||||
|
QLocalServer::removeServer(c_serverName);
|
||||||
|
ret = server->listen(c_serverName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
qDebug() << "local server listening on" << c_serverName;
|
||||||
|
return server;
|
||||||
|
} else {
|
||||||
|
qDebug() << "failed to start local server";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleInstanceGuard::setupServer()
|
||||||
|
{
|
||||||
|
if (!m_server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(m_sharedMemory.isAttached());
|
connect(m_server.data(), &QLocalServer::newConnection,
|
||||||
m_sharedMemory.detach();
|
this, [this]() {
|
||||||
m_online = false;
|
auto socket = m_server->nextPendingConnection();
|
||||||
|
if (socket) {
|
||||||
|
qInfo() << "local server receives new connect" << socket;
|
||||||
|
if (m_ongoingConnect) {
|
||||||
|
qWarning() << "drop the connection since there is one ongoing connect";
|
||||||
|
socket->disconnectFromServer();
|
||||||
|
socket->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ongoingConnect = true;
|
||||||
|
m_command.clear();
|
||||||
|
|
||||||
|
connect(socket, &QLocalSocket::disconnected,
|
||||||
|
this, [this, socket]() {
|
||||||
|
Q_ASSERT(m_ongoingConnect);
|
||||||
|
socket->deleteLater();
|
||||||
|
m_ongoingConnect = false;
|
||||||
|
});
|
||||||
|
connect(socket, &QLocalSocket::readyRead,
|
||||||
|
this, [this, socket]() {
|
||||||
|
receiveCommand(socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleInstanceGuard::receiveCommand(QLocalSocket *p_socket)
|
||||||
|
{
|
||||||
|
QDataStream inStream;
|
||||||
|
inStream.setDevice(p_socket);
|
||||||
|
inStream.setVersion(QDataStream::Qt_5_12);
|
||||||
|
|
||||||
|
if (m_command.m_opCode == OpCode::Null) {
|
||||||
|
// Relies on the fact that QDataStream serializes a quint32 into
|
||||||
|
// sizeof(quint32) bytes.
|
||||||
|
if (p_socket->bytesAvailable() < (int)sizeof(quint32) * 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 opCode = 0;
|
||||||
|
inStream >> opCode;
|
||||||
|
m_command.m_opCode = static_cast<OpCode>(opCode);
|
||||||
|
inStream >> m_command.m_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_socket->bytesAvailable() < m_command.m_size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "op code" << m_command.m_opCode << m_command.m_size;
|
||||||
|
|
||||||
|
switch (m_command.m_opCode) {
|
||||||
|
case OpCode::Show:
|
||||||
|
Q_ASSERT(m_command.m_size == 0);
|
||||||
|
emit showRequested();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
qWarning() << "unknown op code" << m_command.m_opCode;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleInstanceGuard::sendRequest(QLocalSocket *p_socket, OpCode p_code, const QString &p_payload)
|
||||||
|
{
|
||||||
|
QByteArray block;
|
||||||
|
QDataStream out(&block, QIODevice::WriteOnly);
|
||||||
|
out.setVersion(QDataStream::Qt_5_12);
|
||||||
|
out << static_cast<quint32>(p_code);
|
||||||
|
out << static_cast<quint32>(p_payload.size());
|
||||||
|
if (p_payload.size() > 0) {
|
||||||
|
out << p_payload;
|
||||||
|
}
|
||||||
|
p_socket->write(block);
|
||||||
|
if (p_socket->waitForBytesWritten(3000)) {
|
||||||
|
qDebug() << "request sent" << p_code << p_payload.size();
|
||||||
|
} else {
|
||||||
|
qWarning() << "failed to send request" << p_code;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,68 +1,83 @@
|
|||||||
#ifndef SINGLEINSTANCEGUARD_H
|
#ifndef SINGLEINSTANCEGUARD_H
|
||||||
#define SINGLEINSTANCEGUARD_H
|
#define SINGLEINSTANCEGUARD_H
|
||||||
|
|
||||||
#include <QSharedMemory>
|
#include <QObject>
|
||||||
#include <QStringList>
|
#include <QString>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
class QLocalServer;
|
||||||
|
class QLocalSocket;
|
||||||
|
|
||||||
namespace vnotex
|
namespace vnotex
|
||||||
{
|
{
|
||||||
class SingleInstanceGuard
|
class SingleInstanceGuard : public QObject
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
SingleInstanceGuard();
|
SingleInstanceGuard();
|
||||||
|
|
||||||
// Return ture if this is the only instance of VNote.
|
~SingleInstanceGuard();
|
||||||
|
|
||||||
|
// Try to run. Return true on success.
|
||||||
bool tryRun();
|
bool tryRun();
|
||||||
|
|
||||||
// There is already another instance running.
|
// Server API.
|
||||||
// Call this to ask that instance to open external files passed in
|
public:
|
||||||
// via command line arguments.
|
|
||||||
void openExternalFiles(const QStringList &p_files);
|
|
||||||
|
|
||||||
// Ask another instance to show itself.
|
|
||||||
void showInstance();
|
|
||||||
|
|
||||||
// Fetch files from shared memory to open.
|
|
||||||
// Will clear the shared memory.
|
|
||||||
QStringList fetchFilesToOpen();
|
|
||||||
|
|
||||||
// Whether this instance is asked to show itself.
|
|
||||||
bool fetchAskedToShow();
|
|
||||||
|
|
||||||
// A running instance requests to exit.
|
// A running instance requests to exit.
|
||||||
void exit();
|
void exit();
|
||||||
|
|
||||||
|
// Clients API.
|
||||||
|
public:
|
||||||
|
void requestOpenFiles(const QStringList &p_files);
|
||||||
|
|
||||||
|
void requestShow();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void openFilesRequested(const QStringList &p_files);
|
||||||
|
|
||||||
|
void showRequested();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The count of the entries in the buffer to hold the path of the files to open.
|
enum OpCode
|
||||||
enum { FilesBufCount = 1024 };
|
{
|
||||||
|
Null = 0,
|
||||||
struct SharedStruct {
|
Show
|
||||||
// A magic number to identify if this struct is initialized
|
|
||||||
int m_magic;
|
|
||||||
|
|
||||||
// Next empty entry in m_filesBuf.
|
|
||||||
int m_filesBufIdx;
|
|
||||||
|
|
||||||
// File paths to be opened.
|
|
||||||
// Encoded in this way with 2 bytes for each size part.
|
|
||||||
// [size of file1][file1][size of file2][file 2]
|
|
||||||
// Unicode representation of QString.
|
|
||||||
ushort m_filesBuf[FilesBufCount];
|
|
||||||
|
|
||||||
// Whether other instances ask to show the legal instance.
|
|
||||||
bool m_askedToShow;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Append @p_file to the shared struct files buffer.
|
struct Command
|
||||||
// Returns true if succeeds or false if there is no enough space.
|
{
|
||||||
bool appendFileToBuffer(SharedStruct *p_str, const QString &p_file);
|
void clear()
|
||||||
|
{
|
||||||
|
m_opCode = OpCode::Null;
|
||||||
|
m_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool m_online;
|
OpCode m_opCode = OpCode::Null;
|
||||||
|
int m_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
QSharedMemory m_sharedMemory;
|
QSharedPointer<QLocalSocket> tryConnect();
|
||||||
|
|
||||||
static const QString c_memKey;
|
QSharedPointer<QLocalServer> tryListen();
|
||||||
static const int c_magic;
|
|
||||||
|
void setupServer();
|
||||||
|
|
||||||
|
void receiveCommand(QLocalSocket *p_socket);
|
||||||
|
|
||||||
|
void sendRequest(QLocalSocket *p_socket, OpCode p_code, const QString &p_payload);
|
||||||
|
|
||||||
|
// Whether succeeded to run.
|
||||||
|
bool m_online = false;
|
||||||
|
|
||||||
|
QSharedPointer<QLocalSocket> m_client;
|
||||||
|
|
||||||
|
QSharedPointer<QLocalServer> m_server;
|
||||||
|
|
||||||
|
bool m_ongoingConnect = false;
|
||||||
|
|
||||||
|
Command m_command;
|
||||||
|
|
||||||
|
static const QString c_serverName;
|
||||||
};
|
};
|
||||||
} // ns vnotex
|
} // ns vnotex
|
||||||
|
|
||||||
|
19
src/main.cpp
19
src/main.cpp
@ -30,12 +30,6 @@ void initWebEngineSettings();
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
SingleInstanceGuard guard;
|
|
||||||
bool canRun = guard.tryRun();
|
|
||||||
if (!canRun) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTextCodec *codec = QTextCodec::codecForName("UTF8");
|
QTextCodec *codec = QTextCodec::codecForName("UTF8");
|
||||||
if (codec) {
|
if (codec) {
|
||||||
QTextCodec::setCodecForLocale(codec);
|
QTextCodec::setCodecForLocale(codec);
|
||||||
@ -75,12 +69,22 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
initWebEngineSettings();
|
initWebEngineSettings();
|
||||||
|
|
||||||
|
{
|
||||||
const QString iconPath = ":/vnotex/data/core/icons/vnote.ico";
|
const QString iconPath = ":/vnotex/data/core/icons/vnote.ico";
|
||||||
// Make sense only on Windows.
|
// Make sense only on Windows.
|
||||||
app.setWindowIcon(QIcon(iconPath));
|
app.setWindowIcon(QIcon(iconPath));
|
||||||
|
|
||||||
app.setApplicationName(ConfigMgr::c_appName);
|
app.setApplicationName(ConfigMgr::c_appName);
|
||||||
app.setOrganizationName(ConfigMgr::c_orgName);
|
app.setOrganizationName(ConfigMgr::c_orgName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guarding.
|
||||||
|
SingleInstanceGuard guard;
|
||||||
|
bool canRun = guard.tryRun();
|
||||||
|
if (!canRun) {
|
||||||
|
guard.requestShow();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
app.setApplicationVersion(ConfigMgr::getInst().getConfig().getVersion());
|
app.setApplicationVersion(ConfigMgr::getInst().getConfig().getVersion());
|
||||||
@ -125,6 +129,9 @@ int main(int argc, char *argv[])
|
|||||||
window.show();
|
window.show();
|
||||||
VNoteX::getInst().getThemeMgr().setBaseBackground(window.palette().color(QPalette::Base));
|
VNoteX::getInst().getThemeMgr().setBaseBackground(window.palette().color(QPalette::Base));
|
||||||
|
|
||||||
|
QObject::connect(&guard, &SingleInstanceGuard::showRequested,
|
||||||
|
&window, &MainWindow::showMainWindow);
|
||||||
|
|
||||||
window.kickOffOnStart();
|
window.kickOffOnStart();
|
||||||
|
|
||||||
int ret = app.exec();
|
int ret = app.exec();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user