diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -9,7 +9,19 @@ find_package(SharedMimeInfo REQUIRED) +option(KDEVELOP_SINGLE_APP "Use QtSingleApplication as KDevelop's base" OFF) + +set(singleapp_SRCS qtlocalpeer.cpp qtsingleapplication.cpp qtlockedfile.cpp) +if(WIN32) + set(singleapp_SRCS ${singleapp_SRCS} qtlockedfile_win.cpp) +else() + set(singleapp_SRCS ${singleapp_SRCS} qtlockedfile_unix.cpp) +endif() + set(kdevelop_SRCS main.cpp kdevideextension.cpp splash.cpp) +if(KDEVELOP_SINGLE_APP) + set(kdevelop_SRCS ${kdevelop_SRCS} ${singleapp_SRCS}) +endif() # kde4_add_app_icon(kdevelop_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../pics/hi*-app-kdevelop.png") if(APPLE) #kde4_add_app_icon(kdevelop_SRCS "${KDE4_ICON_INSTALL_DIR}/oxygen/*/apps/kdevelop.png") diff --git a/app/main.cpp b/app/main.cpp --- a/app/main.cpp +++ b/app/main.cpp @@ -68,6 +68,9 @@ #include #include "kdevideextension.h" +#if KDEVELOP_SINGLE_APP +#include "qtsingleapplication.h" +#endif #include @@ -79,11 +82,20 @@ using namespace KDevelop; -class KDevelopApplication: public QApplication +class KDevelopApplication: +#if KDEVELOP_SINGLE_APP + public SharedTools::QtSingleApplication +#else + public QApplication +#endif { public: explicit KDevelopApplication(int &argc, char **argv, bool GUIenabled = true) +#if KDEVELOP_SINGLE_APP + : SharedTools::QtSingleApplication(QStringLiteral("KDevelop"), argc, argv) +#else : QApplication(argc, argv, GUIenabled) +#endif { connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState); } diff --git a/app/qtlocalpeer.h b/app/qtlocalpeer.h new file mode 100644 --- /dev/null +++ b/app/qtlocalpeer.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#include + +#include +#include +#include + +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 id; } + static QString appSessionId(const QString &appId); + +Q_SIGNALS: + void messageReceived(const QString &message, QObject *socket); + +protected Q_SLOTS: + void receiveConnection(); + +protected: + QString id; + QString socketName; + QLocalServer* server; + QtLockedFile lockFile; +}; + +} // namespace SharedTools diff --git a/app/qtlocalpeer.cpp b/app/qtlocalpeer.cpp new file mode 100644 --- /dev/null +++ b/app/qtlocalpeer.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#include "qtlocalpeer.h" + +#include +#include +#include + +#if defined(Q_OS_WIN) +#include +#include +typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); +static PProcessIdToSessionId pProcessIdToSessionId = 0; +#endif + +#if defined(Q_OS_UNIX) +#include +#include +#endif + +namespace SharedTools { + +static const char ack[] = "ack"; + +QString QtLocalPeer::appSessionId(const QString &appId) +{ + QByteArray idc = appId.toUtf8(); + quint16 idNum = qChecksum(idc.constData(), idc.size()); + //### could do: two 16bit checksums over separate halves of id, for a 32bit result - improved uniqeness probability. Every-other-char split would be best. + + 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; +} + +QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId) + : QObject(parent), id(appId) +{ + if (id.isEmpty()) + id = QCoreApplication::applicationFilePath(); //### On win, check if this returns .../argv[0] without casefolding; .\MYAPP == .\myapp on Win + + socketName = appSessionId(id); + server = new QLocalServer(this); + QString lockName = QDir(QDir::tempPath()).absolutePath() + + QLatin1Char('/') + socketName + + QLatin1String("-lockfile"); + lockFile.setFileName(lockName); + lockFile.open(QIODevice::ReadWrite); +} + +bool QtLocalPeer::isClient() +{ + if (lockFile.isLocked()) + return false; + + if (!lockFile.lock(QtLockedFile::WriteLock, false)) + return true; + + if (!QLocalServer::removeServer(socketName)) + qWarning("QtSingleCoreApplication: could not cleanup socket"); + bool res = server->listen(socketName); + if (!res) + qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); + QObject::connect(server, SIGNAL(newConnection()), SLOT(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(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 ack + res &= (socket.read(qstrlen(ack)) == ack); + if (block) // block until peer disconnects + socket.waitForDisconnected(-1); + return res; +} + +void QtLocalPeer::receiveConnection() +{ + QLocalSocket* socket = server->nextPendingConnection(); + if (!socket) + return; + + // Why doesn't Qt have a blocking stream that takes care of this shait??? + while (socket->bytesAvailable() < static_cast(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(); + //qDebug() << "RCV: remaining" << remaining; + do { + got = ds.readRawData(uMsgBuf, remaining); + remaining -= got; + uMsgBuf += got; + //qDebug() << "RCV: got" << got << "remaining" << remaining; + } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); + //### error check: got<0 + if (got < 0) { + qWarning() << "QtLocalPeer: Message reception failed" << socket->errorString(); + delete socket; + return; + } + // ### async this + QString message = QString::fromUtf8(uMsg.constData(), uMsg.size()); + socket->write(ack, qstrlen(ack)); + socket->waitForBytesWritten(1000); + emit messageReceived(message, socket); // ##(might take a long time to return) +} + +} // namespace SharedTools diff --git a/app/qtlockedfile.h b/app/qtlockedfile.h new file mode 100644 --- /dev/null +++ b/app/qtlockedfile.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include + +#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/app/qtlockedfile.cpp b/app/qtlockedfile.cpp new file mode 100644 --- /dev/null +++ b/app/qtlockedfile.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#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/app/qtlockedfile_unix.cpp b/app/qtlockedfile_unix.cpp new file mode 100644 --- /dev/null +++ b/app/qtlockedfile_unix.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#include "qtlockedfile.h" + +#include +#include +#include +#include + +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/app/qtlockedfile_win.cpp b/app/qtlockedfile_win.cpp new file mode 100644 --- /dev/null +++ b/app/qtlockedfile_win.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#include "qtlockedfile.h" + +#include +#include + +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/app/qtsingleapplication.h b/app/qtsingleapplication.h new file mode 100644 --- /dev/null +++ b/app/qtsingleapplication.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#include + +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); + + void setActivationWindow(QWidget* aw, bool activateOnMessage = true); + QWidget* activationWindow() const; + bool event(QEvent *event); + + QString applicationId() const; + void setBlock(bool value); + +public Q_SLOTS: + bool sendMessage(const QString &message, int timeout = 5000, qint64 pid = -1); + void activateWindow(); + +Q_SIGNALS: + void messageReceived(const QString &message, QObject *socket); + void fileOpenRequest(const QString &file); + +private: + QString instancesFileName(const QString &appId); + + qint64 firstPeer; + QSharedMemory *instances; + QtLocalPeer *pidPeer; + QWidget *actWin; + QString appId; + bool block; +}; + +} // namespace SharedTools diff --git a/app/qtsingleapplication.cpp b/app/qtsingleapplication.cpp new file mode 100644 --- /dev/null +++ b/app/qtsingleapplication.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#include "qtsingleapplication.h" +#include "qtlocalpeer.h" + +#include + +#include +#include +#include +#include + +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), + firstPeer(-1), + pidPeer(0) +{ + this->appId = appId; + + const QString appSessionId = QtLocalPeer::appSessionId(appId); + + // This shared memory holds a zero-terminated array of active (or crashed) instances + instances = new QSharedMemory(appSessionId, this); + actWin = 0; + block = false; + + // First instance creates the shared memory, later instances attach to it + const bool created = instances->create(instancesSize); + if (!created) { + if (!instances->attach()) { + qWarning() << "Failed to initialize instances shared memory: " + << instances->errorString(); + delete instances; + 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(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 (firstPeer == -1 && isRunning(*pids)) + firstPeer = *pids; + } + } + // Add current pid to list and terminate it + *pids++ = QCoreApplication::applicationPid(); + *pids = 0; + pidPeer = new QtLocalPeer(this, appId + QLatin1Char('-') + + QString::number(QCoreApplication::applicationPid())); + connect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), SIGNAL(messageReceived(QString,QObject*))); + pidPeer->isClient(); + lockfile.unlock(); +} + +QtSingleApplication::~QtSingleApplication() +{ + if (!instances) + return; + const qint64 appPid = QCoreApplication::applicationPid(); + QtLockedFile lockfile(instancesLockFilename(QtLocalPeer::appSessionId(appId))); + lockfile.open(QtLockedFile::ReadWrite); + lockfile.lock(QtLockedFile::WriteLock); + // Rewrite array, removing current pid and previously crashed ones + qint64 *pids = static_cast(instances->data()); + qint64 *newpids = pids; + for (; *pids; ++pids) { + if (*pids != appPid && isRunning(*pids)) + *newpids++ = *pids; + } + *newpids = 0; + lockfile.unlock(); +} + +bool QtSingleApplication::event(QEvent *event) +{ + if (event->type() == QEvent::FileOpen) { + QFileOpenEvent *foe = static_cast(event); + emit fileOpenRequest(foe->file()); + return true; + } + return QApplication::event(event); +} + +bool QtSingleApplication::isRunning(qint64 pid) +{ + if (pid == -1) { + pid = firstPeer; + if (pid == -1) + return false; + } + + QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10)); + return peer.isClient(); +} + +bool QtSingleApplication::sendMessage(const QString &message, int timeout, qint64 pid) +{ + if (pid == -1) { + pid = firstPeer; + if (pid == -1) + return false; + } + + QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10)); + return peer.sendMessage(message, timeout, block); +} + +QString QtSingleApplication::applicationId() const +{ + return appId; +} + +void QtSingleApplication::setBlock(bool value) +{ + block = value; +} + +void QtSingleApplication::setActivationWindow(QWidget *aw, bool activateOnMessage) +{ + actWin = aw; + if (!pidPeer) + return; + if (activateOnMessage) + connect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), this, SLOT(activateWindow())); + else + disconnect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), this, SLOT(activateWindow())); +} + + +QWidget* QtSingleApplication::activationWindow() const +{ + return actWin; +} + + +void QtSingleApplication::activateWindow() +{ + if (actWin) { + actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); + actWin->raise(); + actWin->activateWindow(); + } +} + +} // namespace SharedTools diff --git a/config.h.cmake b/config.h.cmake --- a/config.h.cmake +++ b/config.h.cmake @@ -8,4 +8,6 @@ #define VERSION_MINOR "@KDEVELOP_VERSION_MINOR@" #define VERSION_PATCH "@KDEVELOP_VERSION_PATCH@" +#cmakedefine01 KDEVELOP_SINGLE_APP + #endif // KDEVELOP_CONFIG_H