NOISSUE refactor window management and launch, make MultiMC a single instance application.
This commit is contained in:
parent
deabfa78f8
commit
412855ae3d
@ -98,6 +98,7 @@ add_subdirectory(libraries/xz-embedded) # xz compression
|
|||||||
add_subdirectory(libraries/pack200) # java pack200 compression
|
add_subdirectory(libraries/pack200) # java pack200 compression
|
||||||
add_subdirectory(libraries/rainbow) # Qt extension for colors
|
add_subdirectory(libraries/rainbow) # Qt extension for colors
|
||||||
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
|
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
|
||||||
|
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
|
||||||
|
|
||||||
############################### Built Artifacts ###############################
|
############################### Built Artifacts ###############################
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
|
|||||||
|
|
||||||
# Add executable
|
# Add executable
|
||||||
add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES} ${MULTIMC_RCS})
|
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)
|
if(APPLE)
|
||||||
find_library(OSX_CORE_FOUNDATION CoreFoundation)
|
find_library(OSX_CORE_FOUNDATION CoreFoundation)
|
||||||
|
@ -174,11 +174,6 @@ void InstanceWindow::closeEvent(QCloseEvent *event)
|
|||||||
MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64());
|
MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64());
|
||||||
emit isClosing();
|
emit isClosing();
|
||||||
event->accept();
|
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()
|
bool InstanceWindow::saveAll()
|
||||||
@ -203,11 +198,7 @@ void InstanceWindow::on_btnKillMinecraft_clicked()
|
|||||||
// FIXME: duplicate logic between MainWindow and InstanceWindow
|
// FIXME: duplicate logic between MainWindow and InstanceWindow
|
||||||
else if(saveAll())
|
else if(saveAll())
|
||||||
{
|
{
|
||||||
m_launchController.reset(new LaunchController());
|
MMC->launch(m_instance, true, nullptr);
|
||||||
m_launchController->setInstance(m_instance);
|
|
||||||
m_launchController->setOnline(true);
|
|
||||||
m_launchController->setParentWidget(this);
|
|
||||||
m_launchController->start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,11 +36,6 @@ public:
|
|||||||
|
|
||||||
QString instanceId();
|
QString instanceId();
|
||||||
|
|
||||||
void setQuitOnClose(bool shouldQuit = false)
|
|
||||||
{
|
|
||||||
m_shouldQuit = shouldQuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// save all settings and changes (prepare for launch)
|
// save all settings and changes (prepare for launch)
|
||||||
bool saveAll();
|
bool saveAll();
|
||||||
|
|
||||||
@ -68,9 +63,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<LaunchTask> m_proc;
|
std::shared_ptr<LaunchTask> m_proc;
|
||||||
unique_qobject_ptr<LaunchController> m_launchController;
|
|
||||||
InstancePtr m_instance;
|
InstancePtr m_instance;
|
||||||
bool m_shouldQuit = false;
|
|
||||||
bool m_doNotSave = false;
|
bool m_doNotSave = false;
|
||||||
PageContainer *m_container = nullptr;
|
PageContainer *m_container = nullptr;
|
||||||
QPushButton *m_closeButton = nullptr;
|
QPushButton *m_closeButton = nullptr;
|
||||||
|
@ -205,21 +205,10 @@ void LaunchController::launchInstance()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mainWindow = qobject_cast<MainWindow *>(m_parentWidget);
|
auto console = qobject_cast<InstanceWindow *>(m_parentWidget);
|
||||||
auto instanceWindow = qobject_cast<InstanceWindow *>(m_parentWidget);
|
if(!console)
|
||||||
if(mainWindow)
|
|
||||||
{
|
{
|
||||||
m_console = mainWindow->showInstanceWindow(m_instance);
|
MMC->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);
|
|
||||||
}
|
}
|
||||||
connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);
|
connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);
|
||||||
|
|
||||||
|
@ -691,7 +691,7 @@ void MainWindow::updateToolsMenu()
|
|||||||
QAction *normalLaunch = launchMenu->addAction(tr("Launch"));
|
QAction *normalLaunch = launchMenu->addAction(tr("Launch"));
|
||||||
connect(normalLaunch, &QAction::triggered, [this]()
|
connect(normalLaunch, &QAction::triggered, [this]()
|
||||||
{
|
{
|
||||||
launch(m_selectedInstance);
|
MMC->launch(m_selectedInstance);
|
||||||
});
|
});
|
||||||
launchMenu->addSeparator()->setText(tr("Profilers"));
|
launchMenu->addSeparator()->setText(tr("Profilers"));
|
||||||
for (auto profiler : MMC->profilers().values())
|
for (auto profiler : MMC->profilers().values())
|
||||||
@ -707,7 +707,7 @@ void MainWindow::updateToolsMenu()
|
|||||||
{
|
{
|
||||||
connect(profilerAction, &QAction::triggered, [this, profiler]()
|
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.";
|
qDebug() << "Downloading updates.";
|
||||||
ProgressDialog updateDlg(this);
|
ProgressDialog updateDlg(this);
|
||||||
status.rootPath = MMC->rootPath;
|
status.rootPath = MMC->root();
|
||||||
|
|
||||||
auto dlPath = FS::PathCombine(MMC->root(), "update", "XXXXXX");
|
auto dlPath = FS::PathCombine(MMC->root(), "update", "XXXXXX");
|
||||||
if (!FS::ensureFilePathExists(dlPath))
|
if (!FS::ensureFilePathExists(dlPath))
|
||||||
@ -1004,8 +1004,8 @@ void MainWindow::waitForMinecraftVersions()
|
|||||||
if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask && m_versionLoadTask->isRunning())
|
if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask && m_versionLoadTask->isRunning())
|
||||||
{
|
{
|
||||||
QEventLoop waitLoop;
|
QEventLoop waitLoop;
|
||||||
waitLoop.connect(m_versionLoadTask, SIGNAL(failed(QString)), SLOT(quit()));
|
waitLoop.connect(m_versionLoadTask, &Task::failed, &waitLoop, &QEventLoop::quit);
|
||||||
waitLoop.connect(m_versionLoadTask, SIGNAL(succeeded()), SLOT(quit()));
|
waitLoop.connect(m_versionLoadTask, &Task::succeeded, &waitLoop, &QEventLoop::quit);
|
||||||
waitLoop.exec();
|
waitLoop.exec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1261,62 +1261,24 @@ void MainWindow::on_actionSettings_triggered()
|
|||||||
update();
|
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()
|
void MainWindow::on_actionInstanceSettings_triggered()
|
||||||
{
|
{
|
||||||
showInstanceWindow(m_selectedInstance, "settings");
|
MMC->showInstanceWindow(m_selectedInstance, "settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionEditInstNotes_triggered()
|
void MainWindow::on_actionEditInstNotes_triggered()
|
||||||
{
|
{
|
||||||
showInstanceWindow(m_selectedInstance, "notes");
|
MMC->showInstanceWindow(m_selectedInstance, "notes");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionEditInstance_triggered()
|
void MainWindow::on_actionEditInstance_triggered()
|
||||||
{
|
{
|
||||||
showInstanceWindow(m_selectedInstance);
|
MMC->showInstanceWindow(m_selectedInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionScreenshots_triggered()
|
void MainWindow::on_actionScreenshots_triggered()
|
||||||
{
|
{
|
||||||
showInstanceWindow(m_selectedInstance, "screenshots");
|
MMC->showInstanceWindow(m_selectedInstance, "screenshots");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionManageAccounts_triggered()
|
void MainWindow::on_actionManageAccounts_triggered()
|
||||||
@ -1440,14 +1402,14 @@ void MainWindow::instanceActivated(QModelIndex index)
|
|||||||
if (!inst)
|
if (!inst)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
launch(inst);
|
MMC->launch(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionLaunchInstance_triggered()
|
void MainWindow::on_actionLaunchInstance_triggered()
|
||||||
{
|
{
|
||||||
if (m_selectedInstance)
|
if (m_selectedInstance)
|
||||||
{
|
{
|
||||||
launch(m_selectedInstance);
|
MMC->launch(m_selectedInstance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1455,33 +1417,7 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered()
|
|||||||
{
|
{
|
||||||
if (m_selectedInstance)
|
if (m_selectedInstance)
|
||||||
{
|
{
|
||||||
launch(m_selectedInstance, false);
|
MMC->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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ class MinecraftLauncher;
|
|||||||
class BaseProfilerFactory;
|
class BaseProfilerFactory;
|
||||||
class GroupView;
|
class GroupView;
|
||||||
class ServerStatus;
|
class ServerStatus;
|
||||||
class InstanceWindow;
|
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
@ -55,8 +54,6 @@ public:
|
|||||||
void checkSetDefaultJava();
|
void checkSetDefaultJava();
|
||||||
void checkInstancePathForProblems();
|
void checkInstancePathForProblems();
|
||||||
|
|
||||||
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onCatToggled(bool);
|
void onCatToggled(bool);
|
||||||
|
|
||||||
@ -162,8 +159,6 @@ private slots:
|
|||||||
*/
|
*/
|
||||||
void downloadUpdates(GoUpdate::Status status);
|
void downloadUpdates(GoUpdate::Status status);
|
||||||
|
|
||||||
void on_instanceWindowClose();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setCatBackground(bool enabled);
|
void setCatBackground(bool enabled);
|
||||||
void updateInstanceToolIcon(QString new_icon);
|
void updateInstanceToolIcon(QString new_icon);
|
||||||
@ -174,7 +169,6 @@ private:
|
|||||||
void instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version);
|
void instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version);
|
||||||
void instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url);
|
void instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url);
|
||||||
void finalizeInstance(InstancePtr inst);
|
void finalizeInstance(InstancePtr inst);
|
||||||
void launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<Ui> ui;
|
std::unique_ptr<Ui> ui;
|
||||||
@ -194,14 +188,10 @@ private:
|
|||||||
unique_qobject_ptr<NetJob> skin_download_job;
|
unique_qobject_ptr<NetJob> skin_download_job;
|
||||||
unique_qobject_ptr<NewsChecker> m_newsChecker;
|
unique_qobject_ptr<NewsChecker> m_newsChecker;
|
||||||
unique_qobject_ptr<NotificationChecker> m_notificationChecker;
|
unique_qobject_ptr<NotificationChecker> m_notificationChecker;
|
||||||
unique_qobject_ptr<LaunchController> m_launchController;
|
|
||||||
|
|
||||||
InstancePtr m_selectedInstance;
|
InstancePtr m_selectedInstance;
|
||||||
QString m_currentInstIcon;
|
QString m_currentInstIcon;
|
||||||
|
|
||||||
// managed by the application object
|
// managed by the application object
|
||||||
Task *m_versionLoadTask;
|
Task *m_versionLoadTask;
|
||||||
|
|
||||||
// map from instance ID to its window
|
|
||||||
QMap<QString, InstanceWindow *> m_instanceWindows;
|
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#include "MultiMC.h"
|
#include "MultiMC.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
#include "MainWindow.h"
|
||||||
|
#include "InstanceWindow.h"
|
||||||
#include "pages/BasePageProvider.h"
|
#include "pages/BasePageProvider.h"
|
||||||
#include "pages/global/MultiMCPage.h"
|
#include "pages/global/MultiMCPage.h"
|
||||||
#include "pages/global/MinecraftPage.h"
|
#include "pages/global/MinecraftPage.h"
|
||||||
@ -59,6 +61,7 @@
|
|||||||
#include <Commandline.h>
|
#include <Commandline.h>
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include <DesktopServices.h>
|
#include <DesktopServices.h>
|
||||||
|
#include <LocalPeer.h>
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
@ -159,6 +162,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m_instanceIdToLaunch = args["launch"].toString();
|
||||||
|
|
||||||
QString origcwdPath = QDir::currentPath();
|
QString origcwdPath = QDir::currentPath();
|
||||||
QString binPath = applicationDirPath();
|
QString binPath = applicationDirPath();
|
||||||
@ -179,21 +183,32 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
adjustedBy += "Fallback to binary path " + dataPath;
|
adjustedBy += "Fallback to binary path " + dataPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceIdToLaunch = args["launch"].toString();
|
|
||||||
|
|
||||||
if (!FS::ensureFolderPathExists(dataPath) || !QDir::setCurrent(dataPath))
|
if (!FS::ensureFolderPathExists(dataPath) || !QDir::setCurrent(dataPath))
|
||||||
{
|
{
|
||||||
// BAD STUFF. WHAT DO?
|
// BAD STUFF. WHAT DO?
|
||||||
initLogger();
|
|
||||||
qCritical() << "Failed to set work path. Will exit. NOW.";
|
|
||||||
m_status = MultiMC::Failed;
|
m_status = MultiMC::Failed;
|
||||||
return;
|
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.
|
// in test mode, root path is the same as the binary path.
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
QDir foo(FS::PathCombine(binPath, ".."));
|
QDir foo(FS::PathCombine(binPath, ".."));
|
||||||
rootPath = foo.absolutePath();
|
m_rootPath = foo.absolutePath();
|
||||||
#elif defined(Q_OS_WIN32)
|
#elif defined(Q_OS_WIN32)
|
||||||
rootPath = binPath;
|
rootPath = binPath;
|
||||||
#elif defined(Q_OS_MAC)
|
#elif defined(Q_OS_MAC)
|
||||||
@ -219,10 +234,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
qDebug() << "Work dir : " << QDir::currentPath();
|
qDebug() << "Work dir : " << QDir::currentPath();
|
||||||
}
|
}
|
||||||
qDebug() << "Binary path : " << binPath;
|
qDebug() << "Binary path : " << binPath;
|
||||||
qDebug() << "Application root path : " << rootPath;
|
qDebug() << "Application root path : " << m_rootPath;
|
||||||
if(!instanceIdToLaunch.isEmpty())
|
if(!m_instanceIdToLaunch.isEmpty())
|
||||||
{
|
{
|
||||||
qDebug() << "ID of instance to launch : " << instanceIdToLaunch;
|
qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// load settings
|
// load settings
|
||||||
@ -309,7 +324,22 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
connect(this, SIGNAL(aboutToQuit()), SLOT(onExit()));
|
connect(this, SIGNAL(aboutToQuit()), SLOT(onExit()));
|
||||||
|
|
||||||
m_status = MultiMC::Initialized;
|
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()
|
MultiMC::~MultiMC()
|
||||||
@ -333,6 +363,22 @@ MultiMC::~MultiMC()
|
|||||||
#endif
|
#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
|
#ifdef Q_OS_MAC
|
||||||
#include "CertWorkaround.h"
|
#include "CertWorkaround.h"
|
||||||
#endif
|
#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"
|
#include "MultiMC.moc"
|
||||||
|
@ -7,8 +7,14 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <updater/GoUpdate.h>
|
#include <updater/GoUpdate.h>
|
||||||
class FolderInstanceProvider;
|
|
||||||
|
|
||||||
|
#include <BaseInstance.h>
|
||||||
|
|
||||||
|
class LaunchController;
|
||||||
|
class LocalPeer;
|
||||||
|
class InstanceWindow;
|
||||||
|
class MainWindow;
|
||||||
|
class FolderInstanceProvider;
|
||||||
class GenericPageProvider;
|
class GenericPageProvider;
|
||||||
class QFile;
|
class QFile;
|
||||||
class MinecraftVersionList;
|
class MinecraftVersionList;
|
||||||
@ -36,8 +42,6 @@ class ITheme;
|
|||||||
class MultiMC : public QApplication
|
class MultiMC : public QApplication
|
||||||
{
|
{
|
||||||
// friends for the purpose of limiting access to deprecated stuff
|
// friends for the purpose of limiting access to deprecated stuff
|
||||||
friend class MultiMCPage;
|
|
||||||
friend class MainWindow;
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum Status
|
enum Status
|
||||||
@ -51,13 +55,12 @@ public:
|
|||||||
MultiMC(int &argc, char **argv);
|
MultiMC(int &argc, char **argv);
|
||||||
virtual ~MultiMC();
|
virtual ~MultiMC();
|
||||||
|
|
||||||
// InstanceList, IconList, OneSixFTBInstance, LegacyUpdate, LegacyInstance, MCEditTool, JVisualVM, MinecraftInstance, JProfiler, BaseInstance
|
std::shared_ptr<SettingsObject> settings() const
|
||||||
std::shared_ptr<SettingsObject> settings()
|
|
||||||
{
|
{
|
||||||
return m_settings;
|
return m_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<GenericPageProvider> globalSettingsPages()
|
std::shared_ptr<GenericPageProvider> globalSettingsPages() const
|
||||||
{
|
{
|
||||||
return m_globalSettingsProvider;
|
return m_globalSettingsProvider;
|
||||||
}
|
}
|
||||||
@ -72,6 +75,7 @@ public:
|
|||||||
void setIconTheme(const QString& name);
|
void setIconTheme(const QString& name);
|
||||||
|
|
||||||
std::vector<ITheme *> getValidApplicationThemes();
|
std::vector<ITheme *> getValidApplicationThemes();
|
||||||
|
|
||||||
void setApplicationTheme(const QString& name);
|
void setApplicationTheme(const QString& name);
|
||||||
|
|
||||||
// DownloadUpdateTask
|
// DownloadUpdateTask
|
||||||
@ -86,7 +90,6 @@ public:
|
|||||||
std::shared_ptr<LiteLoaderVersionList> liteloaderlist();
|
std::shared_ptr<LiteLoaderVersionList> liteloaderlist();
|
||||||
std::shared_ptr<JavaInstallList> javalist();
|
std::shared_ptr<JavaInstallList> javalist();
|
||||||
|
|
||||||
// APPLICATION ONLY
|
|
||||||
std::shared_ptr<InstanceList> instances() const
|
std::shared_ptr<InstanceList> instances() const
|
||||||
{
|
{
|
||||||
return m_instances;
|
return m_instances;
|
||||||
@ -102,46 +105,44 @@ public:
|
|||||||
return m_icons;
|
return m_icons;
|
||||||
}
|
}
|
||||||
|
|
||||||
// APPLICATION ONLY
|
|
||||||
std::shared_ptr<MojangAccountList> accounts() const
|
std::shared_ptr<MojangAccountList> accounts() const
|
||||||
{
|
{
|
||||||
return m_accounts;
|
return m_accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// APPLICATION ONLY
|
|
||||||
Status status() const
|
Status status() const
|
||||||
{
|
{
|
||||||
return m_status;
|
return m_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
// APPLICATION ONLY
|
|
||||||
const QMap<QString, std::shared_ptr<BaseProfilerFactory>> &profilers() const
|
const QMap<QString, std::shared_ptr<BaseProfilerFactory>> &profilers() const
|
||||||
{
|
{
|
||||||
return m_profilers;
|
return m_profilers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// APPLICATION ONLY
|
|
||||||
const QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> &tools() const
|
const QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> &tools() const
|
||||||
{
|
{
|
||||||
return m_tools;
|
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);
|
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
|
* specified in the settings
|
||||||
*/
|
*/
|
||||||
bool openJsonEditor(const QString &filename);
|
bool openJsonEditor(const QString &filename);
|
||||||
|
|
||||||
protected: /* to be removed! */
|
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
|
||||||
// FIXME: remove. used by MainWindow to create application update tasks
|
MainWindow *showMainWindow();
|
||||||
/// this is the root of the 'installation'. Used for automatic updates
|
void launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
|
||||||
const QString &root()
|
|
||||||
{
|
|
||||||
return rootPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
/**
|
/**
|
||||||
@ -149,6 +150,10 @@ private slots:
|
|||||||
*/
|
*/
|
||||||
void onExit();
|
void onExit();
|
||||||
|
|
||||||
|
void on_windowClose();
|
||||||
|
|
||||||
|
void messageReceived(const QString & message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initLogger();
|
void initLogger();
|
||||||
void initIcons();
|
void initIcons();
|
||||||
@ -160,6 +165,7 @@ private:
|
|||||||
private:
|
private:
|
||||||
QDateTime startTime;
|
QDateTime startTime;
|
||||||
|
|
||||||
|
unique_qobject_ptr<LaunchController> m_launchController;
|
||||||
std::shared_ptr<QTranslator> m_qt_translator;
|
std::shared_ptr<QTranslator> m_qt_translator;
|
||||||
std::shared_ptr<QTranslator> m_mmc_translator;
|
std::shared_ptr<QTranslator> m_mmc_translator;
|
||||||
std::shared_ptr<SettingsObject> m_settings;
|
std::shared_ptr<SettingsObject> m_settings;
|
||||||
@ -180,12 +186,19 @@ private:
|
|||||||
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
|
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
|
||||||
QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> m_tools;
|
QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> m_tools;
|
||||||
|
|
||||||
QString rootPath;
|
QString m_rootPath;
|
||||||
|
|
||||||
Status m_status = MultiMC::Failed;
|
Status m_status = MultiMC::Failed;
|
||||||
|
|
||||||
|
// used on Windows to attach the standard IO streams
|
||||||
bool consoleAttached = false;
|
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:
|
public:
|
||||||
QString instanceIdToLaunch;
|
QString m_instanceIdToLaunch;
|
||||||
std::unique_ptr<QFile> logFile;
|
std::unique_ptr<QFile> logFile;
|
||||||
};
|
};
|
||||||
|
@ -4,41 +4,6 @@
|
|||||||
#include <InstanceList.h>
|
#include <InstanceList.h>
|
||||||
#include <QDebug>
|
#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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
// initialize Qt
|
// initialize Qt
|
||||||
@ -59,7 +24,7 @@ int main(int argc, char *argv[])
|
|||||||
switch (app.status())
|
switch (app.status())
|
||||||
{
|
{
|
||||||
case MultiMC::Initialized:
|
case MultiMC::Initialized:
|
||||||
return main_gui(app);
|
return app.exec();
|
||||||
case MultiMC::Failed:
|
case MultiMC::Failed:
|
||||||
return 1;
|
return 1;
|
||||||
case MultiMC::Succeeded:
|
case MultiMC::Succeeded:
|
||||||
|
29
libraries/LocalPeer/CMakeLists.txt
Normal file
29
libraries/LocalPeer/CMakeLists.txt
Normal file
@ -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)
|
97
libraries/LocalPeer/include/LocalPeer.h
Normal file
97
libraries/LocalPeer/include/LocalPeer.h
Normal file
@ -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;
|
||||||
|
};
|
236
libraries/LocalPeer/src/LocalPeer.cpp
Normal file
236
libraries/LocalPeer/src/LocalPeer.cpp
Normal file
@ -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)
|
||||||
|
}
|
193
libraries/LocalPeer/src/LockedFile.cpp
Normal file
193
libraries/LocalPeer/src/LockedFile.cpp
Normal file
@ -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.
|
||||||
|
*/
|
77
libraries/LocalPeer/src/LockedFile.h
Normal file
77
libraries/LocalPeer/src/LockedFile.h
Normal file
@ -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;
|
||||||
|
};
|
114
libraries/LocalPeer/src/LockedFile_unix.cpp
Normal file
114
libraries/LocalPeer/src/LockedFile_unix.cpp
Normal file
@ -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();
|
||||||
|
}
|
205
libraries/LocalPeer/src/LockedFile_win.cpp
Normal file
205
libraries/LocalPeer/src/LockedFile_win.cpp
Normal file
@ -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);
|
||||||
|
}
|
@ -143,3 +143,10 @@ Available either under LGPL version 2.1 or later.
|
|||||||
Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth.
|
Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth.
|
||||||
|
|
||||||
Public domain.
|
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.
|
||||||
|
Loading…
Reference in New Issue
Block a user