11 #include "kdsingleapplication_localsocket_p.h"
13 #include <QtCore/QDir>
14 #include <QtCore/QDeadlineTimer>
15 #include <QtCore/QTimer>
16 #include <QtCore/QLockFile>
17 #include <QtCore/QDataStream>
19 #include <QtCore/QtDebug>
20 #include <QtCore/QLoggingCategory>
22 #include <QtNetwork/QLocalServer>
23 #include <QtNetwork/QLocalSocket>
28 #if defined(Q_OS_UNIX)
30 #include <sys/types.h>
36 #include <qt_windows.h>
47 KDSingleApplicationLocalSocket::KDSingleApplicationLocalSocket(
const QString &name, KDSingleApplication::Options options, QObject *parent)
51 m_socketName = QStringLiteral(
"kdsingleapp");
53 #if defined(Q_OS_UNIX)
55 m_socketName += QStringLiteral(
"-");
56 uid_t uid = ::getuid();
57 struct passwd *pw = ::getpwuid(uid);
59 QString username = QString::fromUtf8(pw->pw_name);
60 m_socketName += username;
62 m_socketName += QString::number(uid);
66 QString sessionId = qEnvironmentVariable(
"XDG_SESSION_ID");
67 if (!sessionId.isEmpty()) {
68 m_socketName += QStringLiteral(
"-");
69 m_socketName += sessionId;
72 #elif defined(Q_OS_WIN)
77 DWORD usernameLen = UNLEN + 1;
78 wchar_t username[UNLEN + 1];
79 if (GetUserNameW(username, &usernameLen)) {
80 m_socketName += QStringLiteral(
"-");
81 m_socketName += QString::fromWCharArray(username);
86 BOOL haveSessionId = ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
88 m_socketName += QStringLiteral(
"-");
89 m_socketName += QString::number(sessionId);
93 #error "KDSingleApplication has not been ported to this platform"
96 m_socketName += QStringLiteral(
"-");
99 const QString lockFilePath =
100 QDir::tempPath() + QLatin1Char(
'/') + m_socketName + QLatin1String(
".lock");
102 qCDebug(kdsaLocalSocket) <<
"Socket name is" << m_socketName;
103 qCDebug(kdsaLocalSocket) <<
"Lock file path is" << lockFilePath;
105 std::unique_ptr<QLockFile> lockFile(
new QLockFile(lockFilePath));
106 lockFile->setStaleLockTime(0);
108 if (!lockFile->tryLock()) {
110 qCDebug(kdsaLocalSocket) <<
"Secondary instance";
114 qCDebug(kdsaLocalSocket) <<
"Primary instance";
116 std::unique_ptr<QLocalServer> server = std::make_unique<QLocalServer>();
117 if (!server->listen(m_socketName)) {
119 QLocalServer::removeServer(m_socketName);
120 if (!server->listen(m_socketName)) {
122 qWarning(
"KDSingleApplication: unable to make the primary instance listen on %ls: %ls",
123 qUtf16Printable(m_socketName),
124 qUtf16Printable(server->errorString()));
130 connect(server.get(), &QLocalServer::newConnection,
131 this, &KDSingleApplicationLocalSocket::handleNewConnection);
133 m_lockFile = std::move(lockFile);
134 m_localServer = std::move(server);
137 KDSingleApplicationLocalSocket::~KDSingleApplicationLocalSocket() =
default;
139 bool KDSingleApplicationLocalSocket::isPrimaryInstance()
const
141 return m_localServer !=
nullptr;
144 bool KDSingleApplicationLocalSocket::sendMessage(
const QByteArray &message,
int timeout)
146 Q_ASSERT(!isPrimaryInstance());
149 qCDebug(kdsaLocalSocket) <<
"Preparing to send message" << message <<
"with timeout" << timeout;
151 QDeadlineTimer deadline(timeout);
158 socket.connectToServer(m_socketName);
159 if (socket.waitForConnected(deadline.remainingTime()))
161 }
while (!deadline.hasExpired());
163 qCDebug(kdsaLocalSocket) <<
"Socket state:" << socket.state() <<
"Timer remaining" << deadline.remainingTime() <<
"Expired?" << deadline.hasExpired();
165 if (deadline.hasExpired()) {
166 qCWarning(kdsaLocalSocket) <<
"Connection timed out";
173 QByteArray encodedMessage;
174 QDataStream ds(&encodedMessage, QIODevice::WriteOnly);
176 socket.write(encodedMessage);
179 qCDebug(kdsaLocalSocket) <<
"Wrote message in the socket"
180 <<
"Timer remaining" << deadline.remainingTime() <<
"Expired?" << deadline.hasExpired();
185 while (socket.bytesToWrite() > 0) {
186 if (!socket.waitForBytesWritten(deadline.remainingTime())) {
187 qCWarning(kdsaLocalSocket) <<
"Message to primary timed out";
192 qCDebug(kdsaLocalSocket) <<
"Bytes written, now disconnecting"
193 <<
"Timer remaining" << deadline.remainingTime() <<
"Expired?" << deadline.hasExpired();
195 socket.disconnectFromServer();
197 if (socket.state() == QLocalSocket::UnconnectedState) {
198 qCDebug(kdsaLocalSocket) <<
"Disconnected -- success!";
202 if (!socket.waitForDisconnected(deadline.remainingTime())) {
203 qCWarning(kdsaLocalSocket) <<
"Disconnection from primary timed out";
207 qCDebug(kdsaLocalSocket) <<
"Disconnected -- success!";
212 void KDSingleApplicationLocalSocket::handleNewConnection()
214 Q_ASSERT(m_localServer);
216 QLocalSocket *socket;
217 while ((socket = m_localServer->nextPendingConnection())) {
218 qCDebug(kdsaLocalSocket) <<
"Got new connection on" << m_socketName <<
"state" << socket->state();
220 Connection c(socket);
221 socket = c.socket.get();
223 c.readDataConnection = QObjectConnectionHolder(
224 connect(socket, &QLocalSocket::readyRead,
225 this, &KDSingleApplicationLocalSocket::readDataFromSecondary));
227 c.secondaryDisconnectedConnection = QObjectConnectionHolder(
228 connect(socket, &QLocalSocket::disconnected,
229 this, &KDSingleApplicationLocalSocket::secondaryDisconnected));
231 c.abortConnection = QObjectConnectionHolder(
232 connect(c.timeoutTimer.get(), &QTimer::timeout,
233 this, &KDSingleApplicationLocalSocket::abortConnectionToSecondary));
235 m_clients.push_back(std::move(c));
239 if (readDataFromSecondarySocket(socket))
242 if (socket->state() == QLocalSocket::UnconnectedState)
243 secondarySocketDisconnected(socket);
247 template<
typename Container>
250 auto i = std::find_if(container.begin(),
252 [socket](
const auto &c) { return c.socket.get() == socket; });
253 Q_ASSERT(i != container.end());
257 template<
typename Container>
260 auto i = std::find_if(container.begin(),
262 [timer](
const auto &c) { return c.timeoutTimer.get() == timer; });
263 Q_ASSERT(i != container.end());
267 void KDSingleApplicationLocalSocket::readDataFromSecondary()
269 QLocalSocket *socket =
static_cast<QLocalSocket *
>(sender());
270 readDataFromSecondarySocket(socket);
273 bool KDSingleApplicationLocalSocket::readDataFromSecondarySocket(QLocalSocket *socket)
277 c.readData.append(socket->readAll());
279 qCDebug(kdsaLocalSocket) <<
"Got more data from a secondary. Data read so far:" << c.readData;
281 const QByteArray &data = c.readData;
283 if (data.size() >= 1) {
285 qCDebug(kdsaLocalSocket) <<
"Got an invalid protocol version";
291 QDataStream ds(data);
294 ds.startTransaction();
298 if (ds.commitTransaction()) {
299 qCDebug(kdsaLocalSocket) <<
"Got a complete message:" << message;
300 Q_EMIT messageReceived(message);
308 void KDSingleApplicationLocalSocket::secondaryDisconnected()
310 QLocalSocket *socket =
static_cast<QLocalSocket *
>(sender());
311 secondarySocketDisconnected(socket);
314 void KDSingleApplicationLocalSocket::secondarySocketDisconnected(QLocalSocket *socket)
317 Connection c = std::move(*i);
320 qCDebug(kdsaLocalSocket) <<
"Secondary disconnected. Data read:" << c.readData;
323 void KDSingleApplicationLocalSocket::abortConnectionToSecondary()
325 QTimer *timer =
static_cast<QTimer *
>(sender());
328 Connection c = std::move(*i);
331 qCDebug(kdsaLocalSocket) <<
"Secondary timed out. Data read:" << c.readData;
334 KDSingleApplicationLocalSocket::Connection::Connection(QLocalSocket *_socket)
336 , timeoutTimer(new QTimer)
@ IncludeSessionInSocketName
@ IncludeUsernameInSocketName
static auto findConnectionByTimer(Container &container, QTimer *timer)
static auto findConnectionBySocket(Container &container, QLocalSocket *socket)
static const auto LOCALSOCKET_CONNECTION_TIMEOUT
static const char LOCALSOCKET_PROTOCOL_VERSION
Q_LOGGING_CATEGORY(kdsaLocalSocket, "kdsingleapplication.localsocket", QtWarningMsg)