QtPass Developer documentation
QtPass test coverage
Current view: top level - src/singleapplication.cpp (source / functions) Coverage Total Hit
Test: QtPass Lines: 96.3 % 81 78
Test Date: 2026-11-14 13:19:18 Functions: 100.0 % 9 9

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief Implements SingleApplication; stale-segment and takeover-race
       6              :  *        recovery lives in the constructor and becomePrimary().
       7              :  */
       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            6 : SingleApplication::SingleApplication(
      17              :     int &argc, char *argv[], // NOLINT(modernize-avoid-c-arrays)
      18            6 :     QString uniqueKey)
      19            6 :     : QApplication(argc, argv), _isRunning(false),
      20            6 :       _uniqueKey(std::move(uniqueKey)) {
      21            6 :   sharedMemory.setKey(_uniqueKey);
      22            6 :   if (sharedMemory.attach()) {
      23            5 :     if (peerIsListening()) {
      24            3 :       _isRunning = true;
      25            3 :       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            4 :     qCDebug(lcQtPass) << "Stale single instance segment, taking over.";
      31            2 :     sharedMemory.detach();
      32              :   }
      33            3 :   becomePrimary();
      34            0 : }
      35              : 
      36              : // public slots.
      37              : 
      38            9 : void SingleApplication::receiveMessage() {
      39           18 :   while (QLocalSocket *localSocket = localServer->nextPendingConnection()) {
      40            9 :     auto buffer = QSharedPointer<QByteArray>::create();
      41            9 :     connect(localSocket, &QLocalSocket::readyRead, this,
      42           18 :             [localSocket, buffer]() {
      43           18 :               buffer->append(localSocket->readAll());
      44            9 :               if (buffer->size() > maxMessageBytes) {
      45              :                 // Not a command line any more; drop the peer and its data.
      46            1 :                 buffer->clear();
      47            1 :                 localSocket->abort();
      48              :               }
      49            9 :             });
      50            9 :     connect(localSocket, &QLocalSocket::disconnected, this,
      51           27 :             [this, localSocket, buffer]() {
      52           18 :               buffer->append(localSocket->readAll());
      53            9 :               if (!buffer->isEmpty()) {
      54           12 :                 emit messageAvailable(QString::fromUtf8(buffer->constData()));
      55              :               }
      56            9 :               localSocket->deleteLater();
      57            9 :             });
      58            9 :   }
      59            9 : }
      60              : 
      61              : // public functions.
      62           24 : auto SingleApplication::isRunning() -> bool { return _isRunning; }
      63              : 
      64            5 : auto SingleApplication::sendMessage(const QString &message) -> bool {
      65            5 :   if (!_isRunning) {
      66              :     return false;
      67              :   }
      68            4 :   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            2 :   _isRunning = false;
      76            2 :   sharedMemory.detach();
      77            2 :   becomePrimary();
      78            2 :   return false;
      79              : }
      80              : 
      81              : // private functions.
      82            5 : void SingleApplication::becomePrimary() {
      83              :   // create shared memory.
      84            5 :   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            2 :     if (sharedMemory.attach()) {
      89            7 :       for (int attempt = 0; attempt < takeoverProbes; ++attempt) {
      90            6 :         if (peerIsListening()) {
      91            1 :           _isRunning = true;
      92            1 :           return;
      93              :         }
      94            5 :         QThread::msleep(takeoverProbeIntervalMs);
      95              :       }
      96            1 :       sharedMemory.detach();
      97              :     }
      98            2 :     qCDebug(lcQtPass) << "Unable to create single instance.";
      99            1 :     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            3 :   QLocalServer::removeServer(_uniqueKey);
     107            3 :   localServer.reset(new QLocalServer(this));
     108            3 :   localServer->setSocketOptions(QLocalServer::UserAccessOption);
     109            3 :   connect(localServer.data(), &QLocalServer::newConnection, this,
     110            6 :           &SingleApplication::receiveMessage);
     111            3 :   if (!localServer->listen(_uniqueKey)) {
     112            2 :     qCWarning(lcQtPass) << "SingleApplication: cannot listen on" << _uniqueKey
     113            2 :                         << ":" << 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            1 :     localServer.reset();
     118            1 :     sharedMemory.detach();
     119              :   }
     120              : }
     121              : 
     122            4 : auto SingleApplication::forwardMessage(const QString &message) -> bool {
     123            4 :   QLocalSocket localSocket(this);
     124            4 :   localSocket.connectToServer(_uniqueKey, QIODevice::WriteOnly);
     125            4 :   if (!localSocket.waitForConnected(timeout)) {
     126            4 :     qCDebug(lcQtPass) << localSocket.errorString().toLatin1();
     127            2 :     return false;
     128              :   }
     129              :   QByteArray payload = message.toUtf8();
     130            2 :   if (payload.isEmpty()) {
     131            2 :     payload = QByteArray(1, '\0');
     132              :   }
     133            2 :   localSocket.write(payload);
     134            2 :   if (!localSocket.waitForBytesWritten(timeout)) {
     135            0 :     qCDebug(lcQtPass) << localSocket.errorString().toLatin1();
     136            0 :     return false;
     137              :   }
     138            2 :   localSocket.disconnectFromServer();
     139              :   return true;
     140            4 : }
     141              : 
     142           11 : auto SingleApplication::peerIsListening() -> bool {
     143           11 :   QLocalSocket probe;
     144           11 :   probe.connectToServer(_uniqueKey, QIODevice::WriteOnly);
     145           11 :   if (!probe.waitForConnected(timeout)) {
     146              :     return false;
     147              :   }
     148            4 :   probe.disconnectFromServer();
     149              :   return true;
     150           11 : }
        

Generated by: LCOV version 2.0-1