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

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       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              : #include "multiprocessprovider.h"
      23              : 
      24              : #include <QProcess>
      25              : #include <QCoreApplication>
      26              : #include <QTimer>
      27              : #include <QEventLoop>
      28              : 
      29              : #include "types/uri.h"
      30              : #include "settings.h"
      31              : 
      32              : #include "rcommand/commandtypes.h"
      33              : #include "multiwindowscommands.h"
      34              : 
      35              : #include "log.h"
      36              : 
      37              : using namespace muse;
      38              : using namespace muse::mi;
      39              : using namespace muse::ipc;
      40              : using namespace muse::rcommand;
      41              : 
      42              : static const QString METHOD_PROJECT_IS_OPENED("PROJECT_IS_OPENED");
      43              : static const QString METHOD_ACTIVATE_WINDOW_WITH_PROJECT("ACTIVATE_WINDOW_WITH_PROJECT");
      44              : static const QString METHOD_IS_WITHOUT_PROJECT("IS_WITHOUT_PROJECT");
      45              : static const QString METHOD_ACTIVATE_WINDOW_WITHOUT_PROJECT("METHOD_ACTIVATE_WINDOW_WITHOUT_PROJECT");
      46              : 
      47              : static const muse::Uri PREFERENCES_URI("muse://preferences");
      48              : static const QString METHOD_PREFERENCES_IS_OPENED("PREFERENCES_IS_OPENED");
      49              : static const QString METHOD_ACTIVATE_WINDOW_WITH_OPENED_PREFERENCES("ACTIVATE_WINDOW_WITH_OPENED_PREFERENCES");
      50              : static const QString METHOD_SETTINGS_BEGIN_TRANSACTION("SETTINGS_BEGIN_TRANSACTION");
      51              : static const QString METHOD_SETTINGS_COMMIT_TRANSACTION("SETTINGS_COMMIT_TRANSACTION");
      52              : static const QString METHOD_SETTINGS_ROLLBACK_TRANSACTION("SETTINGS_ROLLBACK_TRANSACTION");
      53              : static const QString METHOD_SETTINGS_RESET("SETTINGS_RESET");
      54              : static const QString METHOD_SETTINGS_SET_VALUE("SETTINGS_SET_VALUE");
      55              : static const QString METHOD_QUIT("METHOD_QUIT");
      56              : static const QString METHOD_QUIT_WITH_RESTART_LAST_INSTANCE("METHOD_QUIT_WITH_RESTART_LAST_INSTANCE");
      57              : static const QString METHOD_QUIT_WITH_RUNING_INSTALLATION("METHOD_QUIT_WITH_RUNING_INSTALLATION");
      58              : static const QString METHOD_INSTANCE_CLOSED("INSTANCE_CLOSED");
      59              : static const QString METHOD_RESOURCE_CHANGED("RESOURCE_CHANGED");
      60              : 
      61            0 : MultiProcessProvider::~MultiProcessProvider()
      62              : {
      63            0 :     delete m_ipcChannel;
      64            0 : }
      65              : 
      66            0 : void MultiProcessProvider::initOnGlobal()
      67              : {
      68            0 :     m_ipcChannel = new IpcChannel();
      69            0 :     m_selfID = m_ipcChannel->selfID().toStdString();
      70            0 :     LOGI() << "our instance id is: " << m_selfID;
      71              : 
      72            0 :     m_ipcChannel->msgReceived().onReceive(this, [this](const Msg& msg) { onMsg(msg); });
      73            0 :     m_ipcChannel->instancesChanged().onNotify(this, [this]() { m_instancesChanged.notify(); });
      74              : 
      75            0 :     m_ipcChannel->connect();
      76            0 : }
      77              : 
      78            0 : bool MultiProcessProvider::isInited() const
      79              : {
      80            0 :     return m_ipcChannel != nullptr;
      81              : }
      82              : 
      83            0 : void MultiProcessProvider::onMsg(const Msg& msg)
      84              : {
      85            0 :     LOGI() << msg.method << ", me: " << m_selfID << ", msg src: " << msg.srcID << ", msg dest: " << msg.destID;
      86              : 
      87              : #define CHECK_ARGS_COUNT(c) IF_ASSERT_FAILED(msg.args.count() >= c) { return; }
      88              : 
      89              :     // Project opening
      90            0 :     if (msg.type == MsgType::Request && msg.method == METHOD_PROJECT_IS_OPENED) {
      91            0 :         CHECK_ARGS_COUNT(1);
      92            0 :         io::path_t scorePath = io::path_t(msg.args.at(0));
      93            0 :         bool isOpened = false;
      94            0 :         if (projectProvider()) {
      95            0 :             isOpened = projectProvider()->isProjectOpened(scorePath);
      96              :         } else {
      97            0 :             LOGW() << "Unable perform request, not found implementation of IProjectProvider";
      98              :         }
      99            0 :         m_ipcChannel->response(METHOD_PROJECT_IS_OPENED, { QString::number(isOpened) }, msg.srcID);
     100            0 :     } else if (msg.method == METHOD_ACTIVATE_WINDOW_WITH_PROJECT) {
     101            0 :         CHECK_ARGS_COUNT(1);
     102            0 :         io::path_t scorePath = io::path_t(msg.args.at(0));
     103            0 :         bool isOpened = false;
     104            0 :         if (projectProvider()) {
     105            0 :             isOpened = projectProvider()->isProjectOpened(scorePath);
     106              :         } else {
     107            0 :             LOGW() << "Unable perform request, not found implementation of IProjectProvider";
     108              :         }
     109            0 :         if (isOpened) {
     110            0 :             mainWindow()->requestShowOnFront();
     111              :         }
     112            0 :     } else if (msg.type == MsgType::Request && msg.method == METHOD_IS_WITHOUT_PROJECT) {
     113            0 :         bool isAnyOpened = false;
     114            0 :         if (projectProvider()) {
     115            0 :             isAnyOpened = projectProvider()->isAnyProjectOpened();
     116              :         } else {
     117            0 :             LOGW() << "Unable perform request, not found implementation of IProjectProvider";
     118              :         }
     119            0 :         m_ipcChannel->response(METHOD_IS_WITHOUT_PROJECT, { QString::number(!isAnyOpened) }, msg.srcID);
     120            0 :     } else if (msg.method == METHOD_ACTIVATE_WINDOW_WITHOUT_PROJECT) {
     121            0 :         bool isAnyOpened = false;
     122            0 :         if (projectProvider()) {
     123            0 :             isAnyOpened = projectProvider()->isAnyProjectOpened();
     124              :         } else {
     125            0 :             LOGW() << "Unable perform request, not found implementation of IProjectProvider";
     126              :         }
     127            0 :         if (!isAnyOpened) {
     128            0 :             mainWindow()->requestShowOnFront();
     129            0 :             if (msg.args.count() > 0 && !msg.args.at(0).isEmpty()) {
     130            0 :                 commandDispatcher()->dispatch(CommandQuery(msg.args.at(0).toStdString()));
     131              :             }
     132              :         }
     133            0 :         m_ipcChannel->response(METHOD_ACTIVATE_WINDOW_WITH_PROJECT, { QString::number(!isAnyOpened) }, msg.srcID);
     134              :     }
     135              :     // Settings
     136            0 :     else if (msg.type == MsgType::Request && msg.method == METHOD_PREFERENCES_IS_OPENED) {
     137            0 :         bool isOpened = interactive()->isOpened(PREFERENCES_URI).val;
     138            0 :         m_ipcChannel->response(METHOD_PREFERENCES_IS_OPENED, { QString::number(isOpened) }, msg.srcID);
     139            0 :     } else if (msg.method == METHOD_ACTIVATE_WINDOW_WITH_OPENED_PREFERENCES) {
     140            0 :         bool isOpened = interactive()->isOpened(PREFERENCES_URI).val;
     141            0 :         if (isOpened) {
     142            0 :             mainWindow()->requestShowOnFront();
     143              :         }
     144            0 :     } else if (msg.method == METHOD_SETTINGS_BEGIN_TRANSACTION) {
     145            0 :         settings()->beginTransaction(false);
     146            0 :     } else if (msg.method == METHOD_SETTINGS_COMMIT_TRANSACTION) {
     147            0 :         settings()->commitTransaction(false);
     148            0 :     } else if (msg.method == METHOD_SETTINGS_ROLLBACK_TRANSACTION) {
     149            0 :         settings()->rollbackTransaction(false);
     150            0 :     } else if (msg.method == METHOD_SETTINGS_RESET) {
     151            0 :         settings()->reset(true, true, false);
     152            0 :     } else if (msg.method == METHOD_SETTINGS_SET_VALUE) {
     153            0 :         CHECK_ARGS_COUNT(3);
     154            0 :         Settings::Key key("", msg.args.at(0).toStdString());
     155            0 :         Val val(msg.args.at(1).toStdString());
     156            0 :         val.setType(static_cast<Val::Type>(msg.args.at(2).toInt()));
     157            0 :         settings()->setLocalValue(key, val);
     158            0 :     } else if (msg.method == METHOD_QUIT) {
     159            0 :         commandDispatcher()->dispatch(APP_QUIT_COMMAND, { { "all_instances", Val(false) } });
     160            0 :     } else if (msg.method == METHOD_QUIT_WITH_RESTART_LAST_INSTANCE) {
     161            0 :         commandDispatcher()->dispatch(APP_RESTART_COMMAND);
     162            0 :     } else if (msg.method == METHOD_QUIT_WITH_RUNING_INSTALLATION) {
     163            0 :         CHECK_ARGS_COUNT(1);
     164            0 :         commandDispatcher()->dispatch(APP_QUIT_COMMAND, {
     165            0 :             { "all_instances", Val(false) },
     166            0 :             { "installer_path", Val(msg.args.at(0).toStdString()) } });
     167            0 :     } else if (msg.method == METHOD_INSTANCE_CLOSED && msg.type == ipc::MsgType::Request) {
     168            0 :         m_ipcChannel->response(METHOD_INSTANCE_CLOSED, { }, msg.srcID);
     169            0 :     } else if (msg.method == METHOD_RESOURCE_CHANGED) {
     170            0 :         resourceChanged().send(msg.args.at(0).toStdString());
     171              :     }
     172            0 : }
     173              : 
     174            0 : bool MultiProcessProvider::isProjectAlreadyOpened(const io::path_t& projectPath) const
     175              : {
     176            0 :     if (!isInited()) {
     177            0 :         return false;
     178              :     }
     179              : 
     180            0 :     int ret = 0;
     181            0 :     m_ipcChannel->syncRequestToAll(METHOD_PROJECT_IS_OPENED, { projectPath.toQString() }, [&ret](const QStringList& args, const ID&) {
     182            0 :         IF_ASSERT_FAILED(!args.empty()) {
     183            0 :             return false;
     184              :         }
     185            0 :         ret = args.at(0).toInt();
     186            0 :         if (ret) {
     187            0 :             return true;
     188              :         }
     189              : 
     190            0 :         return false;
     191              :     });
     192            0 :     return ret;
     193            0 : }
     194              : 
     195            0 : void MultiProcessProvider::activateWindowWithProject(const io::path_t& projectPath)
     196              : {
     197            0 :     if (!isInited()) {
     198            0 :         return;
     199              :     }
     200              : 
     201              : #ifndef Q_OS_MAC
     202              :     // On macOS, this is not desirable, since raising the main window of the other instance works properly without this
     203            0 :     mainWindow()->requestShowOnBack();
     204              : #endif
     205              : 
     206            0 :     m_ipcChannel->broadcast(METHOD_ACTIVATE_WINDOW_WITH_PROJECT, { projectPath.toQString() });
     207            0 : }
     208              : 
     209            0 : bool MultiProcessProvider::isHasWindowWithoutProject() const
     210              : {
     211            0 :     if (!isInited()) {
     212            0 :         return false;
     213              :     }
     214              : 
     215            0 :     bool ret = false;
     216            0 :     m_ipcChannel->syncRequestToAll(METHOD_IS_WITHOUT_PROJECT, {}, [&ret](const QStringList& args, const ID&) {
     217            0 :         IF_ASSERT_FAILED(!args.empty()) {
     218            0 :             return false;
     219              :         }
     220            0 :         if (args.at(0).toInt()) {
     221            0 :             ret = true;
     222            0 :             return true;
     223              :         }
     224            0 :         return false;
     225              :     });
     226            0 :     return ret;
     227              : }
     228              : 
     229            0 : void MultiProcessProvider::activateWindowWithoutProject(const QStringList& args)
     230              : {
     231            0 :     if (!isInited()) {
     232            0 :         return;
     233              :     }
     234              : 
     235              : #ifndef Q_OS_MAC
     236              :     // On macOS, this is not desirable, since raising the main window of the other instance works properly without this
     237            0 :     mainWindow()->requestShowOnBack();
     238              : #endif
     239              : 
     240            0 :     ID idWithNoProject;
     241            0 :     m_ipcChannel->syncRequestToAll(METHOD_IS_WITHOUT_PROJECT, {}, [&idWithNoProject](const QStringList& retArgs, const ID& srcId) {
     242            0 :         IF_ASSERT_FAILED(!retArgs.empty()) {
     243            0 :             return false;
     244              :         }
     245            0 :         if (retArgs.at(0).toInt()) {
     246            0 :             idWithNoProject = srcId;
     247            0 :             return true;
     248              :         }
     249            0 :         return false;
     250              :     });
     251            0 :     if (!idWithNoProject.isEmpty()) {
     252            0 :         m_ipcChannel->response(METHOD_ACTIVATE_WINDOW_WITHOUT_PROJECT, args, idWithNoProject);
     253            0 :         return;
     254              :     }
     255            0 : }
     256              : 
     257            0 : bool MultiProcessProvider::openNewWindow(const QStringList& args)
     258              : {
     259            0 :     if (!isInited()) {
     260            0 :         return false;
     261              :     }
     262              : 
     263            0 :     QList<ipc::ID> currentApps = m_ipcChannel->instances();
     264              : 
     265            0 :     QString appPath = QCoreApplication::applicationFilePath();
     266            0 :     bool ok = QProcess::startDetached(appPath, args);
     267            0 :     if (ok) {
     268            0 :         LOGI() << "success start: " << appPath << ", args: " << args;
     269              :     } else {
     270            0 :         LOGE() << "failed start: " << appPath << ", args: " << args;
     271            0 :         return ok;
     272              :     }
     273              : 
     274            0 :     auto sleep = [](int msec) {
     275            0 :         QTimer timer;
     276            0 :         QEventLoop loop;
     277            0 :         QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
     278            0 :         timer.start(msec);
     279            0 :         loop.exec();
     280            0 :     };
     281              : 
     282            0 :     auto waitNewInstance = [this, sleep, currentApps](int waitMs, int count) {
     283            0 :         for (int i = 0; i < count; ++i) {
     284            0 :             sleep(waitMs);
     285            0 :             QList<ipc::ID> apps = m_ipcChannel->instances();
     286            0 :             for (const ipc::ID& id : apps) {
     287            0 :                 if (!currentApps.contains(id)) {
     288            0 :                     LOGI() << "created new instance with ID: " << id;
     289            0 :                     return true;
     290              :                 }
     291              :             }
     292            0 :         }
     293            0 :         return false;
     294            0 :     };
     295              : 
     296              :     //! NOTE Waiting for a new instance to be created
     297            0 :     ok = waitNewInstance(100, 50);
     298            0 :     if (!ok) {
     299            0 :         LOGE() << "we didn't wait for registration and response from the new instance";
     300              :     }
     301              : 
     302            0 :     return ok;
     303            0 : }
     304              : 
     305            0 : bool MultiProcessProvider::isPreferencesAlreadyOpened() const
     306              : {
     307            0 :     if (!isInited()) {
     308            0 :         return false;
     309              :     }
     310              : 
     311            0 :     int ret = 0;
     312            0 :     m_ipcChannel->syncRequestToAll(METHOD_PREFERENCES_IS_OPENED, {}, [&ret](const QStringList& args, const ID&) {
     313            0 :         IF_ASSERT_FAILED(!args.empty()) {
     314            0 :             return false;
     315              :         }
     316            0 :         ret = args.at(0).toInt();
     317            0 :         if (ret) {
     318            0 :             return true;
     319              :         }
     320              : 
     321            0 :         return false;
     322              :     });
     323            0 :     return ret;
     324              : }
     325              : 
     326            0 : void MultiProcessProvider::activateWindowWithOpenedPreferences() const
     327              : {
     328            0 :     if (!isInited()) {
     329            0 :         return;
     330              :     }
     331              : 
     332              : #ifndef Q_OS_MAC
     333              :     // On macOS, this is not desirable, since raising the main window of the other instance works properly without this
     334            0 :     mainWindow()->requestShowOnBack();
     335              : #endif
     336              : 
     337            0 :     m_ipcChannel->broadcast(METHOD_ACTIVATE_WINDOW_WITH_OPENED_PREFERENCES);
     338              : }
     339              : 
     340            0 : void MultiProcessProvider::settingsBeginTransaction()
     341              : {
     342            0 :     if (!isInited()) {
     343            0 :         return;
     344              :     }
     345              : 
     346            0 :     m_ipcChannel->broadcast(METHOD_SETTINGS_BEGIN_TRANSACTION);
     347              : }
     348              : 
     349            0 : void MultiProcessProvider::settingsCommitTransaction()
     350              : {
     351            0 :     if (!isInited()) {
     352            0 :         return;
     353              :     }
     354              : 
     355            0 :     m_ipcChannel->broadcast(METHOD_SETTINGS_COMMIT_TRANSACTION);
     356              : }
     357              : 
     358            0 : void MultiProcessProvider::settingsRollbackTransaction()
     359              : {
     360            0 :     if (!isInited()) {
     361            0 :         return;
     362              :     }
     363              : 
     364            0 :     m_ipcChannel->broadcast(METHOD_SETTINGS_ROLLBACK_TRANSACTION);
     365              : }
     366              : 
     367            0 : void MultiProcessProvider::settingsReset()
     368              : {
     369            0 :     if (!isInited()) {
     370            0 :         return;
     371              :     }
     372              : 
     373            0 :     m_ipcChannel->broadcast(METHOD_SETTINGS_RESET);
     374              : }
     375              : 
     376            0 : void MultiProcessProvider::settingsSetValue(const std::string& key, const Val& value)
     377              : {
     378            0 :     if (!isInited()) {
     379            0 :         return;
     380              :     }
     381              : 
     382            0 :     QStringList args;
     383            0 :     args << QString::fromStdString(key);
     384            0 :     args << value.toQString();
     385            0 :     args << QString::number(static_cast<int>(value.type()));
     386            0 :     m_ipcChannel->broadcast(METHOD_SETTINGS_SET_VALUE, args);
     387            0 : }
     388              : 
     389            0 : muse::ipc::IpcLock* MultiProcessProvider::lock(const std::string& name)
     390              : {
     391            0 :     auto it = m_locks.find(name);
     392            0 :     if (it != m_locks.end()) {
     393            0 :         return it->second;
     394              :     }
     395            0 :     ipc::IpcLock* l = new ipc::IpcLock(QString::fromStdString(name));
     396            0 :     m_locks[name] = l;
     397            0 :     return l;
     398              : }
     399              : 
     400            0 : bool MultiProcessProvider::lockResource(const std::string& name)
     401              : {
     402            0 :     return lock(name)->lock();
     403              : }
     404              : 
     405            0 : bool MultiProcessProvider::unlockResource(const std::string& name)
     406              : {
     407            0 :     return lock(name)->unlock();
     408              : }
     409              : 
     410            0 : void MultiProcessProvider::notifyAboutResourceChanged(const std::string& name)
     411              : {
     412            0 :     if (!isInited()) {
     413            0 :         return;
     414              :     }
     415              : 
     416            0 :     QStringList args;
     417            0 :     args << QString::fromStdString(name);
     418            0 :     m_ipcChannel->broadcast(METHOD_RESOURCE_CHANGED, args);
     419            0 : }
     420              : 
     421            0 : async::Channel<std::string> MultiProcessProvider::resourceChanged()
     422              : {
     423            0 :     return m_resourceChanged;
     424              : }
     425              : 
     426            0 : size_t MultiProcessProvider::windowCount() const
     427              : {
     428            0 :     return m_ipcChannel ? m_ipcChannel->instances().size() : 1;
     429              : }
     430              : 
     431            0 : bool MultiProcessProvider::isFirstWindow() const
     432              : {
     433            0 :     return windowCount() <= 1;
     434              : }
     435              : 
     436            0 : const std::string& MultiProcessProvider::selfID() const
     437              : {
     438            0 :     return m_selfID;
     439              : }
     440              : 
     441            0 : bool MultiProcessProvider::isMainInstance() const
     442              : {
     443            0 :     return m_ipcChannel ? m_ipcChannel->isServer() : false;
     444              : }
     445              : 
     446            0 : std::vector<InstanceMeta> MultiProcessProvider::instances() const
     447              : {
     448            0 :     std::vector<InstanceMeta> ret;
     449            0 :     QList<ipc::ID> ints = m_ipcChannel->instances();
     450            0 :     for (const ipc::ID& id : std::as_const(ints)) {
     451            0 :         InstanceMeta im;
     452            0 :         im.id = id.toStdString();
     453            0 :         ret.push_back(std::move(im));
     454            0 :     }
     455              : 
     456            0 :     return ret;
     457            0 : }
     458              : 
     459            0 : async::Notification MultiProcessProvider::instancesChanged() const
     460              : {
     461            0 :     return m_instancesChanged;
     462              : }
     463              : 
     464            0 : void MultiProcessProvider::notifyAboutWindowWasQuited()
     465              : {
     466            0 :     if (!isInited()) {
     467            0 :         return;
     468              :     }
     469              : 
     470            0 :     m_ipcChannel->broadcast(METHOD_INSTANCE_CLOSED);
     471              : }
     472              : 
     473            0 : async::Promise<Ret> MultiProcessProvider::quitForAll(const modularity::ContextPtr& ctx)
     474              : {
     475            0 :     return async::make_promise<Ret>([this, ctx](auto resolve) {
     476            0 :         if (isInited()) {
     477            0 :             m_ipcChannel->broadcast(METHOD_QUIT);
     478              :         }
     479              : 
     480              :         // current
     481            0 :         quitWindow(ctx);
     482              : 
     483            0 :         return resolve(make_ok());
     484            0 :     });
     485              : }
     486              : 
     487            0 : void MultiProcessProvider::quitWindow(const modularity::ContextPtr&)
     488              : {
     489            0 :     QCoreApplication::exit();
     490            0 : }
     491              : 
     492            0 : void MultiProcessProvider::quitAllAndRestartLast()
     493              : {
     494            0 :     if (!isInited()) {
     495            0 :         return;
     496              :     }
     497              : 
     498            0 :     m_ipcChannel->broadcast(METHOD_QUIT_WITH_RESTART_LAST_INSTANCE);
     499              : }
     500              : 
     501            0 : void MultiProcessProvider::quitAllAndRunInstallation(const io::path_t& installerPath)
     502              : {
     503              :     //! NOTE: Path can be null in some test modes...
     504            0 :     if (!isInited() || installerPath.empty()) {
     505            0 :         return;
     506              :     }
     507              : 
     508            0 :     QStringList args;
     509            0 :     args << installerPath.toQString();
     510            0 :     m_ipcChannel->broadcast(METHOD_QUIT_WITH_RUNING_INSTALLATION, args);
     511            0 : }
        

Generated by: LCOV version 2.0-1