summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiikka Heikkinen <miikka.heikkinen@qt.io>2018-04-18 17:38:40 +0300
committerMiikka Heikkinen <miikka.heikkinen@qt.io>2018-04-20 13:20:00 +0000
commitfda3aa98013b7e166e3ebc81f743d9f8297200f6 (patch)
treee8175e1f224b3ad94ca8eb55e99e60dd253df297
parent3b6b637a5a7fc492014867ce3ce48c804d8978a4 (diff)
Activate existing instance if desired presentation matches
When starting studio with a command line parameter to launch a presentation, instead of launching a new instance, check if there is and existing instance already showing that presentation and activate it. Task-number: QT3DS-888 Change-Id: I0aa74e40d9796808eb8b07228e85f9dfb382952a Reviewed-by: Tomi Korpipää <tomi.korpipaa@qt.io>
-rw-r--r--src/Authoring/Studio/Qt3DStudio.pro1
-rw-r--r--src/Authoring/Studio/_Win/Application/StudioApp.cpp55
-rw-r--r--src/Authoring/Studio/_Win/Application/StudioApp.h3
-rw-r--r--src/shared/README.txt2
-rw-r--r--src/shared/qtlockedfile/qtlockedfile.cpp158
-rw-r--r--src/shared/qtlockedfile/qtlockedfile.h73
-rw-r--r--src/shared/qtlockedfile/qtlockedfile.pri13
-rw-r--r--src/shared/qtlockedfile/qtlockedfile_unix.cpp107
-rw-r--r--src/shared/qtlockedfile/qtlockedfile_win.cpp195
-rw-r--r--src/shared/qtsingleapplication/qtlocalpeer.cpp191
-rw-r--r--src/shared/qtsingleapplication/qtlocalpeer.h63
-rw-r--r--src/shared/qtsingleapplication/qtsingleapplication.cpp190
-rw-r--r--src/shared/qtsingleapplication/qtsingleapplication.h70
-rw-r--r--src/shared/qtsingleapplication/qtsingleapplication.pri13
14 files changed, 1131 insertions, 3 deletions
diff --git a/src/Authoring/Studio/Qt3DStudio.pro b/src/Authoring/Studio/Qt3DStudio.pro
index 767c0da6..501fa634 100644
--- a/src/Authoring/Studio/Qt3DStudio.pro
+++ b/src/Authoring/Studio/Qt3DStudio.pro
@@ -2,6 +2,7 @@ TEMPLATE = app
TARGET = Qt3DStudio
include(../commoninclude.pri)
include($$OUT_PWD/../qtAuthoring-config.pri)
+include(../../shared/qtsingleapplication/qtsingleapplication.pri)
INCLUDEPATH += $$OUT_PWD/..
CONFIG += nostrictstrings
diff --git a/src/Authoring/Studio/_Win/Application/StudioApp.cpp b/src/Authoring/Studio/_Win/Application/StudioApp.cpp
index 35aca9a8..a2da4135 100644
--- a/src/Authoring/Studio/_Win/Application/StudioApp.cpp
+++ b/src/Authoring/Studio/_Win/Application/StudioApp.cpp
@@ -39,6 +39,8 @@
#include "Qt3DSStateApplication.h"
#include "PlayerWnd.h"
#include "DataInputDlg.h"
+#include "qtsingleapplication.h"
+#include "qtlocalpeer.h"
#include <QtGui/qsurfaceformat.h>
#include <QtCore/qfileinfo.h>
@@ -48,6 +50,8 @@
#include <QtCore/qstandardpaths.h>
#include <QtCore/qcommandlineparser.h>
+const QString activePresentationQuery = QStringLiteral("activePresentation:");
+
int main(int argc, char *argv[])
{
// Hack to work around qml cache bug (QT3DS-556)
@@ -57,7 +61,7 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(res);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
- QApplication guiApp(argc, argv);
+ SharedTools::QtSingleApplication guiApp(QStringLiteral("Qt3DStudio"), argc, argv);
#if defined(Q_OS_MACOS)
QSurfaceFormat openGL33Format;
@@ -118,6 +122,9 @@ int main(int argc, char *argv[])
exit(0);
}
+ QObject::connect(&guiApp, &SharedTools::QtSingleApplication::messageReceived,
+ &g_StudioApp, &CStudioApp::handleMessageReceived);
+
// Load and apply stylesheet for the application
QFile styleFile(":/style.qss");
styleFile.open(QFile::ReadOnly);
@@ -241,10 +248,12 @@ void CStudioApp::performShutdown()
}
if (m_renderer) {
- m_views->getMainFrame()->GetPlayerWnd()->makeCurrent();
+ if (m_views->getMainFrame())
+ m_views->getMainFrame()->GetPlayerWnd()->makeCurrent();
m_renderer->Close();
m_renderer = std::shared_ptr<Q3DStudio::IStudioRenderer>();
- m_views->getMainFrame()->GetPlayerWnd()->doneCurrent();
+ if (m_views->getMainFrame())
+ m_views->getMainFrame()->GetPlayerWnd()->doneCurrent();
}
delete m_views;
@@ -566,6 +575,17 @@ bool CStudioApp::blankRunApplication()
*/
bool CStudioApp::openAndRunApplication(const QString &inFilename)
{
+ // First check if the desired presentation is already open on another instance
+ SharedTools::QtSingleApplication *app =
+ static_cast<SharedTools::QtSingleApplication *>(QCoreApplication::instance());
+ const auto pids = app->runningInstances();
+ for (const auto pid : pids) {
+ app->setBlock(true);
+ QString query = activePresentationQuery + inFilename;
+ if (app->sendMessage(query, true, 5000, pid))
+ return true;
+ }
+
bool theSuccess = false;
initCore();
// Load document. Upon failure, don't show startup dialog but exit immediately.
@@ -715,6 +735,35 @@ void CStudioApp::clearGuides()
SCOPED_DOCUMENT_EDITOR(*m_core->GetDoc(), QObject::tr("Clear Guides"))->ClearGuides();
}
+void CStudioApp::handleMessageReceived(const QString &message, QObject *socket)
+{
+ if (message.startsWith(activePresentationQuery)) {
+ QLocalSocket *lsocket = qobject_cast<QLocalSocket *>(socket);
+ if (lsocket) {
+ // Another studio instance wants to know if specified presentation is open on this one
+ QFileInfo checkFile(message.mid(activePresentationQuery.size()));
+ QFileInfo openFile(m_core->GetDoc()->GetDocumentPath().GetAbsolutePath().toQString());
+ if (checkFile == openFile) {
+ lsocket->write(SharedTools::QtLocalPeer::acceptReply(),
+ SharedTools::QtLocalPeer::acceptReply().size());
+ // Since we accept active presentation query, it means the querying instance will
+ // shut down and this instance must be made active window.
+ if (m_pMainWnd) {
+ m_pMainWnd->setWindowState(m_pMainWnd->windowState() & ~Qt::WindowMinimized);
+ m_pMainWnd->raise();
+ m_pMainWnd->activateWindow();
+ }
+ } else {
+ lsocket->write(SharedTools::QtLocalPeer::denyReply(),
+ SharedTools::QtLocalPeer::denyReply().size());
+ }
+ lsocket->waitForBytesWritten(1000);
+ }
+ }
+ if (socket)
+ delete socket;
+}
+
void SendAsyncCommand(CDispatch &inDispatch, Q3DStudio::TCallbackFunc inFunc)
{
inDispatch.FireOnAsynchronousCommand(inFunc);
diff --git a/src/Authoring/Studio/_Win/Application/StudioApp.h b/src/Authoring/Studio/_Win/Application/StudioApp.h
index 64193573..cba3d748 100644
--- a/src/Authoring/Studio/_Win/Application/StudioApp.h
+++ b/src/Authoring/Studio/_Win/Application/StudioApp.h
@@ -96,6 +96,9 @@ public:
Q3DStudio::IStudioRenderer &getRenderer();
void clearGuides();
+public Q_SLOTS:
+ void handleMessageReceived(const QString &message, QObject *socket);
+
protected:
bool runApplication();
bool blankRunApplication();
diff --git a/src/shared/README.txt b/src/shared/README.txt
new file mode 100644
index 00000000..717efd54
--- /dev/null
+++ b/src/shared/README.txt
@@ -0,0 +1,2 @@
+QtLockedFile and QtSingleApplication were copied from Qt Creator and
+modified for Qt 3D Studio purposes.
diff --git a/src/shared/qtlockedfile/qtlockedfile.cpp b/src/shared/qtlockedfile/qtlockedfile.cpp
new file mode 100644
index 00000000..06195c24
--- /dev/null
+++ b/src/shared/qtlockedfile/qtlockedfile.cpp
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+
+namespace SharedTools {
+
+/*!
+ \class QtLockedFile
+
+ \brief The QtLockedFile class extends QFile with advisory locking functions.
+
+ A file may be locked in read or write mode. Multiple instances of
+ \e QtLockedFile, created in multiple processes running on the same
+ machine, may have a file locked in read mode. Exactly one instance
+ may have it locked in write mode. A read and a write lock cannot
+ exist simultaneously on the same file.
+
+ The file locks are advisory. This means that nothing prevents
+ another process from manipulating a locked file using QFile or
+ file system functions offered by the OS. Serialization is only
+ guaranteed if all processes that access the file use
+ QtLockedFile. Also, while holding a lock on a file, a process
+ must not open the same file again (through any API), or locks
+ can be unexpectedly lost.
+
+ The lock provided by an instance of \e QtLockedFile is released
+ whenever the program terminates. This is true even when the
+ program crashes and no destructors are called.
+*/
+
+/*! \enum QtLockedFile::LockMode
+
+ This enum describes the available lock modes.
+
+ \value ReadLock A read lock.
+ \value WriteLock A write lock.
+ \value NoLock Neither a read lock nor a write lock.
+*/
+
+/*!
+ Constructs an unlocked \e QtLockedFile object. This constructor behaves in the same way
+ as \e QFile::QFile().
+
+ \sa QFile::QFile()
+*/
+QtLockedFile::QtLockedFile()
+ : QFile()
+{
+#ifdef Q_OS_WIN
+ m_semaphore_hnd = 0;
+ m_mutex_hnd = 0;
+#endif
+ m_lock_mode = NoLock;
+}
+
+/*!
+ Constructs an unlocked QtLockedFile object with file \a name. This constructor behaves in
+ the same way as \e QFile::QFile(const QString&).
+
+ \sa QFile::QFile()
+*/
+QtLockedFile::QtLockedFile(const QString &name)
+ : QFile(name)
+{
+#ifdef Q_OS_WIN
+ m_semaphore_hnd = 0;
+ m_mutex_hnd = 0;
+#endif
+ m_lock_mode = NoLock;
+}
+
+/*!
+ Returns \e true if this object has a in read or write lock;
+ otherwise returns \e false.
+
+ \sa lockMode()
+*/
+bool QtLockedFile::isLocked() const
+{
+ return m_lock_mode != NoLock;
+}
+
+/*!
+ Returns the type of lock currently held by this object, or \e QtLockedFile::NoLock.
+
+ \sa isLocked()
+*/
+QtLockedFile::LockMode QtLockedFile::lockMode() const
+{
+ return m_lock_mode;
+}
+
+/*!
+ \fn bool QtLockedFile::lock(LockMode mode, bool block = true)
+
+ Obtains a lock of type \a mode.
+
+ If \a block is true, this
+ function will block until the lock is acquired. If \a block is
+ false, this function returns \e false immediately if the lock cannot
+ be acquired.
+
+ If this object already has a lock of type \a mode, this function returns \e true immediately.
+ If this object has a lock of a different type than \a mode, the lock
+ is first released and then a new lock is obtained.
+
+ This function returns \e true if, after it executes, the file is locked by this object,
+ and \e false otherwise.
+
+ \sa unlock(), isLocked(), lockMode()
+*/
+
+/*!
+ \fn bool QtLockedFile::unlock()
+
+ Releases a lock.
+
+ If the object has no lock, this function returns immediately.
+
+ This function returns \e true if, after it executes, the file is not locked by
+ this object, and \e false otherwise.
+
+ \sa lock(), isLocked(), lockMode()
+*/
+
+/*!
+ \fn QtLockedFile::~QtLockedFile()
+
+ Destroys the \e QtLockedFile object. If any locks were held, they are released.
+*/
+
+} // namespace SharedTools
diff --git a/src/shared/qtlockedfile/qtlockedfile.h b/src/shared/qtlockedfile/qtlockedfile.h
new file mode 100644
index 00000000..dc310d3c
--- /dev/null
+++ b/src/shared/qtlockedfile/qtlockedfile.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtCore/qfile.h>
+
+#if defined(Q_OS_WIN)
+# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT)
+# define QT_QTLOCKEDFILE_EXPORT
+# elif defined(QT_QTLOCKEDFILE_IMPORT)
+# if defined(QT_QTLOCKEDFILE_EXPORT)
+# undef QT_QTLOCKEDFILE_EXPORT
+# endif
+# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport)
+# elif defined(QT_QTLOCKEDFILE_EXPORT)
+# undef QT_QTLOCKEDFILE_EXPORT
+# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport)
+# endif
+#else
+# define QT_QTLOCKEDFILE_EXPORT
+#endif
+
+namespace SharedTools {
+
+class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
+{
+public:
+ enum LockMode { NoLock = 0, ReadLock, WriteLock };
+
+ QtLockedFile();
+ QtLockedFile(const QString &name);
+ ~QtLockedFile();
+
+ bool lock(LockMode mode, bool block = true);
+ bool unlock();
+ bool isLocked() const;
+ LockMode lockMode() const;
+
+private:
+#ifdef Q_OS_WIN
+ Qt::HANDLE m_semaphore_hnd;
+ Qt::HANDLE m_mutex_hnd;
+#endif
+ LockMode m_lock_mode;
+};
+
+} // namespace SharedTools
diff --git a/src/shared/qtlockedfile/qtlockedfile.pri b/src/shared/qtlockedfile/qtlockedfile.pri
new file mode 100644
index 00000000..46d1f1a1
--- /dev/null
+++ b/src/shared/qtlockedfile/qtlockedfile.pri
@@ -0,0 +1,13 @@
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+HEADERS += $$PWD/qtlockedfile.h
+SOURCES += $$PWD/qtlockedfile.cpp
+
+unix:SOURCES += $$PWD/qtlockedfile_unix.cpp
+win32:SOURCES += $$PWD/qtlockedfile_win.cpp
+
+win32:contains(TEMPLATE, lib):contains(CONFIG, shared) {
+ DEFINES += QT_QTLOCKEDFILE_EXPORT=__declspec(dllexport)
+}
+
+
diff --git a/src/shared/qtlockedfile/qtlockedfile_unix.cpp b/src/shared/qtlockedfile/qtlockedfile_unix.cpp
new file mode 100644
index 00000000..e12928fe
--- /dev/null
+++ b/src/shared/qtlockedfile/qtlockedfile_unix.cpp
@@ -0,0 +1,107 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace SharedTools {
+
+bool QtLockedFile::lock(LockMode mode, bool block)
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::lock(): file is not opened");
+ return false;
+ }
+
+ if (mode == NoLock)
+ return unlock();
+
+ if (mode == m_lock_mode)
+ return true;
+
+ if (m_lock_mode != NoLock)
+ unlock();
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
+ int cmd = block ? F_SETLKW : F_SETLK;
+ int ret = fcntl(handle(), cmd, &fl);
+
+ if (ret == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+ return false;
+ }
+
+
+ m_lock_mode = mode;
+ return true;
+}
+
+
+bool QtLockedFile::unlock()
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::unlock(): file is not opened");
+ return false;
+ }
+
+ if (!isLocked())
+ return true;
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ int ret = fcntl(handle(), F_SETLKW, &fl);
+
+ if (ret == -1) {
+ qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+ return false;
+ }
+
+ m_lock_mode = NoLock;
+ remove();
+ return true;
+}
+
+QtLockedFile::~QtLockedFile()
+{
+ if (isOpen())
+ unlock();
+}
+
+} // namespace SharedTools
diff --git a/src/shared/qtlockedfile/qtlockedfile_win.cpp b/src/shared/qtlockedfile/qtlockedfile_win.cpp
new file mode 100644
index 00000000..e1ac9abc
--- /dev/null
+++ b/src/shared/qtlockedfile/qtlockedfile_win.cpp
@@ -0,0 +1,195 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+
+#include <QtCore/qt_windows.h>
+#include <QtCore/qfileinfo.h>
+
+namespace SharedTools {
+
+#define SEMAPHORE_PREFIX "QtLockedFile semaphore "
+#define MUTEX_PREFIX "QtLockedFile mutex "
+#define SEMAPHORE_MAX 100
+
+static QString errorCodeToString(DWORD errorCode)
+{
+ QString result;
+ char *data = 0;
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, errorCode, 0,
+ (char*)&data, 0, 0);
+ result = QString::fromLocal8Bit(data);
+ if (data != 0)
+ LocalFree(data);
+
+ if (result.endsWith(QLatin1Char('\n')))
+ result.truncate(result.length() - 1);
+
+ return result;
+}
+
+bool QtLockedFile::lock(LockMode mode, bool block)
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::lock(): file is not opened");
+ return false;
+ }
+
+ if (mode == m_lock_mode)
+ return true;
+
+ if (m_lock_mode != 0)
+ unlock();
+
+ if (m_semaphore_hnd == 0) {
+ QFileInfo fi(*this);
+ QString sem_name = QString::fromLatin1(SEMAPHORE_PREFIX)
+ + fi.absoluteFilePath().toLower();
+
+ m_semaphore_hnd = CreateSemaphoreW(0, SEMAPHORE_MAX, SEMAPHORE_MAX,
+ (TCHAR*)sem_name.utf16());
+
+ if (m_semaphore_hnd == 0) {
+ qWarning("QtLockedFile::lock(): CreateSemaphore: %s",
+ errorCodeToString(GetLastError()).toLatin1().constData());
+ return false;
+ }
+ }
+
+ bool gotMutex = false;
+ int decrement;
+ if (mode == ReadLock) {
+ decrement = 1;
+ } else {
+ decrement = SEMAPHORE_MAX;
+ if (m_mutex_hnd == 0) {
+ QFileInfo fi(*this);
+ QString mut_name = QString::fromLatin1(MUTEX_PREFIX)
+ + fi.absoluteFilePath().toLower();
+
+ m_mutex_hnd = CreateMutexW(NULL, FALSE, (TCHAR*)mut_name.utf16());
+
+ if (m_mutex_hnd == 0) {
+ qWarning("QtLockedFile::lock(): CreateMutex: %s",
+ errorCodeToString(GetLastError()).toLatin1().constData());
+ return false;
+ }
+ }
+ DWORD res = WaitForSingleObject(m_mutex_hnd, block ? INFINITE : 0);
+ if (res == WAIT_TIMEOUT)
+ return false;
+ if (res == WAIT_FAILED) {
+ qWarning("QtLockedFile::lock(): WaitForSingleObject (mutex): %s",
+ errorCodeToString(GetLastError()).toLatin1().constData());
+ return false;
+ }
+ gotMutex = true;
+ }
+
+ for (int i = 0; i < decrement; ++i) {
+ DWORD res = WaitForSingleObject(m_semaphore_hnd, block ? INFINITE : 0);
+ if (res == WAIT_TIMEOUT) {
+ if (i) {
+ // A failed nonblocking rw locking. Undo changes to semaphore.
+ if (ReleaseSemaphore(m_semaphore_hnd, i, NULL) == 0) {
+ qWarning("QtLockedFile::unlock(): ReleaseSemaphore: %s",
+ errorCodeToString(GetLastError()).toLatin1().constData());
+ // Fall through
+ }
+ }
+ if (gotMutex)
+ ReleaseMutex(m_mutex_hnd);
+ return false;
+ }
+ if (res != WAIT_OBJECT_0) {
+ if (gotMutex)
+ ReleaseMutex(m_mutex_hnd);
+ qWarning("QtLockedFile::lock(): WaitForSingleObject (semaphore): %s",
+ errorCodeToString(GetLastError()).toLatin1().constData());
+ return false;
+ }
+ }
+
+ m_lock_mode = mode;
+ if (gotMutex)
+ ReleaseMutex(m_mutex_hnd);
+ return true;
+}
+
+bool QtLockedFile::unlock()
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::unlock(): file is not opened");
+ return false;
+ }
+
+ if (!isLocked())
+ return true;
+
+ int increment;
+ if (m_lock_mode == ReadLock)
+ increment = 1;
+ else
+ increment = SEMAPHORE_MAX;
+
+ DWORD ret = ReleaseSemaphore(m_semaphore_hnd, increment, 0);
+ if (ret == 0) {
+ qWarning("QtLockedFile::unlock(): ReleaseSemaphore: %s",
+ errorCodeToString(GetLastError()).toLatin1().constData());
+ return false;
+ }
+
+ m_lock_mode = QtLockedFile::NoLock;
+ remove();
+ return true;
+}
+
+QtLockedFile::~QtLockedFile()
+{
+ if (isOpen())
+ unlock();
+ if (m_mutex_hnd != 0) {
+ DWORD ret = CloseHandle(m_mutex_hnd);
+ if (ret == 0) {
+ qWarning("QtLockedFile::~QtLockedFile(): CloseHandle (mutex): %s",
+ errorCodeToString(GetLastError()).toLatin1().constData());
+ }
+ m_mutex_hnd = 0;
+ }
+ if (m_semaphore_hnd != 0) {
+ DWORD ret = CloseHandle(m_semaphore_hnd);
+ if (ret == 0) {
+ qWarning("QtLockedFile::~QtLockedFile(): CloseHandle (semaphore): %s",
+ errorCodeToString(GetLastError()).toLatin1().constData());
+ }
+ m_semaphore_hnd = 0;
+ }
+}
+
+} // namespace SharedTools
diff --git a/src/shared/qtsingleapplication/qtlocalpeer.cpp b/src/shared/qtsingleapplication/qtlocalpeer.cpp
new file mode 100644
index 00000000..efcaa228
--- /dev/null
+++ b/src/shared/qtsingleapplication/qtlocalpeer.cpp
@@ -0,0 +1,191 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtlocalpeer.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdatastream.h>
+#include <QtCore/qdatetime.h>
+
+#if defined(Q_OS_WIN)
+#include <QtCore/qlibrary.h>
+#include <QtCore/qt_windows.h>
+typedef BOOL(WINAPI *PProcessIdToSessionId)(DWORD, DWORD *);
+static PProcessIdToSessionId pProcessIdToSessionId = nullptr;
+#endif
+
+#if defined(Q_OS_UNIX)
+#include <time.h>
+#include <unistd.h>
+#endif
+
+namespace SharedTools {
+
+// All reply strings must be of same length
+static const QByteArray acceptReplyStr = QByteArrayLiteral("@A@");
+static const QByteArray denyReplyStr = QByteArrayLiteral("@D@");
+
+QString QtLocalPeer::appSessionId(const QString &appId)
+{
+ QByteArray idc = appId.toUtf8();
+ quint16 idNum = qChecksum(idc.constData(), idc.size());
+
+ QString res = QLatin1String("qtsingleapplication-")
+ + QString::number(idNum, 16);
+#if defined(Q_OS_WIN)
+ if (!pProcessIdToSessionId) {
+ QLibrary lib(QLatin1String("kernel32"));
+ pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
+ }
+ if (pProcessIdToSessionId) {
+ DWORD sessionId = 0;
+ pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
+ res += QLatin1Char('-') + QString::number(sessionId, 16);
+ }
+#else
+ res += QLatin1Char('-') + QString::number(::getuid(), 16);
+#endif
+ return res;
+}
+
+QByteArray QtLocalPeer::acceptReply()
+{
+ return acceptReplyStr;
+}
+
+QByteArray QtLocalPeer::denyReply()
+{
+ return denyReplyStr;
+}
+
+QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId)
+ : QObject(parent), m_id(appId)
+{
+ if (m_id.isEmpty())
+ m_id = QCoreApplication::applicationFilePath();
+
+ m_socketName = appSessionId(m_id);
+ m_server = new QLocalServer(this);
+ QString lockName = QDir(QDir::tempPath()).absolutePath()
+ + QLatin1Char('/') + m_socketName
+ + QLatin1String("-lockfile");
+ m_lockFile.setFileName(lockName);
+ m_lockFile.open(QIODevice::ReadWrite);
+}
+
+bool QtLocalPeer::isClient()
+{
+ if (m_lockFile.isLocked())
+ return false;
+
+ if (!m_lockFile.lock(QtLockedFile::WriteLock, false))
+ return true;
+
+ if (!QLocalServer::removeServer(m_socketName))
+ qWarning("QtSingleCoreApplication: could not cleanup socket");
+ bool res = m_server->listen(m_socketName);
+ if (!res) {
+ qWarning("QtSingleCoreApplication: listen on local socket failed, %s",
+ qPrintable(m_server->errorString()));
+ }
+ QObject::connect(m_server, &QLocalServer::newConnection,
+ this, &QtLocalPeer::receiveConnection);
+ return false;
+}
+
+bool QtLocalPeer::sendMessage(const QString &message, int timeout, bool block)
+{
+ if (!isClient())
+ return false;
+
+ QLocalSocket socket;
+ bool connOk = false;
+ for (int i = 0; i < 2; i++) {
+ // Try twice, in case the other instance is just starting up
+ socket.connectToServer(m_socketName);
+ connOk = socket.waitForConnected(timeout/2);
+ if (connOk || i)
+ break;
+ int ms = 250;
+#if defined(Q_OS_WIN)
+ Sleep(DWORD(ms));
+#else
+ struct timespec ts = {ms / 1000, (ms % 1000) * 1000 * 1000};
+ nanosleep(&ts, NULL);
+#endif
+ }
+ if (!connOk)
+ return false;
+
+ QByteArray uMsg(message.toUtf8());
+ QDataStream ds(&socket);
+ ds.writeBytes(uMsg.constData(), uMsg.size());
+ bool res = socket.waitForBytesWritten(timeout);
+ res &= socket.waitForReadyRead(timeout); // wait for reply
+ QByteArray reply = socket.read(acceptReplyStr.size());
+ res &= reply == acceptReplyStr;
+ if (block) // block until peer disconnects
+ socket.waitForDisconnected(-1);
+ return res;
+}
+
+void QtLocalPeer::receiveConnection()
+{
+ QLocalSocket* socket = m_server->nextPendingConnection();
+ if (!socket)
+ return;
+
+ // Why doesn't Qt have a blocking stream that takes care of this?
+ while (socket->bytesAvailable() < static_cast<int>(sizeof(quint32))) {
+ if (!socket->isValid()) // stale request
+ return;
+ socket->waitForReadyRead(1000);
+ }
+ QDataStream ds(socket);
+ QByteArray uMsg;
+ quint32 remaining;
+ ds >> remaining;
+ uMsg.resize(remaining);
+ int got = 0;
+ char* uMsgBuf = uMsg.data();
+ do {
+ got = ds.readRawData(uMsgBuf, remaining);
+ remaining -= got;
+ uMsgBuf += got;
+ } while (remaining && got >= 0 && socket->waitForReadyRead(2000));
+ if (got < 0) {
+ qWarning() << "QtLocalPeer: Message reception failed" << socket->errorString();
+ delete socket;
+ return;
+ }
+ QString message = QString::fromUtf8(uMsg.constData(), uMsg.size());
+ // messageReceived handler must write the reply string
+ emit messageReceived(message, socket); // might take a long time to return
+}
+
+} // namespace SharedTools
diff --git a/src/shared/qtsingleapplication/qtlocalpeer.h b/src/shared/qtsingleapplication/qtlocalpeer.h
new file mode 100644
index 00000000..d2db7366
--- /dev/null
+++ b/src/shared/qtsingleapplication/qtlocalpeer.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+
+#include <QtNetwork/qlocalserver.h>
+#include <QtNetwork/qlocalsocket.h>
+#include <QtCore/qdir.h>
+
+namespace SharedTools {
+
+class QtLocalPeer : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
+ bool isClient();
+ bool sendMessage(const QString &message, int timeout, bool block);
+ QString applicationId() const
+ { return m_id; }
+ static QString appSessionId(const QString &appId);
+ static QByteArray acceptReply();
+ static QByteArray denyReply();
+
+Q_SIGNALS:
+ void messageReceived(const QString &message, QObject *socket);
+
+protected:
+ void receiveConnection();
+
+ QString m_id;
+ QString m_socketName;
+ QLocalServer* m_server;
+ QtLockedFile m_lockFile;
+};
+
+} // namespace SharedTools
diff --git a/src/shared/qtsingleapplication/qtsingleapplication.cpp b/src/shared/qtsingleapplication/qtsingleapplication.cpp
new file mode 100644
index 00000000..ae007bd7
--- /dev/null
+++ b/src/shared/qtsingleapplication/qtsingleapplication.cpp
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtsingleapplication.h"
+#include "qtlocalpeer.h"
+
+#include "qtlockedfile.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qsharedmemory.h>
+#include <QtWidgets/qwidget.h>
+
+#if defined(Q_OS_WIN)
+#include <QtCore/qlibrary.h>
+#include <QtCore/qt_windows.h>
+typedef BOOL(WINAPI *allowSetForegroundWindow)(DWORD);
+static allowSetForegroundWindow pAllowSetForegroundWindow = nullptr;
+#endif
+
+namespace SharedTools {
+
+static const int instancesSize = 1024;
+
+static QString instancesLockFilename(const QString &appSessionId)
+{
+ const QChar slash(QLatin1Char('/'));
+ QString res = QDir::tempPath();
+ if (!res.endsWith(slash))
+ res += slash;
+ return res + appSessionId + QLatin1String("-instances");
+}
+
+QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
+ : QApplication(argc, argv),
+ m_firstPeer(-1),
+ m_pidPeer(0)
+{
+ this->m_appId = appId;
+
+ const QString appSessionId = QtLocalPeer::appSessionId(appId);
+
+ // This shared memory holds a zero-terminated array of active (or crashed) instances
+ m_instances = new QSharedMemory(appSessionId, this);
+ m_block = false;
+
+ // First instance creates the shared memory, later instances attach to it
+ const bool created = m_instances->create(instancesSize);
+ if (!created) {
+ if (!m_instances->attach()) {
+ qWarning() << "Failed to initialize instances shared memory: "
+ << m_instances->errorString();
+ delete m_instances;
+ m_instances = 0;
+ return;
+ }
+ }
+
+ // QtLockedFile is used to workaround QTBUG-10364
+ QtLockedFile lockfile(instancesLockFilename(appSessionId));
+
+ lockfile.open(QtLockedFile::ReadWrite);
+ lockfile.lock(QtLockedFile::WriteLock);
+ qint64 *pids = static_cast<qint64 *>(m_instances->data());
+ if (!created) {
+ // Find the first instance that it still running
+ // The whole list needs to be iterated in order to append to it
+ for (; *pids; ++pids) {
+ if (isRunning(*pids)) {
+ m_peers.append(*pids);
+ if (m_firstPeer == -1)
+ m_firstPeer = *pids;
+ }
+ }
+ }
+ // Add current pid to list and terminate it
+ *pids++ = QCoreApplication::applicationPid();
+ *pids = 0;
+ m_pidPeer = new QtLocalPeer(this, appId + QLatin1Char('-') +
+ QString::number(QCoreApplication::applicationPid()));
+ connect(m_pidPeer, &QtLocalPeer::messageReceived,
+ this, &QtSingleApplication::messageReceived);
+ m_pidPeer->isClient();
+ lockfile.unlock();
+
+#if defined(Q_OS_WIN)
+ if (!pAllowSetForegroundWindow) {
+ QLibrary lib(QLatin1String("user32"));
+ pAllowSetForegroundWindow =
+ (allowSetForegroundWindow)lib.resolve("AllowSetForegroundWindow");
+ }
+#endif
+}
+
+QtSingleApplication::~QtSingleApplication()
+{
+ if (!m_instances)
+ return;
+ const qint64 appPid = QCoreApplication::applicationPid();
+ QtLockedFile lockfile(instancesLockFilename(QtLocalPeer::appSessionId(m_appId)));
+ lockfile.open(QtLockedFile::ReadWrite);
+ lockfile.lock(QtLockedFile::WriteLock);
+ // Rewrite array, removing current pid and previously crashed ones
+ qint64 *pids = static_cast<qint64 *>(m_instances->data());
+ qint64 *newpids = pids;
+ for (; *pids; ++pids) {
+ if (*pids != appPid && isRunning(*pids))
+ *newpids++ = *pids;
+ }
+ *newpids = 0;
+ lockfile.unlock();
+}
+
+bool QtSingleApplication::isRunning(qint64 pid)
+{
+ if (pid == -1) {
+ pid = m_firstPeer;
+ if (pid == -1)
+ return false;
+ }
+
+ QtLocalPeer peer(this, m_appId + QLatin1Char('-') + QString::number(pid, 10));
+ return peer.isClient();
+}
+
+bool QtSingleApplication::sendMessage(const QString &message, bool activateOnAccept,
+ int timeout, qint64 pid)
+{
+ if (pid == -1) {
+ pid = m_firstPeer;
+ if (pid == -1)
+ return false;
+ }
+
+#if defined(Q_OS_WIN)
+ if (activateOnAccept && pAllowSetForegroundWindow) {
+ // In windows it is necessary to specifically allow bringing another application to
+ // foreground. Otherwise it will just blink in the task bar.
+ pAllowSetForegroundWindow(pid);
+ }
+#else
+ Q_UNUSED(activateOnAccept)
+#endif
+
+ QtLocalPeer peer(this, m_appId + QLatin1Char('-') + QString::number(pid, 10));
+ return peer.sendMessage(message, timeout, m_block);
+}
+
+QString QtSingleApplication::applicationId() const
+{
+ return m_appId;
+}
+
+void QtSingleApplication::setBlock(bool value)
+{
+ m_block = value;
+}
+
+QVector<qint64> QtSingleApplication::runningInstances() const
+{
+ // Note that this is a list of running instances at the time of application startup,
+ // so it should only be used at application startup.
+ return m_peers;
+}
+
+} // namespace SharedTools
diff --git a/src/shared/qtsingleapplication/qtsingleapplication.h b/src/shared/qtsingleapplication/qtsingleapplication.h
new file mode 100644
index 00000000..5f9cdade
--- /dev/null
+++ b/src/shared/qtsingleapplication/qtsingleapplication.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtWidgets/qapplication.h>
+#include <QtCore/qvector.h>
+
+QT_FORWARD_DECLARE_CLASS(QSharedMemory)
+
+namespace SharedTools {
+
+class QtLocalPeer;
+
+class QtSingleApplication : public QApplication
+{
+ Q_OBJECT
+
+public:
+ QtSingleApplication(const QString &id, int &argc, char **argv);
+ ~QtSingleApplication();
+
+ bool isRunning(qint64 pid = -1);
+
+ QString applicationId() const;
+ void setBlock(bool value);
+
+ bool sendMessage(const QString &message, bool activateOnAccept = true,
+ int timeout = 5000, qint64 pid = -1);
+
+ QVector<qint64> runningInstances() const;
+
+Q_SIGNALS:
+ void messageReceived(const QString &message, QObject *socket);
+
+private:
+ QString instancesFileName(const QString &m_appId);
+
+ qint64 m_firstPeer;
+ QSharedMemory *m_instances;
+ QtLocalPeer *m_pidPeer;
+ QString m_appId;
+ bool m_block;
+ QVector<qint64> m_peers;
+};
+
+} // namespace SharedTools
diff --git a/src/shared/qtsingleapplication/qtsingleapplication.pri b/src/shared/qtsingleapplication/qtsingleapplication.pri
new file mode 100644
index 00000000..dc7264d7
--- /dev/null
+++ b/src/shared/qtsingleapplication/qtsingleapplication.pri
@@ -0,0 +1,13 @@
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h
+SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp
+
+QT *= network widgets
+
+gotqtlockedfile = $$find(HEADERS, .*qtlockedfile.h)
+isEmpty(gotqtlockedfile):include(../qtlockedfile/qtlockedfile.pri)
+
+win32:contains(TEMPLATE, lib):contains(CONFIG, shared) {
+ DEFINES += QT_QTSINGLEAPPLICATION_EXPORT=__declspec(dllexport)
+}