#include "RobloxApplicationManager.hpp" // Qt Headers #include #include #include #include #include // Roblox Headers #include "RobloxCookieJar.hpp" #include "RobloxMainWindow.hpp" #include "RobloxNetworkAccessManager.hpp" #include "UpdateUIManager.hpp" #include "QtUtilities.hpp" #include "StudioDeviceEmulator.hpp" #include "StudioUtilities.hpp" const char* kChildProcessServerName = "rbxDeleteChildProcessesServer_%1"; const char* kDeleteChildProcessesMsg = "rbxDeleteChildProcesses"; const char* kChildProcessesDeletedMsg = "rbxChildProcessesDeleted"; RobloxApplicationManager::RobloxApplicationManager() : childProcessHandler(NULL) { sharedMemory.setKey("QT_ROBLOX_STUDIO"); } RobloxApplicationManager& RobloxApplicationManager::instance() { static RobloxApplicationManager instance; return instance; } bool RobloxApplicationManager::createNewStudioInstance( const QString& script, const QString& fileLocation, bool isTestMode, bool isAvatarMode, NewInstanceMode mode) { // Save the current layout before launching UpdateUIManager::Instance().getMainWindow().saveApplicationStates(); // Persist our cookies (to make sure we have the most recent ones in the new app) RobloxCookieJar* cookieJar = dynamic_cast(RobloxNetworkAccessManager::Instance().cookieJar()); if (cookieJar) cookieJar->saveCookiesToDisk(); QStringList args; args << IDEArgument; composeArgList(args, script, fileLocation, isTestMode, isAvatarMode); QString robloxApp = QCoreApplication::applicationFilePath(); if (mode == NewInstanceMode_Detached) return QProcess::startDetached(robloxApp, args); if (!childProcessHandler) childProcessHandler = new ChildProcessHandler(&UpdateUIManager::Instance().getMainWindow()); return childProcessHandler->startChildProcess(robloxApp, args, mode); } int RobloxApplicationManager::getApplicationCount() { SharedData data; if (!sharedMemory.lock()) return 0; char* sharedData = (char*)sharedMemory.data(); if (!sharedData) // Odd scenario, should this ever happen? { sharedMemory.unlock(); return 0; } memcpy(&data, sharedData, sizeof(SharedData)); sharedMemory.unlock(); return data.numStudioApplications; } void RobloxApplicationManager::registerApplication() { sharedMemory.attach(); sharedMemory.create(sizeof(SharedData)); // Create it if it doesn't exist modifyApplicationCount(1); // Increment our count } void RobloxApplicationManager::unregisterApplication() { modifyApplicationCount(-1); // Decrement our count sharedMemory.detach(); } void RobloxApplicationManager::modifyApplicationCount(int delta) { if (!sharedMemory.lock()) return; SharedData data; char* sharedData = (char*)sharedMemory.data(); if (sharedData) { memcpy(&data, sharedData, sizeof(SharedData)); } else { // Should never be here? data.numStudioApplications = 0; } data.numStudioApplications += delta; memcpy(sharedData, &data, sizeof(SharedData)); sharedMemory.unlock(); } void RobloxApplicationManager::composeArgList( QStringList& argsContainer, const QString& script, const QString& fileLocation, bool isTestMode, bool isAvatarMode) { if (!script.isEmpty()) argsContainer << ScriptArgument << script; if (!fileLocation.isEmpty()) argsContainer << FileLocationArgument << fileLocation; if (isTestMode) argsContainer << TestModeArgument; if (isAvatarMode) argsContainer << AvatarModeArgument; if (StudioDeviceEmulator::Instance().isEmulatingTouch()) argsContainer << StudioUtilities::EmulateTouchArgument; int studioWidth = StudioDeviceEmulator::Instance().getCurrentDeviceWidth(); int studioHeight = StudioDeviceEmulator::Instance().getCurrentDeviceHeight(); if (studioWidth) argsContainer << StudioUtilities::StudioWidthArgument << QString("%1").arg(studioWidth); if (studioHeight) argsContainer << StudioUtilities::StudioHeightArgument << QString("%1").arg(studioHeight); } bool RobloxApplicationManager::hasChildProcesses() { if (childProcessHandler) return childProcessHandler->hasChildProcesses(); return false; } void RobloxApplicationManager::cleanupChildProcesses(int timeout) { if (childProcessHandler) childProcessHandler->cleanupChildProcesses(timeout); } void RobloxApplicationManager::startLocalServer() { if (!childProcessHandler) childProcessHandler = new ChildProcessHandler(&UpdateUIManager::Instance().getMainWindow()); childProcessHandler->startLocalServer(); } bool RobloxApplicationManager::hasLocalServer() { if (childProcessHandler) return childProcessHandler->hasLocalServer(); return false; } ChildProcessHandler::ChildProcessHandler(QObject* parent) : QObject(parent) , m_pLocalServer(NULL) { } bool ChildProcessHandler::startChildProcess(const QString& robloxApp, const QStringList& args, NewInstanceMode mode) { QProcess* newProcess = new QProcess(); newProcess->start(robloxApp, args); if (!newProcess->waitForStarted()) return false; if (mode == NewInstanceMode_Detached) return true; // child process can be managed if it's Server or Player (mode == NewInstanceMode_Server) ? serverProcesses.append(newProcess) : playerProcesses.append(newProcess); connect(newProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onProcessFinished())); return true; } void ChildProcessHandler::startLocalServer() { // if we already have a local server, do not do anything if (m_pLocalServer) return; m_pLocalServer = new QLocalServer(this); connect(m_pLocalServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); int processId = 0; #ifdef Q_WS_WIN32 processId = ::GetCurrentProcessId(); #else processId = getpid(); #endif // set name for the local server (attach process id to make it unique) m_pLocalServer->listen(QString(kChildProcessServerName).arg(processId)); } bool ChildProcessHandler::hasLocalServer() { return m_pLocalServer != NULL; } void ChildProcessHandler::cleanupChildProcesses(int timeout) { // first cleanup players launched from this instance of studio QProcess* childProcess = NULL; for (int ii = 0; ii < playerProcesses.size(); ++ii) { childProcess = playerProcesses.at(ii); if (childProcess && childProcess->state() != QProcess::NotRunning) { childProcess->kill(); } } playerProcesses.clear(); // now time to remove server processes for (int ii = 0; ii < serverProcesses.size(); ++ii) { childProcess = serverProcesses.at(ii); if (childProcess && childProcess->state() != QProcess::NotRunning) { int processId = QtUtilities::toInt(childProcess->processId()); if (!processId) continue; // since server launches player (in Ribbon mode), we need to make sure those launched players also gets cleaned up QLocalSocket localSocket; localSocket.connectToServer(QString(kChildProcessServerName).arg(processId)); if (localSocket.waitForConnected(timeout)) { QString message(kDeleteChildProcessesMsg); localSocket.write(message.toUtf8()); localSocket.waitForBytesWritten(timeout); localSocket.waitForReadyRead(timeout); } // now we are done with players cleanup, all set to kill the server if (childProcess && childProcess->state() != QProcess::NotRunning) childProcess->kill(); } } serverProcesses.clear(); } bool ChildProcessHandler::hasChildProcesses() { return !serverProcesses.isEmpty() || !playerProcesses.isEmpty(); } void ChildProcessHandler::onProcessFinished() { QProcess* pProcess = qobject_cast(sender()); if (!pProcess) return; // check if we can remove process from list bool removed = serverProcesses.removeOne(pProcess); if (!removed) removed = playerProcesses.removeOne(pProcess); // if we do not have any child processes pending update toolbars if (removed && serverProcesses.isEmpty() && playerProcesses.isEmpty()) UpdateUIManager::Instance().updateToolBars(); } void ChildProcessHandler::onNewConnection() { if (!m_pLocalServer) return; QLocalSocket* localSocket = m_pLocalServer->nextPendingConnection(); if (localSocket && localSocket->waitForReadyRead(3000)) { QString receivedMessage = QString::fromUtf8(localSocket->readAll().constData()); if (!receivedMessage.isEmpty() && (receivedMessage == kDeleteChildProcessesMsg)) { // kill all child process cleanupChildProcesses(); // update socket about the completion localSocket->write(kChildProcessesDeletedMsg); } } }