LCOV - code coverage report
Current view: top level - muse/framework/multiwindows/internal - singleinstance.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 91 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2026 MuseScore Limited
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program. If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "../singleinstance.h"
      24              : 
      25              : #include <QLocalServer>
      26              : #include <QLocalSocket>
      27              : 
      28              : #include "multiprocess/ipc/ipc.h"
      29              : #include "multiprocess/ipc/ipclock.h"
      30              : 
      31              : #include "log.h"
      32              : 
      33              : namespace muse::mi {
      34              : static const QString SINGLE_INSTANCE_ACTIVATE("single_instance_activate");
      35              : constexpr int ACK_WAIT_MSEC = 5000;
      36              : }
      37              : 
      38            0 : bool muse::mi::activateExistingInstance(const QString& applicationId, const QStringList& args)
      39              : {
      40            0 :     if (applicationId.isEmpty()) {
      41            0 :         LOGE() << "empty applicationId";
      42            0 :         return false;
      43              :     }
      44              : 
      45            0 :     ipc::IpcLock lock(applicationId);
      46            0 :     ipc::IpcLockGuard guard(&lock);
      47              : 
      48            0 :     QLocalSocket socket;
      49            0 :     socket.connectToServer(applicationId);
      50            0 :     if (!socket.waitForConnected(ipc::TIMEOUT_MSEC)) {
      51            0 :         LOGD() << "no listener for " << applicationId << "; this is the first instance";
      52            0 :         return false;
      53              :     }
      54              : 
      55            0 :     ipc::Msg msg;
      56            0 :     msg.type = ipc::MsgType::Notify;
      57            0 :     msg.method = SINGLE_INSTANCE_ACTIVATE;
      58            0 :     msg.args = args;
      59              : 
      60            0 :     QByteArray data;
      61            0 :     ipc::serialize(msg, data);
      62              : 
      63            0 :     if (!ipc::writeToSocket(&socket, data)) {
      64            0 :         LOGE() << "failed to write activation message";
      65            0 :         return false;
      66              :     }
      67              : 
      68            0 :     if (!socket.waitForReadyRead(ACK_WAIT_MSEC)) {
      69            0 :         LOGW() << "no ack from existing instance within " << ACK_WAIT_MSEC << "ms";
      70            0 :         return false;
      71              :     }
      72              : 
      73            0 :     const QByteArray ack = socket.readAll();
      74            0 :     if (ack != ipc::ACK) {
      75            0 :         LOGW() << "unexpected ack payload: " << ack;
      76            0 :         return false;
      77              :     }
      78              : 
      79            0 :     socket.disconnectFromServer();
      80            0 :     LOGI() << "handoff acked by existing " << applicationId;
      81            0 :     return true;
      82            0 : }
      83              : 
      84              : namespace muse::mi {
      85            0 : SingleInstanceListener::SingleInstanceListener() = default;
      86              : 
      87            0 : SingleInstanceListener::~SingleInstanceListener()
      88              : {
      89            0 :     stop();
      90            0 : }
      91              : 
      92            0 : bool SingleInstanceListener::start(const QString& applicationId)
      93              : {
      94            0 :     if (m_server) {
      95            0 :         return true;
      96              :     }
      97            0 :     if (applicationId.isEmpty()) {
      98            0 :         LOGE() << "empty applicationId";
      99            0 :         return false;
     100              :     }
     101              : 
     102            0 :     m_server = new QLocalServer();
     103              : 
     104            0 :     bool ok = m_server->listen(applicationId);
     105              : 
     106            0 :     if (!ok && m_server->serverError() == QAbstractSocket::AddressInUseError) {
     107            0 :         LOGW() << "stale endpoint for " << applicationId << ", removing";
     108            0 :         QLocalServer::removeServer(applicationId);
     109            0 :         ok = m_server->listen(applicationId);
     110              :     }
     111              : 
     112            0 :     if (!ok) {
     113            0 :         LOGE() << "failed to listen on " << applicationId << ": " << m_server->errorString();
     114            0 :         delete m_server;
     115            0 :         m_server = nullptr;
     116            0 :         return false;
     117              :     }
     118              : 
     119            0 :     QObject::connect(m_server, &QLocalServer::newConnection, m_server, [this]() {
     120            0 :         onNewConnection();
     121            0 :     });
     122              : 
     123            0 :     LOGI() << "single-instance listener bound: " << applicationId;
     124            0 :     return true;
     125              : }
     126              : 
     127            0 : void SingleInstanceListener::stop()
     128              : {
     129            0 :     if (!m_server) {
     130            0 :         return;
     131              :     }
     132            0 :     m_server->close();
     133            0 :     delete m_server;
     134            0 :     m_server = nullptr;
     135              : }
     136              : 
     137            0 : bool SingleInstanceListener::isListening() const
     138              : {
     139            0 :     return m_server && m_server->isListening();
     140              : }
     141              : 
     142            0 : async::Channel<QStringList> SingleInstanceListener::messageReceived() const
     143              : {
     144            0 :     return m_messageReceived;
     145              : }
     146              : 
     147            0 : void SingleInstanceListener::onNewConnection()
     148              : {
     149            0 :     QLocalSocket* socket = m_server->nextPendingConnection();
     150            0 :     if (!socket) {
     151            0 :         return;
     152              :     }
     153              : 
     154            0 :     QObject::connect(socket, &QLocalSocket::disconnected, socket, &QObject::deleteLater);
     155              : 
     156            0 :     auto channel = m_messageReceived;
     157              : 
     158            0 :     auto handleData = [socket, channel]() mutable {
     159            0 :         ipc::readFromSocket(socket, [socket, channel](const QByteArray& data) mutable {
     160            0 :             ipc::Msg msg;
     161            0 :             ipc::deserialize(data, msg);
     162              : 
     163            0 :             if (msg.method != SINGLE_INSTANCE_ACTIVATE) {
     164            0 :                 LOGW() << "ignoring unexpected method: " << msg.method;
     165            0 :                 return;
     166              :             }
     167              : 
     168            0 :             socket->write(ipc::ACK);
     169            0 :             socket->flush();
     170              : 
     171            0 :             LOGI() << "received activation from second instance";
     172            0 :             LOGD() << "args: " << msg.args;
     173            0 :             channel.send(msg.args);
     174            0 :         });
     175            0 :     };
     176              : 
     177            0 :     QObject::connect(socket, &QLocalSocket::readyRead, socket, handleData);
     178              : 
     179            0 :     if (socket->bytesAvailable() > 0) {
     180            0 :         handleData();
     181              :     }
     182            0 : }
     183              : }
        

Generated by: LCOV version 2.0-1