1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "qtsingleapplication.h"
27 #include "qtlocalpeer.h"
28 
29 #include <qtlockedfile.h>
30 
31 #include <QDir>
32 #include <QFileOpenEvent>
33 #include <QSharedMemory>
34 #include <QWidget>
35 
36 namespace SharedTools {
37 
38 static const int instancesSize = 1024;
39 
instancesLockFilename(const QString & appSessionId)40 static QString instancesLockFilename(const QString &appSessionId)
41 {
42     const QChar slash(QLatin1Char('/'));
43     QString res = QDir::tempPath();
44     if (!res.endsWith(slash))
45         res += slash;
46     return res + appSessionId + QLatin1String("-instances");
47 }
48 
QtSingleApplication(const QString & appId,int & argc,char ** argv)49 QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
50     : QApplication(argc, argv),
51       firstPeer(-1),
52       pidPeer(0)
53 {
54     this->appId = appId;
55 
56     const QString appSessionId = QtLocalPeer::appSessionId(appId);
57 
58     // This shared memory holds a zero-terminated array of active (or crashed) instances
59     instances = new QSharedMemory(appSessionId, this);
60     actWin = 0;
61     block = false;
62 
63     // First instance creates the shared memory, later instances attach to it
64     const bool created = instances->create(instancesSize);
65     if (!created) {
66         if (!instances->attach()) {
67             qWarning() << "Failed to initialize instances shared memory: "
68                        << instances->errorString();
69             delete instances;
70             instances = 0;
71             return;
72         }
73     }
74 
75     // QtLockedFile is used to workaround QTBUG-10364
76     QtLockedFile lockfile(instancesLockFilename(appSessionId));
77 
78     lockfile.open(QtLockedFile::ReadWrite);
79     lockfile.lock(QtLockedFile::WriteLock);
80     qint64 *pids = static_cast<qint64 *>(instances->data());
81     if (!created) {
82         // Find the first instance that it still running
83         // The whole list needs to be iterated in order to append to it
84         for (; *pids; ++pids) {
85             if (firstPeer == -1 && isRunning(*pids))
86                 firstPeer = *pids;
87         }
88     }
89     // Add current pid to list and terminate it
90     *pids++ = QCoreApplication::applicationPid();
91     *pids = 0;
92     pidPeer = new QtLocalPeer(this, appId + QLatin1Char('-') +
93                               QString::number(QCoreApplication::applicationPid()));
94     connect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), SIGNAL(messageReceived(QString,QObject*)));
95     pidPeer->isClient();
96     lockfile.unlock();
97 }
98 
~QtSingleApplication()99 QtSingleApplication::~QtSingleApplication()
100 {
101     if (!instances)
102         return;
103     const qint64 appPid = QCoreApplication::applicationPid();
104     QtLockedFile lockfile(instancesLockFilename(QtLocalPeer::appSessionId(appId)));
105     lockfile.open(QtLockedFile::ReadWrite);
106     lockfile.lock(QtLockedFile::WriteLock);
107     // Rewrite array, removing current pid and previously crashed ones
108     qint64 *pids = static_cast<qint64 *>(instances->data());
109     qint64 *newpids = pids;
110     for (; *pids; ++pids) {
111         if (*pids != appPid && isRunning(*pids))
112             *newpids++ = *pids;
113     }
114     *newpids = 0;
115     lockfile.unlock();
116 }
117 
event(QEvent * event)118 bool QtSingleApplication::event(QEvent *event)
119 {
120     if (event->type() == QEvent::FileOpen) {
121         QFileOpenEvent *foe = static_cast<QFileOpenEvent*>(event);
122         emit fileOpenRequest(foe->file());
123         return true;
124     }
125     return QApplication::event(event);
126 }
127 
isRunning(qint64 pid)128 bool QtSingleApplication::isRunning(qint64 pid)
129 {
130     if (pid == -1) {
131         pid = firstPeer;
132         if (pid == -1)
133             return false;
134     }
135 
136     QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10));
137     return peer.isClient();
138 }
139 
sendMessage(const QString & message,int timeout,qint64 pid)140 bool QtSingleApplication::sendMessage(const QString &message, int timeout, qint64 pid)
141 {
142     if (pid == -1) {
143         pid = firstPeer;
144         if (pid == -1)
145             return false;
146     }
147 
148     QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10));
149     return peer.sendMessage(message, timeout, block);
150 }
151 
applicationId() const152 QString QtSingleApplication::applicationId() const
153 {
154     return appId;
155 }
156 
setBlock(bool value)157 void QtSingleApplication::setBlock(bool value)
158 {
159     block = value;
160 }
161 
setActivationWindow(QWidget * aw,bool activateOnMessage)162 void QtSingleApplication::setActivationWindow(QWidget *aw, bool activateOnMessage)
163 {
164     actWin = aw;
165     if (!pidPeer)
166         return;
167     if (activateOnMessage)
168         connect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), this, SLOT(activateWindow()));
169     else
170         disconnect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), this, SLOT(activateWindow()));
171 }
172 
173 
activationWindow() const174 QWidget* QtSingleApplication::activationWindow() const
175 {
176     return actWin;
177 }
178 
179 
activateWindow()180 void QtSingleApplication::activateWindow()
181 {
182     if (actWin) {
183         actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
184         actWin->raise();
185         actWin->activateWindow();
186     }
187 }
188 
189 } // namespace SharedTools
190