├── .gitattributes ├── .gitignore ├── CoreTest ├── .gitignore ├── CoreTest.pro └── main.cpp ├── GuiTest ├── GuiTest.pro ├── deployment.pri ├── main.cpp ├── main.qml └── qml.qrc ├── LICENSE ├── QSingleInstance.pro ├── QSingleInstance ├── QSingleInstance ├── clientinstance.cpp ├── clientinstance.h ├── qsingleinstance.cpp ├── qsingleinstance.h ├── qsingleinstance_p.cpp └── qsingleinstance_p.h ├── README.md ├── WidgetTest ├── WidgetTest.pro ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h └── mainwindow.ui ├── de_skycoder42_qsingleinstance.prc ├── de_skycoder42_qsingleinstance.pri ├── qpm.json └── qpmx.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.moc 20 | moc_*.cpp 21 | qrc_*.cpp 22 | ui_*.h 23 | Makefile* 24 | *-build-* 25 | 26 | # QtCreator 27 | 28 | *.autosave 29 | 30 | #QtCtreator Qml 31 | *.qmlproject.user 32 | *.qmlproject.user.* 33 | 34 | # ========================= 35 | # Operating System Files 36 | # ========================= 37 | 38 | # OSX 39 | # ========================= 40 | 41 | .DS_Store 42 | .AppleDouble 43 | .LSOverride 44 | 45 | # Thumbnails 46 | ._* 47 | 48 | # Files that might appear on external disk 49 | .Spotlight-V100 50 | .Trashes 51 | 52 | # Directories potentially created on remote AFP share 53 | .AppleDB 54 | .AppleDesktop 55 | Network Trash Folder 56 | Temporary Items 57 | .apdisk 58 | 59 | # Windows 60 | # ========================= 61 | 62 | # Windows image file caches 63 | Thumbs.db 64 | ehthumbs.db 65 | 66 | # Folder config file 67 | Desktop.ini 68 | 69 | # Recycle Bin used on file shares 70 | $RECYCLE.BIN/ 71 | 72 | # Windows Installer files 73 | *.cab 74 | *.msi 75 | *.msm 76 | *.msp 77 | 78 | # Windows shortcuts 79 | *.lnk 80 | -------------------------------------------------------------------------------- /CoreTest/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | *.debug 25 | Makefile* 26 | *.prl 27 | *.app 28 | moc_*.cpp 29 | ui_*.h 30 | qrc_*.cpp 31 | Thumbs.db 32 | *.res 33 | *.rc 34 | /.qmake.cache 35 | /.qmake.stash 36 | 37 | # qtcreator generated files 38 | *.pro.user* 39 | 40 | # xemacs temporary files 41 | *.flc 42 | 43 | # Vim temporary files 44 | .*.swp 45 | 46 | # Visual Studio generated files 47 | *.ib_pdb_index 48 | *.idb 49 | *.ilk 50 | *.pdb 51 | *.sln 52 | *.suo 53 | *.vcproj 54 | *vcproj.*.*.user 55 | *.ncb 56 | *.sdf 57 | *.opensdf 58 | *.vcxproj 59 | *vcxproj.* 60 | 61 | # MinGW generated files 62 | *.Debug 63 | *.Release 64 | 65 | # Python byte code 66 | *.pyc 67 | 68 | # Binaries 69 | # -------- 70 | *.dll 71 | *.exe 72 | 73 | 74 | -------------------------------------------------------------------------------- /CoreTest/CoreTest.pro: -------------------------------------------------------------------------------- 1 | QT += core 2 | QT -= gui 3 | 4 | TARGET = CoreTest 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TEMPLATE = app 9 | 10 | include(../de_skycoder42_qsingleinstance.pri) 11 | 12 | SOURCES += main.cpp 13 | 14 | -------------------------------------------------------------------------------- /CoreTest/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define USE_EXEC 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | QCoreApplication a(argc, argv); 10 | QSingleInstance instance; 11 | instance.setGlobal(true); //example for global access 12 | 13 | #ifdef USE_EXEC 14 | instance.setStartupFunction([&]() -> int { 15 | qDebug() << "Instance running! Start another with -quit as first argument to exit"; 16 | return 0; 17 | }); 18 | 19 | QObject::connect(&instance, &QSingleInstance::instanceMessage, [&](QStringList args){ 20 | qDebug() << "New Instance:" << args; 21 | if(args.size() > 1 && args[1] == "-quit") 22 | qApp->quit(); 23 | }); 24 | 25 | return instance.singleExec(); 26 | #else 27 | if(instance.process()) { 28 | if(!instance.isMaster()) 29 | return 0; 30 | } else 31 | return -1; 32 | 33 | qDebug() << "Instance running! Start another with -quit as first argument to exit"; 34 | 35 | QObject::connect(&instance, &QSingleInstance::instanceMessage, [&](QStringList args){ 36 | qDebug() << "New Instance:" << args; 37 | if(args.size() > 1 && args[1] == "-quit") 38 | qApp->quit(); 39 | }); 40 | 41 | return a.exec(); 42 | #endif 43 | } 44 | 45 | -------------------------------------------------------------------------------- /GuiTest/GuiTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick 4 | CONFIG += c++11 5 | 6 | include(../de_skycoder42_qsingleinstance.pri) 7 | 8 | SOURCES += main.cpp 9 | 10 | RESOURCES += qml.qrc 11 | 12 | # Additional import path used to resolve QML modules in Qt Creator's code model 13 | QML_IMPORT_PATH = 14 | 15 | # Default rules for deployment. 16 | include(deployment.pri) 17 | 18 | -------------------------------------------------------------------------------- /GuiTest/deployment.pri: -------------------------------------------------------------------------------- 1 | unix:!android { 2 | isEmpty(target.path) { 3 | qnx { 4 | target.path = /tmp/$${TARGET}/bin 5 | } else { 6 | target.path = /opt/$${TARGET}/bin 7 | } 8 | export(target.path) 9 | } 10 | INSTALLS += target 11 | } 12 | 13 | export(INSTALLS) 14 | 15 | -------------------------------------------------------------------------------- /GuiTest/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define USE_EXEC 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | QGuiApplication app(argc, argv); 11 | QSingleInstance instance; 12 | 13 | 14 | #ifdef USE_EXEC 15 | QQmlApplicationEngine *engine = nullptr; 16 | 17 | instance.setStartupFunction([&]() -> int { 18 | engine = new QQmlApplicationEngine(NULL); 19 | engine->rootContext()->setContextProperty("instance", &instance); 20 | engine->load(QUrl(QStringLiteral("qrc:/main.qml"))); 21 | 22 | instance.setNotifyWindow(QGuiApplication::topLevelWindows().first()); 23 | 24 | return 0; 25 | }); 26 | 27 | QObject::connect(qApp, &QGuiApplication::aboutToQuit, [&](){ 28 | if(engine) 29 | delete engine; 30 | }); 31 | 32 | return instance.singleExec(); 33 | #else 34 | if(instance.process()) { 35 | if(!instance.isMaster()) 36 | return 0; 37 | } else 38 | return -1; 39 | 40 | QQmlApplicationEngine engine; 41 | engine.rootContext()->setContextProperty("instance", &instance); 42 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 43 | 44 | instance.setNotifyWindow(QGuiApplication::topLevelWindows().first()); 45 | 46 | return app.exec(); 47 | #endif 48 | } 49 | 50 | -------------------------------------------------------------------------------- /GuiTest/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | import QtQuick.Window 2.2 3 | import QtQuick.Dialogs 1.2 4 | 5 | Window { 6 | visible: true 7 | 8 | Connections { 9 | target: instance 10 | onInstanceMessage: { 11 | args.open(); 12 | } 13 | } 14 | 15 | Text { 16 | text: qsTr("Hello World") 17 | anchors.centerIn: parent 18 | } 19 | 20 | MessageDialog { 21 | id: args 22 | title: "Message" 23 | text: "New Instance just started!" 24 | icon: StandardIcon.Information 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /GuiTest/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Felix Barz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of QSingleInstance nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /QSingleInstance.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | WidgetTest \ 5 | GuiTest \ 6 | CoreTest 7 | -------------------------------------------------------------------------------- /QSingleInstance/QSingleInstance: -------------------------------------------------------------------------------- 1 | #include "qsingleinstance.h" 2 | -------------------------------------------------------------------------------- /QSingleInstance/clientinstance.cpp: -------------------------------------------------------------------------------- 1 | #include "clientinstance.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | ClientInstance::ClientInstance(QLocalSocket *socket, QSingleInstancePrivate *parent) : 8 | QObject(parent), 9 | instance(parent), 10 | socket(socket), 11 | stream(socket) 12 | { 13 | connect(socket, &QLocalSocket::readyRead, this, &ClientInstance::newData); 14 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 15 | connect(socket, QOverload::of(&QLocalSocket::error), 16 | this, &ClientInstance::socketError); 17 | #else 18 | connect(socket, &QLocalSocket::errorOccurred, this, &ClientInstance::socketError); 19 | #endif 20 | connect(socket, &QLocalSocket::disconnected, this, &ClientInstance::deleteLater); 21 | } 22 | 23 | ClientInstance::~ClientInstance() 24 | { 25 | if(instance->isRunning) { 26 | if(socket->isOpen()) 27 | socket->close(); 28 | socket->deleteLater(); 29 | } 30 | } 31 | 32 | void ClientInstance::newData() 33 | { 34 | stream.startTransaction(); 35 | QStringList arguments; 36 | stream >> arguments; 37 | 38 | if(stream.commitTransaction()) { 39 | socket->write(ACK); 40 | QTimer::singleShot(5000, socket, &QLocalSocket::disconnectFromServer); 41 | instance->newArgsReceived(arguments); 42 | } 43 | } 44 | 45 | void ClientInstance::socketError(QLocalSocket::LocalSocketError error) 46 | { 47 | if(error == QLocalSocket::PeerClosedError) 48 | return; 49 | qCWarning(logQSingleInstance) << "Failed to receive arguments from client with error:" 50 | << socket->errorString(); 51 | deleteLater(); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /QSingleInstance/clientinstance.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENTINSTANCE_H 2 | #define CLIENTINSTANCE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "qsingleinstance_p.h" 8 | 9 | class ClientInstance : public QObject 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit ClientInstance(QLocalSocket *socket, QSingleInstancePrivate *parent); 15 | ~ClientInstance(); 16 | 17 | private slots: 18 | void newData(); 19 | void socketError(QLocalSocket::LocalSocketError error); 20 | 21 | private: 22 | QSingleInstancePrivate *instance; 23 | QLocalSocket *socket; 24 | QDataStream stream; 25 | }; 26 | 27 | #endif // CLIENTINSTANCE_H 28 | -------------------------------------------------------------------------------- /QSingleInstance/qsingleinstance.cpp: -------------------------------------------------------------------------------- 1 | #include "qsingleinstance.h" 2 | #include "qsingleinstance_p.h" 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef Q_OS_WIN 8 | #include 9 | #else 10 | #include 11 | #include 12 | #endif 13 | 14 | Q_LOGGING_CATEGORY(logQSingleInstance, "QSingleInstance") 15 | 16 | QSingleInstance::QSingleInstance(QObject *parent) : 17 | QObject(parent), 18 | d(new QSingleInstancePrivate(this)) 19 | { 20 | resetInstanceID(); 21 | } 22 | 23 | QString QSingleInstance::instanceID() const 24 | { 25 | return d->fullId; 26 | } 27 | 28 | bool QSingleInstance::isMaster() const 29 | { 30 | return d->isMaster; 31 | } 32 | 33 | bool QSingleInstance::isAutoRecoveryActive() const 34 | { 35 | return d->tryRecover; 36 | } 37 | 38 | bool QSingleInstance::isGobal() const 39 | { 40 | return d->global; 41 | } 42 | 43 | bool QSingleInstance::setStartupFunction(const std::function &function) 44 | { 45 | if(d->isRunning) 46 | return false; 47 | else { 48 | d->startFunc = function; 49 | return true; 50 | } 51 | } 52 | 53 | bool QSingleInstance::process() 54 | { 55 | return process(QCoreApplication::arguments()); 56 | } 57 | 58 | bool QSingleInstance::process(const QStringList &arguments) 59 | { 60 | d->startInstance(); 61 | 62 | if(d->isMaster) 63 | return true; 64 | else if(!arguments.isEmpty()) { 65 | d->client = new QLocalSocket(this); 66 | d->client->connectToServer(d->socketFile(), QIODevice::ReadWrite); 67 | if(d->client->waitForConnected(5000)) { 68 | d->performSend(arguments); 69 | if(d->client->waitForBytesWritten(5000) && d->client->waitForReadyRead(5000)) { 70 | if(d->client->read(ACK.size()) == ACK) { 71 | d->client->disconnect(); 72 | d->client->close(); 73 | return true; 74 | } 75 | } 76 | } 77 | 78 | return d->recoverAction(); 79 | } else 80 | return true; 81 | } 82 | 83 | int QSingleInstance::singleExec(bool autoClose) 84 | { 85 | Q_ASSERT_X(!d->isRunning, Q_FUNC_INFO, "Nested call detected!"); 86 | d->isRunning = true; 87 | int res = 0; 88 | d->startInstance(); 89 | 90 | if(d->isMaster) { 91 | res = d->startFunc(); 92 | if(res == EXIT_SUCCESS) { 93 | if(autoClose) 94 | connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QSingleInstance::closeInstance); 95 | res = QCoreApplication::exec(); 96 | } else if(autoClose) 97 | closeInstance(); 98 | } else { 99 | d->autoClose = autoClose; //store in case of recovery 100 | d->sendArgs(); 101 | res = QCoreApplication::exec(); 102 | d->autoClose = false; //and reset 103 | } 104 | 105 | d->isRunning = false; 106 | return res; 107 | } 108 | 109 | void QSingleInstance::closeInstance() 110 | { 111 | if(d->isMaster) { 112 | d->server->close(); 113 | d->server->deleteLater(); 114 | d->server = nullptr; 115 | d->lockFile->unlock(); 116 | d->isMaster = false; 117 | } 118 | } 119 | 120 | bool QSingleInstance::setInstanceID(QString instanceID) 121 | { 122 | if(d->isRunning || d->isMaster) 123 | return false; 124 | else if(instanceID != d->fullId) { 125 | d->fullId = instanceID; 126 | d->resetLockFile(); 127 | emit instanceIDChanged(instanceID); 128 | } 129 | 130 | return true; 131 | } 132 | 133 | bool QSingleInstance::resetInstanceID() 134 | { 135 | if(d->isRunning || d->isMaster) 136 | return false; 137 | 138 | d->fullId = QCoreApplication::applicationName(); 139 | #ifdef Q_OS_WIN 140 | d->fullId = d->fullId.toLower(); 141 | #endif 142 | d->fullId.remove(QRegularExpression(QStringLiteral("[^a-zA-Z0-9_]"))); 143 | d->fullId.truncate(8); 144 | d->fullId.prepend(QStringLiteral("qsingleinstance-")); 145 | QByteArray hashBase = (QCoreApplication::organizationName() + QLatin1Char('_') + QCoreApplication::applicationName()).toUtf8(); 146 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 147 | quint16 hashValue = qChecksum(hashBase.data(), hashBase.size()); 148 | #else 149 | quint16 hashValue = qChecksum(hashBase); 150 | #endif 151 | d->fullId += QLatin1Char('-') + QString::number(hashValue, 16); 152 | 153 | if(!d->global) { 154 | d->fullId += QLatin1Char('-'); 155 | #ifdef Q_OS_WIN 156 | DWORD sessID; 157 | if(ProcessIdToSessionId(GetCurrentProcessId(), &sessID)) 158 | d->fullId += QString::number(sessID, 16); 159 | #else 160 | d->fullId += QString::number(::getuid(), 16); 161 | #endif 162 | } 163 | 164 | d->resetLockFile(); 165 | return true; 166 | } 167 | 168 | void QSingleInstance::setAutoRecovery(bool autoRecovery) 169 | { 170 | if(d->tryRecover != autoRecovery) { 171 | d->tryRecover = autoRecovery; 172 | emit autoRecoveryChanged(autoRecovery); 173 | } 174 | } 175 | 176 | bool QSingleInstance::setGlobal(bool global, bool recreateId) 177 | { 178 | if(d->isRunning || d->isMaster) 179 | return false; 180 | else { 181 | d->global = global; 182 | if(recreateId) 183 | return resetInstanceID(); 184 | else 185 | return true; 186 | } 187 | } 188 | 189 | void QSingleInstance::setNotifyWindowFn(const std::function &fn) 190 | { 191 | d->notifyFn = fn; 192 | } 193 | -------------------------------------------------------------------------------- /QSingleInstance/qsingleinstance.h: -------------------------------------------------------------------------------- 1 | #ifndef QSINGLEINSTANCEBASE 2 | #define QSINGLEINSTANCEBASE 3 | 4 | #include 5 | #include 6 | #include 7 | #ifdef QT_GUI_LIB 8 | #include 9 | #include 10 | #endif 11 | #ifdef QT_WIDGETS_LIB 12 | #include 13 | #include 14 | #include 15 | #endif 16 | 17 | class QSingleInstancePrivate; 18 | //! A class to provide a single instance application 19 | class QSingleInstance : public QObject 20 | { 21 | Q_OBJECT 22 | 23 | //! Holds the unique ID the instance uses to identify itself and others 24 | Q_PROPERTY(QString instanceID READ instanceID WRITE setInstanceID RESET resetInstanceID NOTIFY instanceIDChanged) 25 | //! Specifies whether the instance should try to recover the application or not 26 | Q_PROPERTY(bool autoRecovery READ isAutoRecoveryActive WRITE setAutoRecovery NOTIFY autoRecoveryChanged) 27 | //! Specifies whether the instance is local (per user) or global (per machine) 28 | Q_PROPERTY(bool global READ isGobal WRITE setGlobal) 29 | 30 | public: 31 | //! Constructor 32 | QSingleInstance(QObject *parent = nullptr); 33 | 34 | //! Specifies whether this instance is master or not 35 | Q_INVOKABLE bool isMaster() const; 36 | 37 | //! READ-Accessor for QSingleInstance::instanceID 38 | QString instanceID() const; 39 | //! READ-Accessor for QSingleInstance::autoRecovery 40 | bool isAutoRecoveryActive() const; 41 | //! READ-Accessor for QSingleInstance::global 42 | bool isGobal() const; 43 | 44 | //! Sets the function to be called when the application starts 45 | bool setStartupFunction(const std::function &function); 46 | #ifdef QT_GUI_LIB 47 | //! Sets a window to be activted on new received arguments 48 | template 49 | void setNotifyWindow(QWindow *window); 50 | #endif 51 | #ifdef QT_WIDGETS_LIB 52 | //! Sets a window to be activted on new received arguments 53 | template 54 | void setNotifyWindow(QWidget *widget); 55 | #endif 56 | 57 | //! Starts the instance if master or sends arguments to the master 58 | bool process(); 59 | //! Starts the instance if master or sends custom arguments to the master 60 | bool process(const QStringList &arguments); 61 | 62 | public slots: 63 | //! Starts the instance if master or sends arguments to the master and runs the Application 64 | int singleExec(bool autoClose = true); 65 | 66 | //! Stops the singleinstance (but not the application) 67 | void closeInstance(); 68 | 69 | //! WRITE-Accessor for QSingleInstance::instanceID 70 | bool setInstanceID(QString instanceID); 71 | //! RESET-Accessor for QSingleInstance::instanceID 72 | bool resetInstanceID(); 73 | //! WRITE-Accessor for QSingleInstance::autoRecovery 74 | void setAutoRecovery(bool autoRecovery); 75 | //! WRITE-Accessor for QSingleInstance::global 76 | bool setGlobal(bool global, bool recreateId = true); 77 | 78 | signals: 79 | //! Will be emitted if the master receives arguments from another instance 80 | void instanceMessage(const QStringList &arguments); 81 | 82 | //! NOTIFY-Accessor for QSingleInstance::instanceID 83 | void instanceIDChanged(QString instanceID); 84 | //! NOTIFY-Accessor for QSingleInstance::autoRecovery 85 | void autoRecoveryChanged(bool autoRecovery); 86 | 87 | private: 88 | QSingleInstancePrivate *d; 89 | 90 | void setNotifyWindowFn(const std::function &fn); 91 | }; 92 | 93 | Q_DECLARE_LOGGING_CATEGORY(logQSingleInstance) 94 | 95 | #ifdef QT_GUI_LIB 96 | template 97 | void QSingleInstance::setNotifyWindow(QWindow *window) 98 | { 99 | QPointer notifyWindow(window); 100 | setNotifyWindowFn([notifyWindow](){ 101 | if(!notifyWindow.isNull()) { 102 | notifyWindow->show(); 103 | notifyWindow->raise(); 104 | notifyWindow->alert(0); 105 | notifyWindow->requestActivate(); 106 | } 107 | }); 108 | } 109 | #endif 110 | 111 | #ifdef QT_WIDGETS_LIB 112 | template 113 | void QSingleInstance::setNotifyWindow(QWidget *widget) 114 | { 115 | QPointer notifyWidget(widget); 116 | setNotifyWindowFn([notifyWidget](){ 117 | if(!notifyWidget.isNull()) { 118 | notifyWidget->setWindowState(notifyWidget->windowState() & ~ Qt::WindowMinimized); 119 | notifyWidget->show(); 120 | notifyWidget->raise(); 121 | QApplication::alert(notifyWidget); 122 | notifyWidget->activateWindow(); 123 | } 124 | }); 125 | } 126 | #endif 127 | 128 | #endif // QSINGLEINSTANCEBASE 129 | 130 | -------------------------------------------------------------------------------- /QSingleInstance/qsingleinstance_p.cpp: -------------------------------------------------------------------------------- 1 | #include "qsingleinstance_p.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "clientinstance.h" 9 | 10 | QSingleInstancePrivate::QSingleInstancePrivate(QSingleInstance *q_ptr) : 11 | QObject(q_ptr), 12 | q(q_ptr), 13 | fullId(), 14 | lockName(), 15 | server(nullptr), 16 | lockFile(nullptr), 17 | isMaster(false), 18 | tryRecover(false), 19 | global(false), 20 | autoClose(false), 21 | isRunning(false), 22 | startFunc([]()->int{return 0;}), 23 | notifyFn(), 24 | client(nullptr), 25 | lockdownTimer(nullptr) 26 | {} 27 | 28 | QString QSingleInstancePrivate::socketFile() const 29 | { 30 | #ifdef Q_OS_UNIX 31 | auto socket = fullId + QStringLiteral(".socket"); 32 | if(!global) 33 | socket = QDir(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation)).absoluteFilePath(socket); 34 | return socket; 35 | #else 36 | return fullId; 37 | #endif 38 | } 39 | 40 | void QSingleInstancePrivate::resetLockFile() 41 | { 42 | lockName = QDir::temp().absoluteFilePath(fullId + QStringLiteral(".lock")); 43 | lockFile.reset(new QLockFile(lockName)); 44 | lockFile->setStaleLockTime(0); 45 | } 46 | 47 | void QSingleInstancePrivate::startInstance() 48 | { 49 | Q_ASSERT_X(isMaster == lockFile->isLocked(), Q_FUNC_INFO, "Lockfile-state is not as expected"); 50 | if(isMaster) 51 | return; 52 | 53 | isMaster = lockFile->tryLock(); 54 | if(isMaster) { 55 | server = new QLocalServer(this); 56 | if(global) 57 | server->setSocketOptions(QLocalServer::WorldAccessOption); 58 | else 59 | server->setSocketOptions(QLocalServer::UserAccessOption); 60 | bool isListening = server->listen(socketFile()); 61 | 62 | if (!isListening && server->serverError() == QAbstractSocket::AddressInUseError) { 63 | if(QLocalServer::removeServer(socketFile())) 64 | isListening = server->listen(socketFile()); 65 | } 66 | 67 | if(!isListening) { 68 | qCWarning(logQSingleInstance) << "Failed to listen for other instances with error:" 69 | << server->errorString(); 70 | } else { 71 | connect(server, &QLocalServer::newConnection, this, &QSingleInstancePrivate::newConnection); 72 | } 73 | } 74 | } 75 | 76 | void QSingleInstancePrivate::recover(int exitCode) 77 | { 78 | if(lockdownTimer) { 79 | lockdownTimer->stop(); 80 | lockdownTimer->deleteLater(); 81 | } 82 | 83 | if(recoverAction()) { 84 | auto res = startFunc(); 85 | if(res == EXIT_SUCCESS) { 86 | if(autoClose) 87 | connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, q, &QSingleInstance::closeInstance); 88 | } else if(autoClose) 89 | q->closeInstance(); 90 | } else 91 | QCoreApplication::exit(exitCode); 92 | } 93 | 94 | bool QSingleInstancePrivate::recoverAction() 95 | { 96 | client->deleteLater(); 97 | client = nullptr; 98 | 99 | if(tryRecover) { 100 | qCInfo(logQSingleInstance) << "Trying to create a new master instance..."; 101 | if(!QFile::exists(lockName) || QFile::remove(lockName)) { 102 | startInstance(); 103 | if(isMaster) { 104 | qCInfo(logQSingleInstance) << "Recovery Successful - This instance is now the master."; 105 | return true; 106 | } 107 | } 108 | qCWarning(logQSingleInstance) << "Failed to create new master instance. The application cannot recover!"; 109 | } 110 | return false; 111 | } 112 | 113 | void QSingleInstancePrivate::sendArgs() 114 | { 115 | client = new QLocalSocket(this); 116 | 117 | connect(client, &QLocalSocket::connected, 118 | this, &QSingleInstancePrivate::clientConnected, Qt::QueuedConnection); 119 | connect(client, &QLocalSocket::readyRead, 120 | this, &QSingleInstancePrivate::sendResultReady, Qt::QueuedConnection); 121 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 122 | connect(client, SIGNAL(error(QLocalSocket::LocalSocketError)), 123 | this, SLOT(clientError(QLocalSocket::LocalSocketError))); 124 | #else 125 | connect(client, SIGNAL(errorOccurred(QLocalSocket::LocalSocketError)), 126 | this, SLOT(clientError(QLocalSocket::LocalSocketError))); 127 | #endif 128 | 129 | client->connectToServer(socketFile(), QIODevice::ReadWrite); 130 | 131 | lockdownTimer = new QTimer(this); 132 | lockdownTimer->setInterval(5000); 133 | lockdownTimer->setSingleShot(true); 134 | connect(lockdownTimer, &QTimer::timeout, this, [this](){ 135 | qCWarning(logQSingleInstance) << "Master did not respond in time"; 136 | recover(QLocalSocket::SocketTimeoutError); 137 | }); 138 | lockdownTimer->start(); 139 | } 140 | 141 | void QSingleInstancePrivate::clientConnected() 142 | { 143 | lockdownTimer->start(); 144 | performSend(QCoreApplication::arguments()); 145 | } 146 | 147 | void QSingleInstancePrivate::performSend(const QStringList &arguments) 148 | { 149 | QDataStream stream(client); 150 | stream << arguments; 151 | client->flush(); 152 | } 153 | 154 | void QSingleInstancePrivate::sendResultReady() 155 | { 156 | if(client->bytesAvailable() >= ACK.size()) { 157 | if(client->read(ACK.size()) == ACK) { 158 | lockdownTimer->stop(); 159 | client->disconnect(); 160 | client->close(); 161 | QCoreApplication::quit(); 162 | } else { 163 | qCWarning(logQSingleInstance) << "Master didn't ackknowledge the sent arguments"; 164 | recover(QLocalSocket::UnknownSocketError); 165 | } 166 | } else 167 | lockdownTimer->start(); 168 | } 169 | 170 | void QSingleInstancePrivate::clientError(QLocalSocket::LocalSocketError error) 171 | { 172 | qCWarning(logQSingleInstance) << "Failed to transfer arguments to master with error:" 173 | << client->errorString(); 174 | recover(error); 175 | } 176 | 177 | void QSingleInstancePrivate::newConnection() 178 | { 179 | while(server->hasPendingConnections()) { 180 | QLocalSocket *instanceClient = server->nextPendingConnection(); 181 | new ClientInstance(instanceClient, this); 182 | } 183 | } 184 | 185 | void QSingleInstancePrivate::newArgsReceived(const QStringList &args) 186 | { 187 | if(notifyFn) 188 | notifyFn(); 189 | QMetaObject::invokeMethod(q, "instanceMessage", Qt::QueuedConnection, 190 | Q_ARG(QStringList, args)); 191 | } 192 | 193 | -------------------------------------------------------------------------------- /QSingleInstance/qsingleinstance_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QSINGLEINSTANCEPRIVATE_H 2 | #define QSINGLEINSTANCEPRIVATE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "qsingleinstance.h" 11 | 12 | #define ACK QByteArray("ACK") 13 | 14 | class QSingleInstancePrivate : public QObject 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | QSingleInstance *q; 20 | 21 | QString fullId; 22 | QString lockName; 23 | QLocalServer *server; 24 | QScopedPointer lockFile; 25 | bool isMaster; 26 | bool tryRecover; 27 | bool global; 28 | bool autoClose; 29 | 30 | bool isRunning; 31 | std::function startFunc; 32 | std::function notifyFn; 33 | 34 | QLocalSocket *client; 35 | QTimer *lockdownTimer; 36 | 37 | QSingleInstancePrivate(QSingleInstance *q_ptr); 38 | 39 | QString socketFile() const; 40 | void resetLockFile(); 41 | 42 | public slots: 43 | void startInstance(); 44 | void recover(int exitCode); 45 | bool recoverAction(); 46 | 47 | void sendArgs(); 48 | void clientConnected(); 49 | void performSend(const QStringList &arguments); 50 | void sendResultReady(); 51 | void clientError(QLocalSocket::LocalSocketError error); 52 | 53 | void newConnection(); 54 | 55 | void newArgsReceived(const QStringList &args); 56 | }; 57 | 58 | #endif // QSINGLEINSTANCEPRIVATE_H 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QSingleInstance 2 | A simple single instance application for Qt 3 | 4 | ## Features 5 | - Allows to run an application as "single instance" 6 | - This means only one instance at a time of the application will be running 7 | - Subsequent starts send their arguments to the running instance, instead of starting a new one 8 | - Instances are generated per user 9 | - It supports all kinds of applications, with or without gui. 10 | - For gui/widgets applications, you can register a window to be activated 11 | 12 | ## Installation 13 | The package is providet as qpm package, [`de.skycoder42.qsingleinstance`](https://www.qpm.io/packages/de.skycoder42.qsingleinstance/index.html). To install: 14 | 15 | 1. Install qpm (See [GitHub - Installing](https://github.com/Cutehacks/qpm/blob/master/README.md#installing), for **windows** see below) 16 | 2. In your projects root directory, run `qpm install de.skycoder42.qsingleinstance` 17 | 3. Include qpm to your project by adding `include(vendor/vendor.pri)` to your `.pro` file 18 | 19 | Check their [GitHub - Usage for App Developers](https://github.com/Cutehacks/qpm/blob/master/README.md#usage-for-app-developers) to learn more about qpm. 20 | 21 | **Important for Windows users:** QPM Version *0.10.0* (the one you can download on the website) is currently broken on windows! It's already fixed in master, but not released yet. Until a newer versions gets released, you can download the latest dev build from here: 22 | - https://storage.googleapis.com/www.qpm.io/download/latest/windows_amd64/qpm.exe 23 | - https://storage.googleapis.com/www.qpm.io/download/latest/windows_386/qpm.exe 24 | 25 | ## Usage 26 | Check the three example projects to see what you can do for each application type. The following shows a simple console-based example: 27 | 28 | ```cpp 29 | #include 30 | 31 | int main(int argc, char *argv[]) 32 | { 33 | QCoreApplication a(argc, argv); 34 | QSingleInstance instance; 35 | 36 | //this lambda only gets executed on the first instance 37 | instance.setStartupFunction([&]() -> int { 38 | qDebug() << "Instance running! Start another with -quit as first argument to exit"; 39 | return 0; 40 | }); 41 | 42 | //new instances send their arguments and this signal is received on the main instance 43 | QObject::connect(&instance, &QSingleInstance::instanceMessage, [&](QStringList args){ 44 | qDebug() << "New Instance:" << args; 45 | if(args.size() > 1 && args[1] == "-quit") 46 | qApp->quit(); 47 | }); 48 | 49 | return instance.singleExec(); 50 | } 51 | 52 | ``` 53 | -------------------------------------------------------------------------------- /WidgetTest/WidgetTest.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2015-10-23T16:34:58 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = QSingleInstance 12 | TEMPLATE = app 13 | 14 | include(../de_skycoder42_qsingleinstance.pri) 15 | 16 | SOURCES += main.cpp\ 17 | mainwindow.cpp 18 | 19 | HEADERS += mainwindow.h 20 | 21 | FORMS += mainwindow.ui 22 | 23 | CONFIG += mobility 24 | MOBILITY = 25 | 26 | -------------------------------------------------------------------------------- /WidgetTest/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #define USE_EXEC 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | QApplication a(argc, argv); 12 | QSingleInstance instance; 13 | instance.setAutoRecovery(true); 14 | 15 | #ifdef USE_EXEC 16 | MainWindow *w = nullptr; 17 | 18 | instance.setStartupFunction([&]() -> int { 19 | w = new MainWindow(nullptr); 20 | instance.setNotifyWindow(w); 21 | w->show(); 22 | return 0; 23 | }); 24 | 25 | QObject::connect(qApp, &QApplication::aboutToQuit, [&](){ 26 | if(w) 27 | delete w; 28 | }); 29 | 30 | QObject::connect(&instance, &QSingleInstance::instanceMessage, [&](QStringList args){ 31 | QMessageBox::information(w, "Message", args.join('\n')); 32 | }); 33 | 34 | return instance.singleExec(); 35 | #else 36 | if(instance.process()) { 37 | if(!instance.isMaster()) 38 | return 0; 39 | } else 40 | return -1; 41 | 42 | MainWindow w; 43 | instance.setNotifyWindow(&w); 44 | 45 | QObject::connect(&instance, &QSingleInstance::instanceMessage, [&](QStringList args){ 46 | QMessageBox::information(w, "Message", args.join('\n')); 47 | }); 48 | 49 | w.show(); 50 | return a.exec(); 51 | #endif 52 | } 53 | -------------------------------------------------------------------------------- /WidgetTest/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | MainWindow::MainWindow(QWidget *parent) : 5 | QMainWindow(parent), 6 | ui(new Ui::MainWindow) 7 | { 8 | ui->setupUi(this); 9 | } 10 | 11 | MainWindow::~MainWindow() 12 | { 13 | delete ui; 14 | } 15 | -------------------------------------------------------------------------------- /WidgetTest/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class MainWindow; 8 | } 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = 0); 16 | ~MainWindow(); 17 | 18 | private: 19 | Ui::MainWindow *ui; 20 | }; 21 | 22 | #endif // MAINWINDOW_H 23 | -------------------------------------------------------------------------------- /WidgetTest/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 480 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 0 22 | 800 23 | 21 24 | 25 | 26 | 27 | 28 | 29 | toolBar 30 | 31 | 32 | TopToolBarArea 33 | 34 | 35 | false 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /de_skycoder42_qsingleinstance.prc: -------------------------------------------------------------------------------- 1 | QT *= network 2 | -------------------------------------------------------------------------------- /de_skycoder42_qsingleinstance.pri: -------------------------------------------------------------------------------- 1 | QT *= network 2 | qpmx_static: QT -= gui widgets 3 | 4 | PUBLIC_HEADERS += \ 5 | $$PWD/QSingleInstance/qsingleinstance.h 6 | 7 | HEADERS += $$PUBLIC_HEADERS \ 8 | $$PWD/QSingleInstance/clientinstance.h \ 9 | $$PWD/QSingleInstance/qsingleinstance_p.h 10 | 11 | SOURCES += \ 12 | $$PWD/QSingleInstance/qsingleinstance.cpp \ 13 | $$PWD/QSingleInstance/clientinstance.cpp \ 14 | $$PWD/QSingleInstance/qsingleinstance_p.cpp 15 | 16 | INCLUDEPATH += $$PWD/QSingleInstance 17 | -------------------------------------------------------------------------------- /qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "skycoder42.de@gmx.de", 4 | "name": "Skycoder" 5 | }, 6 | "dependencies": [ 7 | ], 8 | "description": "A simple single instance application for Qt", 9 | "license": "BSD_3_CLAUSE", 10 | "name": "de.skycoder42.qsingleinstance", 11 | "pri_filename": "de_skycoder42_qsingleinstance.pri", 12 | "repository": { 13 | "type": "GITHUB", 14 | "url": "https://github.com/Skycoder42/QSingleInstance.git" 15 | }, 16 | "version": { 17 | "fingerprint": "", 18 | "label": "1.2.2", 19 | "revision": "" 20 | }, 21 | "webpage": "https://github.com/Skycoder42/QSingleInstance" 22 | } 23 | -------------------------------------------------------------------------------- /qpmx.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | ], 4 | "license": { 5 | "file": "LICENSE", 6 | "name": "BSD_3_CLAUSE" 7 | }, 8 | "prcFile": "de_skycoder42_qsingleinstance.prc", 9 | "priFile": "de_skycoder42_qsingleinstance.pri", 10 | "priIncludes": [ 11 | ], 12 | "publishers": { 13 | "qpm": { 14 | "author": { 15 | "email": "skycoder42.de@gmx.de", 16 | "name": "Skycoder" 17 | }, 18 | "description": "A simple single instance application for Qt", 19 | "name": "de.skycoder42.qsingleinstance", 20 | "repository": { 21 | "type": "GITHUB", 22 | "url": "https://github.com/Skycoder42/QSingleInstance.git" 23 | }, 24 | "webpage": "https://github.com/Skycoder42/QSingleInstance" 25 | } 26 | }, 27 | "source": false 28 | } 29 | --------------------------------------------------------------------------------