diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2ed91037be..74307ea673 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -104,6 +104,7 @@ #include #include "AudioClient.h" +#include "CrashHandler.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" #include "LODManager.h" @@ -262,6 +263,12 @@ bool setupEssentials(int& argc, char** argv) { // Set build version QCoreApplication::setApplicationVersion(BUILD_VERSION); + Setting::preInit(); + + CrashHandler::checkForAndHandleCrash(); + CrashHandler::writeRunningMarkerFiler(); + qAddPostRoutine(CrashHandler::deleteRunningMarkerFile); + DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); @@ -5036,3 +5043,9 @@ void Application::emulateMouse(Hand* hand, float click, float shift, int index) _oldHandLeftClick[index] = false; } } + +void Application::crashApplication() { + QObject* object = nullptr; + bool value = object->isWindowType(); + qCDebug(interfaceapp) << "Intentionally crashed Interface"; +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 7e4e340eee..9b819eae53 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -433,6 +433,8 @@ public slots: void reloadResourceCaches(); + void crashApplication(); + private slots: void clearDomainOctreeDetails(); void checkFPS(); diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp new file mode 100644 index 0000000000..ce5facb580 --- /dev/null +++ b/interface/src/CrashHandler.cpp @@ -0,0 +1,183 @@ +// +// CrashHandler.cpp +// interface/src +// +// Created by David Rowe on 24 Aug 2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CrashHandler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DataServerAccountInfo.h" +#include "Menu.h" + +Q_DECLARE_METATYPE(DataServerAccountInfo) + +static const QString RUNNING_MARKER_FILENAME = "Interface.running"; + +void CrashHandler::checkForAndHandleCrash() { + QFile runningMarkerFile(runningMarkerFilePath()); + if (runningMarkerFile.exists()) { + QSettings::setDefaultFormat(QSettings::IniFormat); + QSettings settings; + settings.beginGroup("Developer"); + QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions); + settings.endGroup(); + if (!displayCrashOptions.isValid() // Option does not exist in Interface.ini so assume default behavior. + || displayCrashOptions.toBool()) { + Action action = promptUserForAction(); + if (action != DO_NOTHING) { + handleCrash(action); + } + } + } +} + +CrashHandler::Action CrashHandler::promptUserForAction() { + QDialog crashDialog; + crashDialog.setWindowTitle("Interface Crashed Last Run"); + + QVBoxLayout* layout = new QVBoxLayout; + + QLabel* label = new QLabel("If you are having trouble starting would you like to reset your settings?"); + layout->addWidget(label); + + QRadioButton* option1 = new QRadioButton("Reset all my settings"); + QRadioButton* option2 = new QRadioButton("Reset my settings but retain login and avatar info."); + QRadioButton* option3 = new QRadioButton("Continue with my current settings"); + option3->setChecked(true); + layout->addWidget(option1); + layout->addWidget(option2); + layout->addWidget(option3); + layout->addSpacing(12); + layout->addStretch(); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); + layout->addWidget(buttons); + crashDialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); + + crashDialog.setLayout(layout); + + int result = crashDialog.exec(); + + if (result == QDialog::Accepted) { + if (option1->isChecked()) { + return CrashHandler::DELETE_INTERFACE_INI; + } + if (option2->isChecked()) { + return CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO; + } + } + + // Dialog cancelled or "do nothing" option chosen + return CrashHandler::DO_NOTHING; +} + +void CrashHandler::handleCrash(CrashHandler::Action action) { + if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { + // CrashHandler::DO_NOTHING or unexpected value + return; + } + + QSettings::setDefaultFormat(QSettings::IniFormat); + QSettings settings; + const QString ADDRESS_MANAGER_GROUP = "AddressManager"; + const QString ADDRESS_KEY = "address"; + const QString AVATAR_GROUP = "Avatar"; + const QString DISPLAY_NAME_KEY = "displayName"; + const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; + const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; + const QString ACCOUNTS_GROUP = "accounts"; + QString displayName; + QUrl fullAvatarURL; + QString fullAvatarModelName; + QUrl address; + QMap accounts; + + if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { + // Read login and avatar info + + qRegisterMetaType("DataServerAccountInfo"); + qRegisterMetaTypeStreamOperators("DataServerAccountInfo"); + + // Location and orientation + settings.beginGroup(ADDRESS_MANAGER_GROUP); + address = settings.value(ADDRESS_KEY).toUrl(); + settings.endGroup(); + + // Display name and avatar + settings.beginGroup(AVATAR_GROUP); + displayName = settings.value(DISPLAY_NAME_KEY).toString(); + fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); + fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); + settings.endGroup(); + + // Accounts + settings.beginGroup(ACCOUNTS_GROUP); + foreach(const QString& key, settings.allKeys()) { + accounts.insert(key, settings.value(key).value()); + } + settings.endGroup(); + } + + // Delete Interface.ini + QFile settingsFile(settings.fileName()); + if (settingsFile.exists()) { + settingsFile.remove(); + } + + if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { + // Write login and avatar info + + // Location and orientation + settings.beginGroup(ADDRESS_MANAGER_GROUP); + settings.setValue(ADDRESS_KEY, address); + settings.endGroup(); + + // Display name and avatar + settings.beginGroup(AVATAR_GROUP); + settings.setValue(DISPLAY_NAME_KEY, displayName); + settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); + settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); + settings.endGroup(); + + // Accounts + settings.beginGroup(ACCOUNTS_GROUP); + foreach(const QString& key, accounts.keys()) { + settings.setValue(key, QVariant::fromValue(accounts.value(key))); + } + settings.endGroup(); + } +} + +void CrashHandler::writeRunningMarkerFiler() { + QFile runningMarkerFile(runningMarkerFilePath()); + if (!runningMarkerFile.exists()) { + runningMarkerFile.open(QIODevice::WriteOnly); + runningMarkerFile.close(); + } +} +void CrashHandler::deleteRunningMarkerFile() { + QFile runningMarkerFile(runningMarkerFilePath()); + if (runningMarkerFile.exists()) { + runningMarkerFile.remove(); + } +} + +const QString CrashHandler::runningMarkerFilePath() { + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + RUNNING_MARKER_FILENAME; +} diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h new file mode 100644 index 0000000000..fc754cf1ac --- /dev/null +++ b/interface/src/CrashHandler.h @@ -0,0 +1,38 @@ +// +// CrashHandler.h +// interface/src +// +// Created by David Rowe on 24 Aug 2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CrashHandler_h +#define hifi_CrashHandler_h + +#include + +class CrashHandler { + +public: + static void checkForAndHandleCrash(); + + static void writeRunningMarkerFiler(); + static void deleteRunningMarkerFile(); + +private: + enum Action { + DELETE_INTERFACE_INI, + RETAIN_LOGIN_AND_AVATAR_INFO, + DO_NOTHING + }; + + static Action promptUserForAction(); + static void handleCrash(Action action); + + static const QString runningMarkerFilePath(); +}; + +#endif // hifi_CrashHandler_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index b756236fe0..17e64b5765 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -560,6 +560,9 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls); + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); + addActionToQMenuAndActionHash(developerMenu, MenuOption::CrashInterface, 0, qApp, SLOT(crashApplication())); + MenuWrapper* helpMenu = addMenu("Help"); addActionToQMenuAndActionHash(helpMenu, MenuOption::EditEntitiesHelp, 0, qApp, SLOT(showEditEntitiesHelp())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 98b3fe9892..9bcfd1f7aa 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -164,6 +164,7 @@ namespace MenuOption { const QString CopyAddress = "Copy Address to Clipboard"; const QString CopyPath = "Copy Path to Clipboard"; const QString CoupleEyelids = "Couple Eyelids"; + const QString CrashInterface = "Crash Interface"; const QString DebugAmbientOcclusion = "Debug Ambient Occlusion"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; @@ -172,6 +173,7 @@ namespace MenuOption { const QString DisableLightEntities = "Disable Light Entities"; const QString DisableNackPackets = "Disable Entity NACK Packets"; const QString DiskCacheEditor = "Disk Cache Editor"; + const QString DisplayCrashOptions = "Display Crash Options"; const QString DisplayHands = "Show Hand Info"; const QString DisplayHandTargets = "Show Hand Targets"; const QString DisplayModelBounds = "Display Model Bounds"; diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index b60ffc0891..11ed64cac4 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -35,9 +35,9 @@ namespace Setting { settingsManagerThread->quit(); settingsManagerThread->wait(); } - - // Sets up the settings private instance. Should only be run once at startup - void init() { + + // Set up application settings. Should only be run once at startup. + void preInit() { // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings::setDefaultFormat(QSettings::IniFormat); QSettings applicationInfo(PathUtils::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -46,7 +46,19 @@ namespace Setting { QCoreApplication::setApplicationName(applicationInfo.value("name").toString()); QCoreApplication::setOrganizationName(applicationInfo.value("organizationName").toString()); QCoreApplication::setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); - + + // Delete Interface.ini.lock file if it exists, otherwise Interface freezes. + QSettings settings; + QString settingsLockFilename = settings.fileName() + ".lock"; + QFile settingsLockFile(settingsLockFilename); + if (settingsLockFile.exists()) { + bool deleted = settingsLockFile.remove(); + qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; + } + } + + // Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand, + void init() { // Let's set up the settings Private instance on its own thread QThread* thread = new QThread(); Q_CHECK_PTR(thread); @@ -55,14 +67,6 @@ namespace Setting { privateInstance = new Manager(); Q_CHECK_PTR(privateInstance); - // Delete Interface.ini.lock file if it exists, otherwise Interface freezes. - QString settingsLockFilename = privateInstance->fileName() + ".lock"; - QFile settingsLockFile(settingsLockFilename); - if (settingsLockFile.exists()) { - bool deleted = settingsLockFile.remove(); - qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; - } - QObject::connect(privateInstance, SIGNAL(destroyed()), thread, SLOT(quit())); QObject::connect(thread, SIGNAL(started()), privateInstance, SLOT(startTimer())); QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); diff --git a/libraries/shared/src/SettingInterface.h b/libraries/shared/src/SettingInterface.h index 5092fd09c8..c8b1595a75 100644 --- a/libraries/shared/src/SettingInterface.h +++ b/libraries/shared/src/SettingInterface.h @@ -16,6 +16,7 @@ #include namespace Setting { + void preInit(); void init(); void cleanupSettings();