diff --git a/CMakeLists.txt b/CMakeLists.txt index 272250ad..dceb4615 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,7 @@ IF(DEFINED MultiMC_BUILD_TAG) MESSAGE(STATUS "Build tag: ${MultiMC_BUILD_TAG}") ELSE() MESSAGE(STATUS "No build tag specified.") + SET(MultiMC_BUILD_TAG custom) ENDIF() # Architecture detection @@ -168,6 +169,7 @@ gui/aboutdialog.h data/version.h data/userinfo.h data/loginresponse.h +data/minecraftprocess.h data/plugin/pluginmanager.h @@ -195,6 +197,7 @@ data/userinfo.cpp data/loginresponse.cpp data/plugin/pluginmanager.cpp +data/minecraftprocess.cpp gui/mainwindow.cpp gui/modeditwindow.cpp diff --git a/data/minecraftprocess.cpp b/data/minecraftprocess.cpp new file mode 100644 index 00000000..4cbe7208 --- /dev/null +++ b/data/minecraftprocess.cpp @@ -0,0 +1,214 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "minecraftprocess.h" + +#include +#include +#include +#include +#include + +#include "instance.h" + +#include "osutils.h" +#include "pathutils.h" + +#define LAUNCHER_FILE "MultiMCLauncher.jar" +#define IBUS "@im=ibus" + +// commandline splitter +QStringList MinecraftProcess::splitArgs(QString args) +{ + QStringList argv; + QString current; + bool escape = false; + QChar inquotes; + for (int i=0; iiconKey()).save(destination); +} + +inline void MinecraftProcess::extractLauncher(QString destination) +{ + QFile(":/launcher/launcher.jar").copy(destination); +} + +void MinecraftProcess::prepare(InstancePtr inst) +{ + extractLauncher(PathCombine(inst->minecraftDir(), LAUNCHER_FILE)); + extractIcon(inst, PathCombine(inst->minecraftDir(), "icon.png")); +} + +// constructor +MinecraftProcess::MinecraftProcess(InstancePtr inst, QString user, QString session, ConsoleWindow *console) : + m_instance(inst), m_user(user), m_session(session), m_console(console) +{ + connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finish(int, QProcess::ExitStatus))); + + // prepare the process environment + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + +#ifdef LINUX + // Strip IBus + if (env.value("XMODIFIERS").contains(IBUS)) + env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, "")); +#endif + + // export some infos + env.insert("INST_NAME", inst->name()); + env.insert("INST_ID", inst->id()); + env.insert("INST_DIR", QDir(inst->rootDir()).absolutePath()); + + this->setProcessEnvironment(env); + m_prepostlaunchprocess.setProcessEnvironment(env); + + // set the cwd + QDir mcDir(inst->minecraftDir()); + this->setWorkingDirectory(mcDir.absolutePath()); + m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); + + //TODO: do console redirection +} + +void MinecraftProcess::finish(int code, ExitStatus status) +{ + if (status != NormalExit) + { + //TODO: error handling + } + + m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); + + // run post-exit + if (!m_instance->getPostExitCommand().isEmpty()) + { + m_prepostlaunchprocess.start(m_instance->getPostExitCommand()); + m_prepostlaunchprocess.waitForFinished(); + if (m_prepostlaunchprocess.exitStatus() != NormalExit) + { + //TODO: error handling + } + } + + emit ended(); +} + +void MinecraftProcess::launch() +{ + if (!m_instance->getPreLaunchCommand().isEmpty()) + { + m_prepostlaunchprocess.start(m_instance->getPreLaunchCommand()); + m_prepostlaunchprocess.waitForFinished(); + if (m_prepostlaunchprocess.exitStatus() != NormalExit) + { + //TODO: error handling + return; + } + } + + m_instance->setLastLaunch(); + + prepare(m_instance); + + genArgs(); + + qDebug("Minecraft folder is: '%s'", qPrintable(workingDirectory())); + qDebug("Instance launched with arguments: '%s'", qPrintable(m_arguments.join("' '"))); + + start(m_instance->getJavaPath(), m_arguments); + if (!waitForStarted()) + { + //TODO: error handling + } +} + +void MinecraftProcess::genArgs() +{ + // start fresh + m_arguments.clear(); + + // window size + QString windowSize; + if (m_instance->getLaunchMaximized()) + windowSize = "max"; + else + windowSize = QString("%1x%2").arg(m_instance->getMinecraftWinWidth()).arg(m_instance->getMinecraftWinHeight()); + + // window title + QString windowTitle; + windowTitle.append("MultiMC: ").append(m_instance->name()); + + // Java arguments + m_arguments.append(splitArgs(m_instance->getJvmArgs())); + +#ifdef OSX + // OSX dock icon and name + m_arguments << "-Xdock:icon=icon.png"; + m_arguments << QString("-Xdock:name=\"%1\"").arg(windowTitle); +#endif + + // lwjgl + QString lwjgl = m_instance->lwjglVersion(); + if (lwjgl != "Mojang") + lwjgl = QDir(settings->getLWJGLDir() + "/" + lwjgl).absolutePath(); + + // launcher arguments + m_arguments << QString("-Xms%1m").arg(m_instance->getMinMemAlloc()); + m_arguments << QString("-Xmx%1m").arg(m_instance->getMaxMemAlloc()); + m_arguments << "-jar" << LAUNCHER_FILE; + m_arguments << m_user; + m_arguments << m_session; + m_arguments << windowTitle; + m_arguments << windowSize; + m_arguments << lwjgl; +} diff --git a/data/minecraftprocess.h b/data/minecraftprocess.h new file mode 100644 index 00000000..99a3bed6 --- /dev/null +++ b/data/minecraftprocess.h @@ -0,0 +1,94 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MINECRAFTPROCESS_H +#define MINECRAFTPROCESS_H + +#include + +class ConsoleWindow; + +#include "instance.h" + +/** + * @file data/minecraftprocess.h + * @brief The MinecraftProcess class + */ +class MinecraftProcess : public QProcess +{ + Q_OBJECT +public: + /** + * @brief MinecraftProcess constructor + * @param inst the Instance pointer to launch + * @param user the minecraft username + * @param session the minecraft session id + * @param console the instance console window + */ + MinecraftProcess(InstancePtr inst, QString user, QString session, ConsoleWindow *console); + + /** + * @brief launch minecraft + */ + void launch(); + + /** + * @brief extract the instance icon + * @param inst the instance + * @param destination the destination path + */ + static inline void extractIcon(InstancePtr inst, QString destination); + + /** + * @brief extract the MultiMC launcher.jar + * @param destination the destination path + */ + static inline void extractLauncher(QString destination); + + /** + * @brief prepare the launch by extracting icon and launcher + * @param inst the instance + */ + static void prepare(InstancePtr inst); + + /** + * @brief split a string into argv items like a shell would do + * @param args the argument string + * @return a QStringList containing all arguments + */ + static QStringList splitArgs(QString args); + +signals: + /** + * @brief emitted when mc has finished and the PostLaunchCommand was run + */ + void ended(); + +protected: + ConsoleWindow *m_console; + InstancePtr m_instance; + QString m_user; + QString m_session; + QProcess m_prepostlaunchprocess; + QStringList m_arguments; + + void genArgs(); + +protected slots: + void finish(int, QProcess::ExitStatus status); +}; + +#endif // MINECRAFTPROCESS_H diff --git a/libinstance/include/instance.h b/libinstance/include/instance.h index 7b3c001d..995b88d8 100644 --- a/libinstance/include/instance.h +++ b/libinstance/include/instance.h @@ -339,4 +339,7 @@ private: QString m_rootDir; }; +// pointer for lazy people +typedef QSharedPointer InstancePtr; + #endif // INSTANCE_H diff --git a/libinstance/src/instance.cpp b/libinstance/src/instance.cpp index c79c0213..c10286b6 100644 --- a/libinstance/src/instance.cpp +++ b/libinstance/src/instance.cpp @@ -50,13 +50,9 @@ QString Instance::minecraftDir() const QFileInfo dotMCDir(PathCombine(rootDir(), ".minecraft")); if (dotMCDir.exists() && !mcDir.exists()) - { - return dotMCDir.path(); - } + return dotMCDir.filePath(); else - { - return mcDir.path(); - } + return mcDir.filePath(); } QString Instance::binDir() const diff --git a/libutil/include/siglist.h b/libutil/include/siglist.h index dcae7c04..24b1a889 100644 --- a/libutil/include/siglist.h +++ b/libutil/include/siglist.h @@ -71,7 +71,7 @@ public: virtual QList &operator =(const QList &other); - +protected: // Signal emitted after an item is added to the list. // Contains a reference to item and the item's new index. virtual void onItemAdded(const T &item, int index) = 0; diff --git a/main.cpp b/main.cpp index 4a4d8e7e..4f072b38 100644 --- a/main.cpp +++ b/main.cpp @@ -22,9 +22,14 @@ #include #include "gui/mainwindow.h" +#include "gui/logindialog.h" +#include "gui/taskdialog.h" +#include "instancelist.h" #include "appsettings.h" #include "data/loginresponse.h" +#include "tasks/logintask.h" +#include "data/minecraftprocess.h" #include "data/plugin/pluginmanager.h" @@ -35,6 +40,98 @@ using namespace Util::Commandline; +// Commandline instance launcher +class InstanceLauncher : public QObject +{ + Q_OBJECT +private: + InstanceList instances; + QString instId; + InstancePtr instance; + MinecraftProcess *proc; +public: + InstanceLauncher(QString instId) : QObject(), instances(settings->getInstanceDir()) + { + this->instId = instId; + } + +private: + InstancePtr findInstance(QString instId) + { + QListIterator iter(instances); + InstancePtr inst; + while(iter.hasNext()) + { + inst = iter.next(); + if (inst->id() == instId) + break; + } + if (inst->id() != instId) + return InstancePtr(); + else + return iter.peekPrevious(); + } + +private slots: + void onTerminated() + { + std::cout << "Minecraft exited" << std::endl; + QApplication::instance()->quit(); + } + + void onLoginComplete(LoginResponse response) + { + // TODO: console + proc = new MinecraftProcess(instance, response.getUsername(), response.getSessionID(), nullptr); + connect(proc, SIGNAL(ended()), SLOT(onTerminated())); + proc->launch(); + /*if (proc->pid() == 0) + { + std::cout << "Could not start instance." << std::endl; + QApplication::instance()->quit(); + return; + }*/ + } + + void doLogin(const QString &errorMsg) + { + LoginDialog* loginDlg = new LoginDialog(nullptr, errorMsg); + if (loginDlg->exec()) + { + UserInfo uInfo(loginDlg->getUsername(), loginDlg->getPassword()); + + TaskDialog* tDialog = new TaskDialog(nullptr); + LoginTask* loginTask = new LoginTask(uInfo, tDialog); + connect(loginTask, SIGNAL(loginComplete(LoginResponse)), + SLOT(onLoginComplete(LoginResponse)), Qt::QueuedConnection); + connect(loginTask, SIGNAL(loginFailed(QString)), + SLOT(doLogin(QString)), Qt::QueuedConnection); + tDialog->exec(loginTask); + } + //onLoginComplete(LoginResponse("Offline","Offline", 1)); + } + +public: + int launch() + { + std::cout << "Loading Instances..." << std::endl; + instances.loadList(); + + std::cout << "Launching Instance '" << qPrintable(instId) << "'" << std::endl; + instance = findInstance(instId); + if (instance.isNull()) + { + std::cout << "Could not find instance requested. note that you have to specify the ID, not the NAME" << std::endl; + return 1; + } + + std::cout << "Logging in..." << std::endl; + doLogin(""); + + return QApplication::instance()->exec(); + } +}; + int main(int argc, char *argv[]) { // initialize Qt @@ -44,7 +141,7 @@ int main(int argc, char *argv[]) // Print app header std::cout << "MultiMC 5" << std::endl; - std::cout << "(c) 2013 MultiMC contributors" << std::endl << std::endl; + std::cout << "(c) 2013 MultiMC Contributors" << std::endl << std::endl; // Commandline parsing Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); @@ -92,7 +189,9 @@ int main(int argc, char *argv[]) // display version and exit if (args["version"].toBool()) { - std::cout << VERSION_STR << " " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; + std::cout << "Version " << VERSION_STR << std::endl; + std::cout << "Git " << GIT_COMMIT << std::endl; + std::cout << "Tag: " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; return 0; } @@ -113,16 +212,6 @@ int main(int argc, char *argv[]) // change directory QDir::setCurrent(args["dir"].toString()); - // launch instance. - if (!args["launch"].isNull()) - { - std::cout << "Launching instance: " << qPrintable(args["launch"].toString()) << std::endl; - // TODO: make it launch the an instance. - // needs the new instance model to be complete - std::cerr << "Launching Instances is not implemented yet!" << std::endl; - return 255; - } - // load settings settings = new AppSettings(&app); @@ -133,6 +222,10 @@ int main(int argc, char *argv[]) PluginManager::get().loadPlugins(PathCombine(qApp->applicationDirPath(), "plugins")); PluginManager::get().initInstanceTypes(); + // launch instance. + if (!args["launch"].isNull()) + return InstanceLauncher(args["launch"].toString()).launch(); + // show main window MainWindow mainWin; mainWin.show(); @@ -140,3 +233,5 @@ int main(int argc, char *argv[]) // loop return app.exec(); } + +#include "main.moc" diff --git a/multimc.qrc b/multimc.qrc index 7008206b..cfcc9829 100644 --- a/multimc.qrc +++ b/multimc.qrc @@ -28,7 +28,7 @@ resources/icons/multimc.svg - resources/MultiMCLauncher.jar + resources/MultiMCLauncher.jar resources/icons/multimc.svg