QtPass 2.0.0
Developer documentation
Loading...
Searching...
No Matches
singleapplication.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
8#include "singleapplication.h"
9#include "qtpasslogging.h"
10#include <QDebug>
11#include <QLocalSocket>
12#include <QSharedPointer>
13#include <QThread>
14#include <utility>
15
17 int &argc, char *argv[], // NOLINT(modernize-avoid-c-arrays)
18 QString uniqueKey)
19 : QApplication(argc, argv), _isRunning(false),
20 _uniqueKey(std::move(uniqueKey)) {
22 if (sharedMemory.attach()) {
23 if (peerIsListening()) {
24 _isRunning = true;
25 return;
26 }
27 // The segment outlived the instance that created it (a crash leaves
28 // System V shared memory behind on Unix). Nobody answers on the socket,
29 // so take over instead of forwarding into the void forever.
30 qCDebug(lcQtPass) << "Stale single instance segment, taking over.";
31 sharedMemory.detach();
32 }
33 becomePrimary();
34}
35
36// public slots.
37
39 while (QLocalSocket *localSocket = localServer->nextPendingConnection()) {
40 auto buffer = QSharedPointer<QByteArray>::create();
41 connect(localSocket, &QLocalSocket::readyRead, this,
42 [localSocket, buffer]() {
43 buffer->append(localSocket->readAll());
44 if (buffer->size() > maxMessageBytes) {
45 // Not a command line any more; drop the peer and its data.
46 buffer->clear();
47 localSocket->abort();
48 }
49 });
50 connect(localSocket, &QLocalSocket::disconnected, this,
51 [this, localSocket, buffer]() {
52 buffer->append(localSocket->readAll());
53 if (!buffer->isEmpty()) {
54 emit messageAvailable(QString::fromUtf8(buffer->constData()));
55 }
56 localSocket->deleteLater();
57 });
58 }
59}
60
61// public functions.
62auto SingleApplication::isRunning() -> bool { return _isRunning; }
63
64auto SingleApplication::sendMessage(const QString &message) -> bool {
65 if (!_isRunning) {
66 return false;
67 }
68 if (forwardMessage(message)) {
69 return true;
70 }
71 // The peer vanished between the probe in the constructor and now. Let go of
72 // its segment and take its place, so the window main() opens next is the
73 // reachable instance. Staying attached without listening would make every
74 // later launch fail create() and open yet another window without IPC.
75 _isRunning = false;
76 sharedMemory.detach();
78 return false;
79}
80
81// private functions.
83 // create shared memory.
84 if (!sharedMemory.create(1)) {
85 // Another launch claimed the segment first (two launchers recovering
86 // from the same crash). Give it a moment to start listening and become
87 // its secondary, so main() forwards instead of opening a second window.
88 if (sharedMemory.attach()) {
89 for (int attempt = 0; attempt < takeoverProbes; ++attempt) {
90 if (peerIsListening()) {
91 _isRunning = true;
92 return;
93 }
94 QThread::msleep(takeoverProbeIntervalMs);
95 }
96 sharedMemory.detach();
97 }
98 qCDebug(lcQtPass) << "Unable to create single instance.";
99 return;
100 }
101 // create local server and listen to incoming messages from other
102 // instances. A socket file left behind by a crashed instance would make
103 // listen() fail (address in use) and silently disable IPC for good, so
104 // clear it first: no live peer answered on it, or create() would have
105 // failed.
106 QLocalServer::removeServer(_uniqueKey);
107 localServer.reset(new QLocalServer(this));
108 localServer->setSocketOptions(QLocalServer::UserAccessOption);
109 connect(localServer.data(), &QLocalServer::newConnection, this,
111 if (!localServer->listen(_uniqueKey)) {
112 qCWarning(lcQtPass) << "SingleApplication: cannot listen on" << _uniqueKey
113 << ":" << localServer->errorString();
114 // Holding the segment without a server would make every later launch
115 // attach, fail the probe and then fail create(): no instance could ever
116 // take over while this one lives. Release it and run without IPC.
117 localServer.reset();
118 sharedMemory.detach();
119 }
120}
121
122auto SingleApplication::forwardMessage(const QString &message) -> bool {
123 QLocalSocket localSocket(this);
124 localSocket.connectToServer(_uniqueKey, QIODevice::WriteOnly);
125 if (!localSocket.waitForConnected(timeout)) {
126 qCDebug(lcQtPass) << localSocket.errorString().toLatin1();
127 return false;
128 }
129 QByteArray payload = message.toUtf8();
130 if (payload.isEmpty()) {
131 payload = QByteArray(1, '\0');
132 }
133 localSocket.write(payload);
134 if (!localSocket.waitForBytesWritten(timeout)) {
135 qCDebug(lcQtPass) << localSocket.errorString().toLatin1();
136 return false;
137 }
138 localSocket.disconnectFromServer();
139 return true;
140}
141
143 QLocalSocket probe;
144 probe.connectToServer(_uniqueKey, QIODevice::WriteOnly);
145 if (!probe.waitForConnected(timeout)) {
146 return false;
147 }
148 probe.disconnectFromServer();
149 return true;
150}
QSharedMemory sharedMemory
The one-byte segment whose existence marks a running instance.
bool _isRunning
true when another instance holds the segment and answers on the socket; this instance should forward ...
auto forwardMessage(const QString &message) -> bool
Deliver a message over the local socket.
void receiveMessage()
Slot for QLocalServer::newConnection: accept every pending connection and emit messageAvailable() wit...
QScopedPointer< QLocalServer > localServer
Server accepting forwarded command lines; only set in the primary instance, null in a secondary or wh...
SingleApplication(int &argc, char *argv[], QString uniqueKey)
Construct a SingleApplication with a unique instance key.
void becomePrimary()
Claim the segment and listen; no-op when another instance holds it.
QString _uniqueKey
Name of both the shared-memory segment and the local socket.
static const int timeout
Milliseconds to wait for a socket connect or write.
auto isRunning() -> bool
Query whether another instance is already running.
static const int maxMessageBytes
Upper bound for one forwarded message (a command line).
void messageAvailable(const QString &message)
messageAvailable notification from commandline
static const int takeoverProbes
Probes for the winner of a takeover race before giving up on IPC.
auto sendMessage(const QString &message) -> bool
Send a message to the running instance.
auto peerIsListening() -> bool
Probe whether an instance is actually accepting connections.
static const int takeoverProbeIntervalMs
Pause between two takeover probes, in milliseconds.
The "qtpass" logging category.
SingleApplication: a QApplication that keeps one QtPass instance per user via a shared-memory segment...