QtPass
2.0.0
Developer documentation
Toggle main menu visibility
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
16
SingleApplication::SingleApplication
(
17
int
&argc,
char
*argv[],
// NOLINT(modernize-avoid-c-arrays)
18
QString uniqueKey)
19
: QApplication(argc, argv),
_isRunning
(false),
20
_uniqueKey
(std::move(uniqueKey)) {
21
sharedMemory
.setKey(
_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
38
void
SingleApplication::receiveMessage
() {
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.
62
auto
SingleApplication::isRunning
() ->
bool
{
return
_isRunning
; }
63
64
auto
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();
77
becomePrimary
();
78
return
false
;
79
}
80
81
// private functions.
82
void
SingleApplication::becomePrimary
() {
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
,
110
&
SingleApplication::receiveMessage
);
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
122
auto
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
142
auto
SingleApplication::peerIsListening
() ->
bool
{
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
}
SingleApplication::sharedMemory
QSharedMemory sharedMemory
The one-byte segment whose existence marks a running instance.
Definition
singleapplication.h:107
SingleApplication::_isRunning
bool _isRunning
true when another instance holds the segment and answers on the socket; this instance should forward ...
Definition
singleapplication.h:103
SingleApplication::forwardMessage
auto forwardMessage(const QString &message) -> bool
Deliver a message over the local socket.
Definition
singleapplication.cpp:122
SingleApplication::receiveMessage
void receiveMessage()
Slot for QLocalServer::newConnection: accept every pending connection and emit messageAvailable() wit...
Definition
singleapplication.cpp:38
SingleApplication::localServer
QScopedPointer< QLocalServer > localServer
Server accepting forwarded command lines; only set in the primary instance, null in a secondary or wh...
Definition
singleapplication.h:112
SingleApplication::SingleApplication
SingleApplication(int &argc, char *argv[], QString uniqueKey)
Construct a SingleApplication with a unique instance key.
Definition
singleapplication.cpp:16
SingleApplication::becomePrimary
void becomePrimary()
Claim the segment and listen; no-op when another instance holds it.
Definition
singleapplication.cpp:82
SingleApplication::_uniqueKey
QString _uniqueKey
Name of both the shared-memory segment and the local socket.
Definition
singleapplication.h:105
SingleApplication::timeout
static const int timeout
Milliseconds to wait for a socket connect or write.
Definition
singleapplication.h:115
SingleApplication::isRunning
auto isRunning() -> bool
Query whether another instance is already running.
Definition
singleapplication.cpp:62
SingleApplication::maxMessageBytes
static const int maxMessageBytes
Upper bound for one forwarded message (a command line).
Definition
singleapplication.h:123
SingleApplication::messageAvailable
void messageAvailable(const QString &message)
messageAvailable notification from commandline
SingleApplication::takeoverProbes
static const int takeoverProbes
Probes for the winner of a takeover race before giving up on IPC.
Definition
singleapplication.h:125
SingleApplication::sendMessage
auto sendMessage(const QString &message) -> bool
Send a message to the running instance.
Definition
singleapplication.cpp:64
SingleApplication::peerIsListening
auto peerIsListening() -> bool
Probe whether an instance is actually accepting connections.
Definition
singleapplication.cpp:142
SingleApplication::takeoverProbeIntervalMs
static const int takeoverProbeIntervalMs
Pause between two takeover probes, in milliseconds.
Definition
singleapplication.h:127
qtpasslogging.h
The "qtpass" logging category.
singleapplication.h
SingleApplication: a QApplication that keeps one QtPass instance per user via a shared-memory segment...
src
singleapplication.cpp
Generated by
1.18.0