summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--application/CMakeLists.txt2
-rw-r--r--application/InstanceWindow.cpp11
-rw-r--r--application/InstanceWindow.h7
-rw-r--r--application/LaunchController.cpp17
-rw-r--r--application/MainWindow.cpp88
-rw-r--r--application/MainWindow.h10
-rw-r--r--application/MultiMC.cpp159
-rw-r--r--application/MultiMC.h57
-rw-r--r--application/main.cpp37
-rw-r--r--libraries/LocalPeer/CMakeLists.txt29
-rw-r--r--libraries/LocalPeer/include/LocalPeer.h97
-rw-r--r--libraries/LocalPeer/src/LocalPeer.cpp236
-rw-r--r--libraries/LocalPeer/src/LockedFile.cpp193
-rw-r--r--libraries/LocalPeer/src/LockedFile.h77
-rw-r--r--libraries/LocalPeer/src/LockedFile_unix.cpp114
-rw-r--r--libraries/LocalPeer/src/LockedFile_win.cpp205
-rw-r--r--libraries/README.md9
18 files changed, 1164 insertions, 185 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e20d375a..25708418 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -98,6 +98,7 @@ add_subdirectory(libraries/xz-embedded) # xz compression
add_subdirectory(libraries/pack200) # java pack200 compression
add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
+add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
############################### Built Artifacts ###############################
diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt
index 59a51ce7..4a7d644d 100644
--- a/application/CMakeLists.txt
+++ b/application/CMakeLists.txt
@@ -322,7 +322,7 @@ qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
# Add executable
add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES} ${MULTIMC_RCS})
-target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown rainbow)
+target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown rainbow LocalPeer)
if(APPLE)
find_library(OSX_CORE_FOUNDATION CoreFoundation)
diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp
index 7b3e1d10..1c7731bd 100644
--- a/application/InstanceWindow.cpp
+++ b/application/InstanceWindow.cpp
@@ -174,11 +174,6 @@ void InstanceWindow::closeEvent(QCloseEvent *event)
MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64());
emit isClosing();
event->accept();
- if(m_shouldQuit)
- {
- // this needs to be delayed so we don't do horrible things
- QMetaObject::invokeMethod(MMC, "quit", Qt::QueuedConnection);
- }
}
bool InstanceWindow::saveAll()
@@ -203,11 +198,7 @@ void InstanceWindow::on_btnKillMinecraft_clicked()
// FIXME: duplicate logic between MainWindow and InstanceWindow
else if(saveAll())
{
- m_launchController.reset(new LaunchController());
- m_launchController->setInstance(m_instance);
- m_launchController->setOnline(true);
- m_launchController->setParentWidget(this);
- m_launchController->start();
+ MMC->launch(m_instance, true, nullptr);
}
}
diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h
index 1a6bc8c3..5cbced03 100644
--- a/application/InstanceWindow.h
+++ b/application/InstanceWindow.h
@@ -36,11 +36,6 @@ public:
QString instanceId();
- void setQuitOnClose(bool shouldQuit = false)
- {
- m_shouldQuit = shouldQuit;
- }
-
// save all settings and changes (prepare for launch)
bool saveAll();
@@ -68,9 +63,7 @@ private:
private:
std::shared_ptr<LaunchTask> m_proc;
- unique_qobject_ptr<LaunchController> m_launchController;
InstancePtr m_instance;
- bool m_shouldQuit = false;
bool m_doNotSave = false;
PageContainer *m_container = nullptr;
QPushButton *m_closeButton = nullptr;
diff --git a/application/LaunchController.cpp b/application/LaunchController.cpp
index bc7f976a..4dce6708 100644
--- a/application/LaunchController.cpp
+++ b/application/LaunchController.cpp
@@ -205,21 +205,10 @@ void LaunchController::launchInstance()
return;
}
- auto mainWindow = qobject_cast<MainWindow *>(m_parentWidget);
- auto instanceWindow = qobject_cast<InstanceWindow *>(m_parentWidget);
- if(mainWindow)
+ auto console = qobject_cast<InstanceWindow *>(m_parentWidget);
+ if(!console)
{
- m_console = mainWindow->showInstanceWindow(m_instance);
- }
- else if(instanceWindow)
- {
- // NOOP
- }
- else
- {
- // this is used when launching directly from command line
- m_console = new InstanceWindow(m_instance);
- m_console->setQuitOnClose(true);
+ MMC->showInstanceWindow(m_instance);
}
connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);
diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp
index e07686dd..85484700 100644
--- a/application/MainWindow.cpp
+++ b/application/MainWindow.cpp
@@ -691,7 +691,7 @@ void MainWindow::updateToolsMenu()
QAction *normalLaunch = launchMenu->addAction(tr("Launch"));
connect(normalLaunch, &QAction::triggered, [this]()
{
- launch(m_selectedInstance);
+ MMC->launch(m_selectedInstance);
});
launchMenu->addSeparator()->setText(tr("Profilers"));
for (auto profiler : MMC->profilers().values())
@@ -707,7 +707,7 @@ void MainWindow::updateToolsMenu()
{
connect(profilerAction, &QAction::triggered, [this, profiler]()
{
- launch(m_selectedInstance, true, profiler.get());
+ MMC->launch(m_selectedInstance, true, profiler.get());
});
}
}
@@ -953,7 +953,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)
{
qDebug() << "Downloading updates.";
ProgressDialog updateDlg(this);
- status.rootPath = MMC->rootPath;
+ status.rootPath = MMC->root();
auto dlPath = FS::PathCombine(MMC->root(), "update", "XXXXXX");
if (!FS::ensureFilePathExists(dlPath))
@@ -1004,8 +1004,8 @@ void MainWindow::waitForMinecraftVersions()
if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask && m_versionLoadTask->isRunning())
{
QEventLoop waitLoop;
- waitLoop.connect(m_versionLoadTask, SIGNAL(failed(QString)), SLOT(quit()));
- waitLoop.connect(m_versionLoadTask, SIGNAL(succeeded()), SLOT(quit()));
+ waitLoop.connect(m_versionLoadTask, &Task::failed, &waitLoop, &QEventLoop::quit);
+ waitLoop.connect(m_versionLoadTask, &Task::succeeded, &waitLoop, &QEventLoop::quit);
waitLoop.exec();
}
}
@@ -1261,62 +1261,24 @@ void MainWindow::on_actionSettings_triggered()
update();
}
-InstanceWindow *MainWindow::showInstanceWindow(InstancePtr instance, QString page)
-{
- if(!instance)
- return nullptr;
- auto id = instance->id();
- InstanceWindow * window = nullptr;
-
- auto iter = m_instanceWindows.find(id);
- if(iter != m_instanceWindows.end())
- {
- window = *iter;
- window->raise();
- window->activateWindow();
- }
- else
- {
- window = new InstanceWindow(instance);
- m_instanceWindows[id] = window;
- connect(window, &InstanceWindow::isClosing, this, &MainWindow::on_instanceWindowClose);
- }
- if(!page.isEmpty())
- {
- window->selectPage(page);
- }
- return window;
-}
-
-void MainWindow::on_instanceWindowClose()
-{
- auto senderWindow = qobject_cast<InstanceWindow *>(QObject::sender());
- if(!senderWindow)
- {
- return;
- }
- m_instanceWindows.remove(senderWindow->instanceId());
-}
-
-
void MainWindow::on_actionInstanceSettings_triggered()
{
- showInstanceWindow(m_selectedInstance, "settings");
+ MMC->showInstanceWindow(m_selectedInstance, "settings");
}
void MainWindow::on_actionEditInstNotes_triggered()
{
- showInstanceWindow(m_selectedInstance, "notes");
+ MMC->showInstanceWindow(m_selectedInstance, "notes");
}
void MainWindow::on_actionEditInstance_triggered()
{
- showInstanceWindow(m_selectedInstance);
+ MMC->showInstanceWindow(m_selectedInstance);
}
void MainWindow::on_actionScreenshots_triggered()
{
- showInstanceWindow(m_selectedInstance, "screenshots");
+ MMC->showInstanceWindow(m_selectedInstance, "screenshots");
}
void MainWindow::on_actionManageAccounts_triggered()
@@ -1440,14 +1402,14 @@ void MainWindow::instanceActivated(QModelIndex index)
if (!inst)
return;
- launch(inst);
+ MMC->launch(inst);
}
void MainWindow::on_actionLaunchInstance_triggered()
{
if (m_selectedInstance)
{
- launch(m_selectedInstance);
+ MMC->launch(m_selectedInstance);
}
}
@@ -1455,33 +1417,7 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered()
{
if (m_selectedInstance)
{
- launch(m_selectedInstance, false);
- }
-}
-
-void MainWindow::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler)
-{
- if(instance->canLaunch())
- {
- // FIXME: duplicate logic between MainWindow and InstanceWindow
- auto window = m_instanceWindows.find(instance->id());
- if(window != m_instanceWindows.end())
- {
- if(!(*window)->saveAll())
- {
- return;
- }
- }
- m_launchController.reset(new LaunchController());
- m_launchController->setInstance(instance);
- m_launchController->setOnline(online);
- m_launchController->setParentWidget(this);
- m_launchController->setProfiler(profiler);
- m_launchController->start();
- }
- else if (instance->isRunning())
- {
- showInstanceWindow(instance, "console");
+ MMC->launch(m_selectedInstance, false);
}
}
diff --git a/application/MainWindow.h b/application/MainWindow.h
index d0660676..e499f162 100644
--- a/application/MainWindow.h
+++ b/application/MainWindow.h
@@ -37,7 +37,6 @@ class MinecraftLauncher;
class BaseProfilerFactory;
class GroupView;
class ServerStatus;
-class InstanceWindow;
class MainWindow : public QMainWindow
{
@@ -55,8 +54,6 @@ public:
void checkSetDefaultJava();
void checkInstancePathForProblems();
- InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
-
private slots:
void onCatToggled(bool);
@@ -162,8 +159,6 @@ private slots:
*/
void downloadUpdates(GoUpdate::Status status);
- void on_instanceWindowClose();
-
private:
void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon);
@@ -174,7 +169,6 @@ private:
void instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version);
void instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url);
void finalizeInstance(InstancePtr inst);
- void launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
private:
std::unique_ptr<Ui> ui;
@@ -194,14 +188,10 @@ private:
unique_qobject_ptr<NetJob> skin_download_job;
unique_qobject_ptr<NewsChecker> m_newsChecker;
unique_qobject_ptr<NotificationChecker> m_notificationChecker;
- unique_qobject_ptr<LaunchController> m_launchController;
InstancePtr m_selectedInstance;
QString m_currentInstIcon;
// managed by the application object
Task *m_versionLoadTask;
-
- // map from instance ID to its window
- QMap<QString, InstanceWindow *> m_instanceWindows;
};
diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp
index 927468b6..41f5281c 100644
--- a/application/MultiMC.cpp
+++ b/application/MultiMC.cpp
@@ -1,5 +1,7 @@
#include "MultiMC.h"
#include "BuildConfig.h"
+#include "MainWindow.h"
+#include "InstanceWindow.h"
#include "pages/BasePageProvider.h"
#include "pages/global/MultiMCPage.h"
#include "pages/global/MinecraftPage.h"
@@ -59,6 +61,7 @@
#include <Commandline.h>
#include <FileSystem.h>
#include <DesktopServices.h>
+#include <LocalPeer.h>
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
@@ -159,6 +162,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
return;
}
}
+ m_instanceIdToLaunch = args["launch"].toString();
QString origcwdPath = QDir::currentPath();
QString binPath = applicationDirPath();
@@ -179,21 +183,32 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
adjustedBy += "Fallback to binary path " + dataPath;
}
- instanceIdToLaunch = args["launch"].toString();
-
if (!FS::ensureFolderPathExists(dataPath) || !QDir::setCurrent(dataPath))
{
// BAD STUFF. WHAT DO?
- initLogger();
- qCritical() << "Failed to set work path. Will exit. NOW.";
m_status = MultiMC::Failed;
return;
}
+ m_peerInstance = new LocalPeer(this, ApplicationId::fromPathAndVersion(dataPath, BuildConfig.printableVersionString()));
+ connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived);
+ if(m_peerInstance->isClient())
+ {
+ if(m_instanceIdToLaunch.isEmpty())
+ {
+ m_peerInstance->sendMessage("activate", 2000);
+ }
+ else
+ {
+ m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000);
+ }
+ quit();
+ return;
+ }
// in test mode, root path is the same as the binary path.
#ifdef Q_OS_LINUX
QDir foo(FS::PathCombine(binPath, ".."));
- rootPath = foo.absolutePath();
+ m_rootPath = foo.absolutePath();
#elif defined(Q_OS_WIN32)
rootPath = binPath;
#elif defined(Q_OS_MAC)
@@ -219,10 +234,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "Work dir : " << QDir::currentPath();
}
qDebug() << "Binary path : " << binPath;
- qDebug() << "Application root path : " << rootPath;
- if(!instanceIdToLaunch.isEmpty())
+ qDebug() << "Application root path : " << m_rootPath;
+ if(!m_instanceIdToLaunch.isEmpty())
{
- qDebug() << "ID of instance to launch : " << instanceIdToLaunch;
+ qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
}
// load settings
@@ -309,7 +324,22 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
}
connect(this, SIGNAL(aboutToQuit()), SLOT(onExit()));
+
m_status = MultiMC::Initialized;
+
+ setIconTheme(settings()->get("IconTheme").toString());
+ setApplicationTheme(settings()->get("ApplicationTheme").toString());
+ if(!m_instanceIdToLaunch.isEmpty())
+ {
+ auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
+ if(inst)
+ {
+ minecraftlist();
+ launch(inst, true, nullptr);
+ return;
+ }
+ }
+ showMainWindow();
}
MultiMC::~MultiMC()
@@ -333,6 +363,22 @@ MultiMC::~MultiMC()
#endif
}
+void MultiMC::messageReceived(const QString& message)
+{
+ if(message == "activate")
+ {
+ showMainWindow();
+ }
+ else
+ {
+ auto inst = instances()->getInstanceById(message);
+ if(inst)
+ {
+ launch(inst, true, nullptr);
+ }
+ }
+}
+
#ifdef Q_OS_MAC
#include "CertWorkaround.h"
#endif
@@ -1024,4 +1070,101 @@ bool MultiMC::openJsonEditor(const QString &filename)
}
}
+void MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler)
+{
+ if(instance->canLaunch())
+ {
+ m_launchController.reset(new LaunchController());
+ m_launchController->setInstance(instance);
+ m_launchController->setOnline(online);
+ m_launchController->setProfiler(profiler);
+ auto windowIter = m_instanceWindows.find(instance->id());
+ if(windowIter != m_instanceWindows.end())
+ {
+ auto window = *windowIter;
+ if(!window->saveAll())
+ {
+ return;
+ }
+ m_launchController->setParentWidget(window);
+ }
+ if(m_mainWindow)
+ {
+ m_launchController->setParentWidget(m_mainWindow);
+ }
+ m_launchController->start();
+ }
+ else if (instance->isRunning())
+ {
+ showInstanceWindow(instance, "console");
+ }
+}
+
+MainWindow * MultiMC::showMainWindow()
+{
+ if(m_mainWindow)
+ {
+ m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized);
+ m_mainWindow->raise();
+ m_mainWindow->activateWindow();
+ }
+ else
+ {
+ m_mainWindow = new MainWindow();
+ m_mainWindow->restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray()));
+ m_mainWindow->restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray()));
+ m_mainWindow->show();
+ m_mainWindow->checkSetDefaultJava();
+ m_mainWindow->checkInstancePathForProblems();
+ }
+ return m_mainWindow;
+}
+
+InstanceWindow *MultiMC::showInstanceWindow(InstancePtr instance, QString page)
+{
+ if(!instance)
+ return nullptr;
+ auto id = instance->id();
+ InstanceWindow * window = nullptr;
+
+ auto iter = m_instanceWindows.find(id);
+ if(iter != m_instanceWindows.end())
+ {
+ window = *iter;
+ window->raise();
+ window->activateWindow();
+ }
+ else
+ {
+ window = new InstanceWindow(instance);
+ m_instanceWindows[id] = window;
+ connect(window, &InstanceWindow::isClosing, this, &MultiMC::on_windowClose);
+ }
+ if(!page.isEmpty())
+ {
+ window->selectPage(page);
+ }
+ return window;
+}
+
+void MultiMC::on_windowClose()
+{
+ auto instWindow = qobject_cast<InstanceWindow *>(QObject::sender());
+ if(instWindow)
+ {
+ m_instanceWindows.remove(instWindow->instanceId());
+ return;
+ }
+ auto mainWindow = qobject_cast<MainWindow *>(QObject::sender());
+ if(mainWindow)
+ {
+ m_mainWindow = nullptr;
+ }
+ // quit when there are no more windows.
+ if(m_instanceWindows.isEmpty() && !m_mainWindow)
+ {
+ quit();
+ }
+}
+
#include "MultiMC.moc"
diff --git a/application/MultiMC.h b/application/MultiMC.h
index 070447fc..663da1a1 100644
--- a/application/MultiMC.h
+++ b/application/MultiMC.h
@@ -7,8 +7,14 @@
#include <QIcon>
#include <QDateTime>
#include <updater/GoUpdate.h>
-class FolderInstanceProvider;
+#include <BaseInstance.h>
+
+class LaunchController;
+class LocalPeer;
+class InstanceWindow;
+class MainWindow;
+class FolderInstanceProvider;
class GenericPageProvider;
class QFile;
class MinecraftVersionList;
@@ -36,8 +42,6 @@ class ITheme;
class MultiMC : public QApplication
{
// friends for the purpose of limiting access to deprecated stuff
- friend class MultiMCPage;
- friend class MainWindow;
Q_OBJECT
public:
enum Status
@@ -51,13 +55,12 @@ public:
MultiMC(int &argc, char **argv);
virtual ~MultiMC();
- // InstanceList, IconList, OneSixFTBInstance, LegacyUpdate, LegacyInstance, MCEditTool, JVisualVM, MinecraftInstance, JProfiler, BaseInstance
- std::shared_ptr<SettingsObject> settings()
+ std::shared_ptr<SettingsObject> settings() const
{
return m_settings;
}
- std::shared_ptr<GenericPageProvider> globalSettingsPages()
+ std::shared_ptr<GenericPageProvider> globalSettingsPages() const
{
return m_globalSettingsProvider;
}
@@ -72,6 +75,7 @@ public:
void setIconTheme(const QString& name);
std::vector<ITheme *> getValidApplicationThemes();
+
void setApplicationTheme(const QString& name);
// DownloadUpdateTask
@@ -86,7 +90,6 @@ public:
std::shared_ptr<LiteLoaderVersionList> liteloaderlist();
std::shared_ptr<JavaInstallList> javalist();
- // APPLICATION ONLY
std::shared_ptr<InstanceList> instances() const
{
return m_instances;
@@ -102,46 +105,44 @@ public:
return m_icons;
}
- // APPLICATION ONLY
std::shared_ptr<MojangAccountList> accounts() const
{
return m_accounts;
}
- // APPLICATION ONLY
Status status() const
{
return m_status;
}
- // APPLICATION ONLY
const QMap<QString, std::shared_ptr<BaseProfilerFactory>> &profilers() const
{
return m_profilers;
}
- // APPLICATION ONLY
const QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> &tools() const
{
return m_tools;
}
- // APPLICATION ONLY
+ /// this is the root of the 'installation'. Used for automatic updates
+ const QString &root()
+ {
+ return m_rootPath;
+ }
+
+ // install updates now.
void installUpdates(const QString updateFilesDir, GoUpdate::OperationList operations);
/*!
- * Opens a json file using either a system default editor, or, if note empty, the editor
+ * Opens a json file using either a system default editor, or, if not empty, the editor
* specified in the settings
*/
bool openJsonEditor(const QString &filename);
-protected: /* to be removed! */
- // FIXME: remove. used by MainWindow to create application update tasks
- /// this is the root of the 'installation'. Used for automatic updates
- const QString &root()
- {
- return rootPath;
- }
+ InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
+ MainWindow *showMainWindow();
+ void launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
private slots:
/**
@@ -149,6 +150,10 @@ private slots:
*/
void onExit();
+ void on_windowClose();
+
+ void messageReceived(const QString & message);
+
private:
void initLogger();
void initIcons();
@@ -160,6 +165,7 @@ private:
private:
QDateTime startTime;
+ unique_qobject_ptr<LaunchController> m_launchController;
std::shared_ptr<QTranslator> m_qt_translator;
std::shared_ptr<QTranslator> m_mmc_translator;
std::shared_ptr<SettingsObject> m_settings;
@@ -180,12 +186,19 @@ private:
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> m_tools;
- QString rootPath;
+ QString m_rootPath;
Status m_status = MultiMC::Failed;
+ // used on Windows to attach the standard IO streams
bool consoleAttached = false;
+
+ // map from instance ID to its window
+ QMap<QString, InstanceWindow *> m_instanceWindows;
+ // main window, if any
+ MainWindow * m_mainWindow = nullptr;
+ LocalPeer * m_peerInstance = nullptr;
public:
- QString instanceIdToLaunch;
+ QString m_instanceIdToLaunch;
std::unique_ptr<QFile> logFile;
};
diff --git a/application/main.cpp b/application/main.cpp
index c6847c5d..fde5eba7 100644
--- a/application/main.cpp
+++ b/application/main.cpp
@@ -4,41 +4,6 @@
#include <InstanceList.h>
#include <QDebug>
-int launchMainWindow(MultiMC &app)
-{
- MainWindow mainWin;
- mainWin.restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray()));
- mainWin.restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray()));
- mainWin.show();
- mainWin.checkSetDefaultJava();
- mainWin.checkInstancePathForProblems();
- return app.exec();
-}
-
-int launchInstance(MultiMC &app, InstancePtr inst)
-{
- app.minecraftlist();
- LaunchController launchController;
- launchController.setInstance(inst);
- launchController.setOnline(true);
- QMetaObject::invokeMethod(&launchController, "start", Qt::QueuedConnection);
- return app.exec();
-}
-
-int main_gui(MultiMC &app)
-{
- app.setIconTheme(MMC->settings()->get("IconTheme").toString());
- app.setApplicationTheme(MMC->settings()->get("ApplicationTheme").toString());
-
- // show main window
- auto inst = app.instances()->getInstanceById(app.instanceIdToLaunch);
- if(inst)
- {
- return launchInstance(app, inst);
- }
- return launchMainWindow(app);
-}
-
int main(int argc, char *argv[])
{
// initialize Qt
@@ -59,7 +24,7 @@ int main(int argc, char *argv[])
switch (app.status())
{
case MultiMC::Initialized:
- return main_gui(app);
+ return app.exec();
case MultiMC::Failed:
return 1;
case MultiMC::Succeeded:
diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt
new file mode 100644
index 00000000..99e3fe4d
--- /dev/null
+++ b/libraries/LocalPeer/CMakeLists.txt
@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 3.1)
+project(LocalPeer)
+
+find_package(Qt5Core REQUIRED QUIET)
+find_package(Qt5Network REQUIRED QUIET)
+
+set(SINGLE_SOURCES
+src/LocalPeer.cpp
+src/LockedFile.cpp
+src/LockedFile.h
+include/LocalPeer.h
+)
+
+if(UNIX)
+ list(APPEND SINGLE_SOURCES
+ src/LockedFile_unix.cpp
+ )
+endif()
+
+if(WIN32)
+ list(APPEND SINGLE_SOURCES
+ src/LockedFile_win.cpp
+ )
+endif()
+
+add_library(LocalPeer STATIC ${SINGLE_SOURCES})
+target_include_directories(LocalPeer PUBLIC include)
+
+qt5_use_modules(LocalPeer Core Network)
diff --git a/libraries/LocalPeer/include/LocalPeer.h b/libraries/LocalPeer/include/LocalPeer.h
new file mode 100644
index 00000000..940bfcbd
--- /dev/null
+++ b/libraries/LocalPeer/include/LocalPeer.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Solutions component.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+#include <QObject>
+#include <QString>
+#include <memory>
+
+
+class QLocalServer;
+class LockedFile;
+
+class ApplicationId
+{
+public: /* methods */
+ // traditional app = installed system wide and used in a multi-user environment
+ static ApplicationId fromTraditionalApp();
+ // ID based on a path with all the application data (no two instances with the same data path should run)
+ static ApplicationId fromPathAndVersion(const QString & dataPath, const QString & version);
+ // fully custom ID
+ static ApplicationId fromCustomId(const QString & id);
+
+ QString toString()
+ {
+ return m_id;
+ }
+
+private: /* methods */
+ ApplicationId(const QString & value)
+ {
+ m_id = value;
+ }
+
+private: /* data */
+ QString m_id;
+};
+
+class LocalPeer : public QObject
+{
+ Q_OBJECT
+
+public:
+ LocalPeer(QObject *parent, const ApplicationId &appId);
+ ~LocalPeer();
+ bool isClient();
+ bool sendMessage(const QString &message, int timeout);
+ ApplicationId applicationId() const;
+
+Q_SIGNALS:
+ void messageReceived(const QString &message);
+
+protected Q_SLOTS:
+ void receiveConnection();
+
+protected:
+ ApplicationId id;
+ QString socketName;
+ std::unique_ptr<QLocalServer> server;
+ std::unique_ptr<LockedFile> lockFile;
+};
diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp
new file mode 100644
index 00000000..db0c73e5
--- /dev/null
+++ b/libraries/LocalPeer/src/LocalPeer.cpp
@@ -0,0 +1,236 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Solutions component.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include "LocalPeer.h"
+#include <QCoreApplication>
+#include <QDataStream>
+#include <QTime>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QDir>
+#include "LockedFile.h"
+
+#if defined(Q_OS_WIN)
+#include <QLibrary>
+#include <qt_windows.h>
+typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
+static PProcessIdToSessionId pProcessIdToSessionId = 0;
+#endif
+#if defined(Q_OS_UNIX)
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include <chrono>
+#include <thread>
+#include <QCryptographicHash>
+
+static const char* ack = "ack";
+
+ApplicationId ApplicationId::fromTraditionalApp()
+{
+ QString protoId = QCoreApplication::applicationFilePath();
+#if defined(Q_OS_WIN)
+ protoId = protoId.toLower();
+#endif
+ auto prefix = protoId.section(QLatin1Char('/'), -1);
+ prefix.remove(QRegExp("[^a-zA-Z]"));
+ prefix.truncate(6);
+ QByteArray idc = protoId.toUtf8();
+ quint16 idNum = qChecksum(idc.constData(), idc.size());
+ auto socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16);
+#if defined(Q_OS_WIN)
+ if (!pProcessIdToSessionId)
+ {
+ QLibrary lib("kernel32");
+ pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
+ }
+ if (pProcessIdToSessionId)
+ {
+ DWORD sessionId = 0;
+ pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
+ socketName += QLatin1Char('-') + QString::number(sessionId, 16);
+ }
+#else
+ socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
+#endif
+ return ApplicationId(socketName);
+}
+
+ApplicationId ApplicationId::fromPathAndVersion(const QString& dataPath, const QString& version)
+{
+ QCryptographicHash shasum(QCryptographicHash::Algorithm::Sha1);
+ QString result = dataPath + QLatin1Char('-') + version;
+ shasum.addData(result.toUtf8());
+ return ApplicationId(QLatin1String("qtsingleapp-") + QString::fromLatin1(shasum.result().toHex()));
+}
+
+ApplicationId ApplicationId::fromCustomId(const QString& id)
+{
+ return ApplicationId(QLatin1String("qtsingleapp-") + id);
+}
+
+LocalPeer::LocalPeer(QObject * parent, const ApplicationId &appId)
+ : QObject(parent), id(appId)
+{
+ socketName = id.toString();
+ server.reset(new QLocalServer());
+ QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile");
+ lockFile.reset(new LockedFile(lockName));
+ lockFile->open(QIODevice::ReadWrite);
+}
+
+LocalPeer::~LocalPeer()
+{
+}
+
+ApplicationId LocalPeer::applicationId() const
+{
+ return id;
+}
+
+bool LocalPeer::isClient()
+{
+ if (lockFile->isLocked())
+ return false;
+
+ if (!lockFile->lock(LockedFile::WriteLock, false))
+ return true;
+
+ bool res = server->listen(socketName);
+#if defined(Q_OS_UNIX)
+ // ### Workaround
+ if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
+ QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
+ res = server->listen(socketName);
+ }
+#endif
+ if (!res)
+ qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
+ QObject::connect(server.get(), SIGNAL(newConnection()), SLOT(receiveConnection()));
+ return false;
+}
+
+
+bool LocalPeer::sendMessage(const QString &message, int timeout)
+{
+ if (!isClient())
+ return false;
+
+ QLocalSocket socket;
+ bool connOk = false;
+ for(int i = 0; i < 2; i++) {
+ // Try twice, in case the other instance is just starting up
+ socket.connectToServer(socketName);
+ connOk = socket.waitForConnected(timeout/2);
+ if (connOk || i)
+ {
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(250));
+ }
+ if (!connOk)
+ {
+ return false;
+ }
+
+ QByteArray uMsg(message.toUtf8());
+ QDataStream ds(&socket);
+
+ ds.writeBytes(uMsg.constData(), uMsg.size());
+ if(!socket.waitForBytesWritten(timeout))
+ {
+ return false;
+ }
+
+ // wait for 'ack'
+ if(!socket.waitForReadyRead(timeout))
+ {
+ return false;
+ }
+
+ // make sure we got 'ack'
+ if(!(socket.read(qstrlen(ack)) == ack))
+ {
+ return false;
+ }
+ return true;
+}
+
+
+void LocalPeer::receiveConnection()
+{
+ QLocalSocket* socket = server->nextPendingConnection();
+ if (!socket)
+ {
+ return;
+ }
+
+ while (socket->bytesAvailable() < (int)sizeof(quint32))
+ {
+ socket->waitForReadyRead();
+ }
+ QDataStream ds(socket);
+ QByteArray uMsg;
+ quint32 remaining;
+ ds >> remaining;
+ uMsg.resize(remaining);
+ int got = 0;
+ char* uMsgBuf = uMsg.data();
+ do
+ {
+ got = ds.readRawData(uMsgBuf, remaining);
+ remaining -= got;
+ uMsgBuf += got;
+ } while (remaining && got >= 0 && socket->waitForReadyRead(2000));
+ if (got < 0)
+ {
+ qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
+ delete socket;
+ return;
+ }
+ QString message(QString::fromUtf8(uMsg));
+ socket->write(ack, qstrlen(ack));
+ socket->waitForBytesWritten(1000);
+ socket->waitForDisconnected(1000); // make sure client reads ack
+ delete socket;
+ emit messageReceived(message); //### (might take a long time to return)
+}
diff --git a/libraries/LocalPeer/src/LockedFile.cpp b/libraries/LocalPeer/src/LockedFile.cpp
new file mode 100644
index 00000000..a4951bfe
--- /dev/null
+++ b/libraries/LocalPeer/src/LockedFile.cpp
@@ -0,0 +1,193 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Solutions component.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "LockedFile.h"
+
+/*!
+ \class QtLockedFile
+
+ \brief The QtLockedFile class extends QFile with advisory locking
+ functions.
+
+ A file may be locked in read or write mode. Multiple instances of
+ \e QtLockedFile, created in multiple processes running on the same
+ machine, may have a file locked in read mode. Exactly one instance
+ may have it locked in write mode. A read and a write lock cannot
+ exist simultaneously on the same file.
+
+ The file locks are advisory. This means that nothing prevents
+ another process from manipulating a locked file using QFile or
+ file system functions offered by the OS. Serialization is only
+ guaranteed if all processes that access the file use
+ QLockedFile. Also, while holding a lock on a file, a process
+ must not open the same file again (through any API), or locks
+ can be unexpectedly lost.
+
+ The lock provided by an instance of \e QtLockedFile is released
+ whenever the program terminates. This is true even when the
+ program crashes and no destructors are called.
+*/
+
+/*! \enum QtLockedFile::LockMode
+
+ This enum describes the available lock modes.
+
+ \value ReadLock A read lock.
+ \value WriteLock A write lock.
+ \value NoLock Neither a read lock nor a write lock.
+*/
+
+/*!
+ Constructs an unlocked \e QtLockedFile object. This constructor
+ behaves in the same way as \e QFile::QFile().
+
+ \sa QFile::QFile()
+*/
+LockedFile::LockedFile()
+ : QFile()
+{
+#ifdef Q_OS_WIN
+ wmutex = 0;
+ rmutex = 0;
+#endif
+ m_lock_mode = NoLock;
+}
+
+/*!
+ Constructs an unlocked QtLockedFile object with file \a name. This
+ constructor behaves in the same way as \e QFile::QFile(const
+ QString&).
+
+ \sa QFile::QFile()
+*/
+LockedFile::LockedFile(const QString &name)
+ : QFile(name)
+{
+#ifdef Q_OS_WIN
+ wmutex = 0;
+ rmutex = 0;
+#endif
+ m_lock_mode = NoLock;
+}
+
+/*!
+Opens the file in OpenMode \a mode.
+
+This is identical to QFile::open(), with the one exception that the
+Truncate mode flag is disallowed. Truncation would conflict with the
+advisory file locking, since the file would be modified before the
+write lock is obtained. If truncation is required, use resize(0)
+after obtaining the write lock.
+
+Returns true if successful; otherwise false.
+
+\sa QFile::open(), QFile::resize()
+*/
+bool LockedFile::open(OpenMode mode)
+{
+ if (mode & QIODevice::Truncate) {
+ qWarning("QtLockedFile::open(): Truncate mode not allowed.");
+ return false;
+ }
+ return QFile::open(mode);
+}
+
+/*!
+ Returns \e true if this object has a in read or write lock;
+ otherwise returns \e false.
+
+ \sa lockMode()
+*/
+bool LockedFile::isLocked() const
+{
+ return m_lock_mode != NoLock;
+}
+
+/*!
+ Returns the type of lock currently held by this object, or \e
+ QtLockedFile::NoLock.
+
+ \sa isLocked()
+*/
+LockedFile::LockMode LockedFile::lockMode() const
+{
+ return m_lock_mode;
+}
+
+/*!
+ \fn bool QtLockedFile::lock(LockMode mode, bool block = true)
+
+ Obtains a lock of type \a mode. The file must be opened before it
+ can be locked.
+
+ If \a block is true, this function will block until the lock is
+ aquired. If \a block is false, this function returns \e false
+ immediately if the lock cannot be aquired.
+
+ If this object already has a lock of type \a mode, this function
+ returns \e true immediately. If this object has a lock of a
+ different type than \a mode, the lock is first released and then a
+ new lock is obtained.
+
+ This function returns \e true if, after it executes, the file is
+ locked by this object, and \e false otherwise.
+
+ \sa unlock(), isLocked(), lockMode()
+*/
+
+/*!
+ \fn bool QtLockedFile::unlock()
+
+ Releases a lock.
+
+ If the object has no lock, this function returns immediately.
+
+ This function returns \e true if, after it executes, the file is
+ not locked by this object, and \e false otherwise.
+
+ \sa lock(), isLocked(), lockMode()
+*/
+
+/*!
+ \fn QtLockedFile::~QtLockedFile()
+
+ Destroys the \e QtLockedFile object. If any locks were held, they
+ are released.
+*/
diff --git a/libraries/LocalPeer/src/LockedFile.h b/libraries/LocalPeer/src/LockedFile.h
new file mode 100644
index 00000000..8c178250
--- /dev/null
+++ b/libraries/LocalPeer/src/LockedFile.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Solutions component.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QFile>
+#ifdef Q_OS_WIN
+#include <QVector>
+#endif
+
+class LockedFile : public QFile
+{
+public:
+ enum LockMode { NoLock = 0, ReadLock, WriteLock };
+
+ LockedFile();
+ LockedFile(const QString &name);
+ ~LockedFile();
+
+ bool open(OpenMode mode);
+
+ bool lock(LockMode mode, bool block = true);
+ bool unlock();
+ bool isLocked() const;
+ LockMode lockMode() const;
+
+ private:
+
+#ifdef Q_OS_WIN
+ Qt::HANDLE wmutex;
+ Qt::HANDLE rmutex;
+ QVector<Qt::HANDLE> rmutexes;
+ QString mutexname;
+
+ Qt::HANDLE getMutexHandle(int idx, bool doCreate);
+ bool waitMutex(Qt::HANDLE mutex, bool doBlock);
+#endif
+
+ LockMode m_lock_mode;
+};
diff --git a/libraries/LocalPeer/src/LockedFile_unix.cpp b/libraries/LocalPeer/src/LockedFile_unix.cpp
new file mode 100644
index 00000000..4b68916c
--- /dev/null
+++ b/libraries/LocalPeer/src/LockedFile_unix.cpp
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Solutions component.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "LockedFile.h"
+
+bool LockedFile::lock(LockMode mode, bool block)
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::lock(): file is not opened");
+ return false;
+ }
+
+ if (mode == NoLock)
+ return unlock();
+
+ if (mode == m_lock_mode)
+ return true;
+
+ if (m_lock_mode != NoLock)
+ unlock();
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
+ int cmd = block ? F_SETLKW : F_SETLK;
+ int ret = fcntl(handle(), cmd, &fl);
+
+ if (ret == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+ return false;
+ }
+
+
+ m_lock_mode = mode;
+ return true;
+}
+
+
+bool LockedFile::unlock()
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::unlock(): file is not opened");
+ return false;
+ }
+
+ if (!isLocked())
+ return true;
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ int ret = fcntl(handle(), F_SETLKW, &fl);
+
+ if (ret == -1) {
+ qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+ return false;
+ }
+
+ m_lock_mode = NoLock;
+ return true;
+}
+
+LockedFile::~LockedFile()
+{
+ if (isOpen())
+ unlock();
+}
diff --git a/libraries/LocalPeer/src/LockedFile_win.cpp b/libraries/LocalPeer/src/LockedFile_win.cpp
new file mode 100644
index 00000000..3d7d5553
--- /dev/null
+++ b/libraries/LocalPeer/src/LockedFile_win.cpp
@@ -0,0 +1,205 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Solutions component.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "LockedFile.h"
+#include <qt_windows.h>
+#include <QFileInfo>
+
+#define MUTEX_PREFIX "QtLockedFile mutex "
+// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
+#define MAX_READERS MAXIMUM_WAIT_OBJECTS
+
+Qt::HANDLE LockedFile::getMutexHandle(int idx, bool doCreate)
+{
+ if (mutexname.isEmpty()) {
+ QFileInfo fi(*this);
+ mutexname = QString::fromLatin1(MUTEX_PREFIX)
+ + fi.absoluteFilePath().toLower();
+ }
+ QString mname(mutexname);
+ if (idx >= 0)
+ mname += QString::number(idx);
+
+ Qt::HANDLE mutex;
+ if (doCreate) {
+ mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16());
+ if (!mutex) {
+ qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
+ return 0;
+ }
+ }
+ else {
+ OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16());
+ if (!mutex) {
+ if (GetLastError() != ERROR_FILE_NOT_FOUND)
+ qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
+ return 0;
+ }
+ }
+ return mutex;
+}
+
+bool LockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
+{
+ Q_ASSERT(mutex);
+ DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
+ switch (res) {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED:
+ return true;
+ break;
+ case WAIT_TIMEOUT:
+ break;
+ default:
+ qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
+ }
+ return false;
+}
+
+
+
+bool LockedFile::lock(LockMode mode, bool block)
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::lock(): file is not opened");
+ return false;
+ }
+
+ if (mode == NoLock)
+ return unlock();
+
+ if (mode == m_lock_mode)
+ return true;
+
+ if (m_lock_mode != NoLock)
+ unlock();
+
+ if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
+ return false;
+
+ if (!waitMutex(wmutex, block))
+ return false;
+
+ if (mode == ReadLock) {
+ int idx = 0;
+ for (; idx < MAX_READERS; idx++) {
+ rmutex = getMutexHandle(idx, false);
+ if (!rmutex || waitMutex(rmutex, false))
+ break;
+ CloseHandle(rmutex);
+ }
+ bool ok = true;
+ if (idx >= MAX_READERS) {
+ qWarning("QtLockedFile::lock(): too many readers");
+ rmutex = 0;
+ ok = false;
+ }
+ else if (!rmutex) {
+ rmutex = getMutexHandle(idx, true);
+ if (!rmutex || !waitMutex(rmutex, false))
+ ok = false;
+ }
+ if (!ok && rmutex) {
+ CloseHandle(rmutex);
+ rmutex = 0;
+ }
+ ReleaseMutex(wmutex);
+ if (!ok)
+ return false;
+ }
+ else {
+ Q_ASSERT(rmutexes.isEmpty());
+ for (int i = 0; i < MAX_READERS; i++) {
+ Qt::HANDLE mutex = getMutexHandle(i, false);
+ if (mutex)
+ rmutexes.append(mutex);
+ }
+ if (rmutexes.size()) {
+ DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
+ TRUE, block ? INFINITE : 0);
+ if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
+ if (res != WAIT_TIMEOUT)
+ qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
+ m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
+ unlock();
+ return false;
+ }
+ }
+ }
+
+ m_lock_mode = mode;
+ return true;
+}
+
+bool LockedFile::unlock()
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::unlock(): file is not opened");
+ return false;
+ }
+
+ if (!isLocked())
+ return true;
+
+ if (m_lock_mode == ReadLock) {
+ ReleaseMutex(rmutex);
+ CloseHandle(rmutex);
+ rmutex = 0;
+ }
+ else {
+ foreach(Qt::HANDLE mutex, rmutexes) {
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+ }
+ rmutexes.clear();
+ ReleaseMutex(wmutex);
+ }
+
+ m_lock_mode = LockedFile::NoLock;
+ return true;
+}
+
+LockedFile::~LockedFile()
+{
+ if (isOpen())
+ unlock();
+ if (wmutex)
+ CloseHandle(wmutex);
+}
diff --git a/libraries/README.md b/libraries/README.md
index 809acb16..9d91d098 100644
--- a/libraries/README.md
+++ b/libraries/README.md
@@ -142,4 +142,11 @@ Available either under LGPL version 2.1 or later.
## xz-embedded
Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth.
-Public domain. \ No newline at end of file
+Public domain.
+
+## LocalPeer
+Library for making only one instance of the application run at all times.
+
+BSD licensed, derived from [QtSingleApplication](https://github.com/qtproject/qt-solutions/tree/master/qtsingleapplication).
+
+Changes are made to make the code more generic and useful in less usual conditions.