From 4eb7d5d09c2880b8c09cbc43e69daa3ade6b20f7 Mon Sep 17 00:00:00 2001 From: Benjamin Nauck Date: Mon, 21 Oct 2024 18:08:52 +0200 Subject: [PATCH] Add "Safe mode" which temporarily disables configs and addons (#16858) * Add safe-mode which starts freecad in a clean environment * Integrate safe-mode * Show "(Safe Mode)" in main window title * Show safe mode info when safe mode is started * Created Safe Mode Icon * Use new icon * Add PreCompiled.h * Add exports for windows --------- Co-authored-by: Gabriel --- src/App/Application.cpp | 8 ++ src/App/CMakeLists.txt | 1 + src/App/SafeMode.cpp | 79 +++++++++++ src/App/SafeMode.h | 30 +++++ src/Gui/CommandStd.cpp | 51 +++++++ src/Gui/Icons/resource.qrc | 2 + src/Gui/Icons/safe-mode-restart.svg | 200 ++++++++++++++++++++++++++++ src/Gui/MainWindow.cpp | 21 +++ src/Gui/Workbench.cpp | 3 +- 9 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 src/App/SafeMode.cpp create mode 100644 src/App/SafeMode.h create mode 100644 src/Gui/Icons/safe-mode-restart.svg diff --git a/src/App/Application.cpp b/src/App/Application.cpp index e348c19662..4eac6b9888 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -137,6 +137,8 @@ #include #include +#include "SafeMode.h" + #ifdef _MSC_VER // New handler for Microsoft Visual C++ compiler # pragma warning( disable : 4535 ) # if !defined(_DEBUG) && defined(HAVE_SEH) @@ -1742,6 +1744,7 @@ void Application::destruct() Base::InterpreterSingleton::Destruct(); Base::Type::destruct(); ParameterManager::Terminate(); + SafeMode::Destruct(); } void Application::destructObserver() @@ -2230,6 +2233,7 @@ void parseProgramOptions(int ac, char ** av, const string& exe, variables_map& v ("module-path,M", value< vector >()->composing(),"Additional module paths") ("python-path,P", value< vector >()->composing(),"Additional python paths") ("single-instance", "Allow to run a single instance of the application") + ("safe-mode", "Force enable safe mode") ("pass", value< vector >()->multitoken(), "Ignores the following arguments and pass them through to be used by a script") ; @@ -2557,6 +2561,10 @@ void Application::initConfig(int argc, char ** argv) // extract home paths ExtractUserPath(); + + if (vm.count("safe-mode")) { + SafeMode::StartSafeMode(); + } # ifdef FC_DEBUG mConfig["Debug"] = "1"; diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index 52c0c85968..d343b297c3 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -288,6 +288,7 @@ SET(FreeCADApp_CPP_SRCS Metadata.cpp MetadataPyImp.cpp ElementNamingUtils.cpp + SafeMode.cpp StringHasher.cpp StringHasherPyImp.cpp StringIDPyImp.cpp diff --git a/src/App/SafeMode.cpp b/src/App/SafeMode.cpp new file mode 100644 index 0000000000..63fbd7cd90 --- /dev/null +++ b/src/App/SafeMode.cpp @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (c) 2024 Benjamin Nauck * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License (LGPL) * + * as published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * for detail see the LICENCE text file. * + * * + * FreeCAD is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with FreeCAD; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#include + +#include "Application.h" +#include "FCConfig.h" + +#include "SafeMode.h" + +static QTemporaryDir * tempDir = nullptr; + +static bool _createTemporaryBaseDir() { + tempDir = new QTemporaryDir(); + if (!tempDir->isValid()) { + delete tempDir; + tempDir = nullptr; + } + return tempDir; +} + +static void _replaceDirs() +{ + auto &config = App::GetApplication().Config(); + + auto const temp_base = tempDir->path().toStdString(); + auto const dirs = { + "UserAppData", + "UserConfigPath", + "UserCachePath", + "AppTempPath", + "UserMacroPath", + "UserHomePath", + }; + + for (auto const d : dirs) { + auto const path = temp_base + PATHSEP + d + PATHSEP; + auto const qpath = QString::fromStdString(path); + QDir().mkpath(qpath); + config[d] = path; + } +} + +void SafeMode::StartSafeMode() +{ + if (_createTemporaryBaseDir()) { + _replaceDirs(); + } +} + +bool SafeMode::SafeModeEnabled() { + return tempDir; +} + +void SafeMode::Destruct() { + delete tempDir; +} diff --git a/src/App/SafeMode.h b/src/App/SafeMode.h new file mode 100644 index 0000000000..95a3749c3d --- /dev/null +++ b/src/App/SafeMode.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (c) 2024 Benjamin Nauck * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License (LGPL) * + * as published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * for detail see the LICENCE text file. * + * * + * FreeCAD is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with FreeCAD; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * USA * + * * + ***************************************************************************/ + +#pragma once + +namespace SafeMode { + AppExport bool SafeModeEnabled(); + AppExport void StartSafeMode(); + AppExport void Destruct(); +}; diff --git a/src/Gui/CommandStd.cpp b/src/Gui/CommandStd.cpp index 36215ab2ea..a6104093ea 100644 --- a/src/Gui/CommandStd.cpp +++ b/src/Gui/CommandStd.cpp @@ -29,6 +29,9 @@ # include # include # include +# include +# include +# include #endif #include @@ -319,6 +322,53 @@ void StdCmdWhatsThis::activated(int iMsg) QWhatsThis::enterWhatsThisMode(); } +//=========================================================================== +// Std_RestartInSafeMode +//=========================================================================== +DEF_STD_CMD(StdCmdRestartInSafeMode) + +StdCmdRestartInSafeMode::StdCmdRestartInSafeMode() + :Command("Std_RestartInSafeMode") +{ + sGroup = "Help"; + sMenuText = QT_TR_NOOP("Restart in safe mode"); + sToolTipText = QT_TR_NOOP("Restart in safe mode"); + sWhatsThis = "Std_RestartInSafeMode"; + sStatusTip = QT_TR_NOOP("Restart in safe mode"); + sPixmap = "safe-mode-restart"; + eType = 0; +} + +void StdCmdRestartInSafeMode::activated(int iMsg) +{ + Q_UNUSED(iMsg); + + QMessageBox restartBox; + restartBox.setIcon(QMessageBox::Warning); + restartBox.setWindowTitle(QObject::tr("Restart in safe mode")); + restartBox.setText(QObject::tr("Are you sure you want to restart FreeCAD and enter safe mode?")); + restartBox.setInformativeText(QObject::tr("Safe mode temporarily disables your configuration and addons.")); + restartBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + restartBox.setDefaultButton(QMessageBox::No); + + if (restartBox.exec() == QMessageBox::Yes) { + //restart FreeCAD after a delay to give time to this dialog to close + const int ms = 1000; + QTimer::singleShot(ms, []() + { + QStringList args = QApplication::arguments(); + args.pop_front(); + auto const safeModeArgument = QString::fromLatin1("--safe-mode"); + if (!args.contains(safeModeArgument)) { + args.append(safeModeArgument); + } + if (getMainWindow()->close()) { + QProcess::startDetached(QApplication::applicationFilePath(), args); + } + }); + } +} + //=========================================================================== // Std_DlgParameter //=========================================================================== @@ -938,6 +988,7 @@ void CreateStdCommands() rcCmdMgr.addCommand(new StdCmdRecentFiles()); rcCmdMgr.addCommand(new StdCmdRecentMacros()); rcCmdMgr.addCommand(new StdCmdWhatsThis()); + rcCmdMgr.addCommand(new StdCmdRestartInSafeMode()); rcCmdMgr.addCommand(new StdCmdPythonHelp()); rcCmdMgr.addCommand(new StdCmdOnlineHelp()); rcCmdMgr.addCommand(new StdCmdOnlineHelpWebsite()); diff --git a/src/Gui/Icons/resource.qrc b/src/Gui/Icons/resource.qrc index ef055822d4..901e5389a3 100644 --- a/src/Gui/Icons/resource.qrc +++ b/src/Gui/Icons/resource.qrc @@ -135,6 +135,7 @@ internet-web-browser.svg InTray.svg InTray_missed_notifications.svg + safe-mode-restart.svg view-select.svg view-unselectable.svg view-refresh.svg @@ -391,6 +392,7 @@ accessories-text-editor.svg accessories-calculator.svg internet-web-browser.svg + safe-mode-restart.svg view-select.svg view-unselectable.svg view-refresh.svg diff --git a/src/Gui/Icons/safe-mode-restart.svg b/src/Gui/Icons/safe-mode-restart.svg new file mode 100644 index 0000000000..ef1e8f2436 --- /dev/null +++ b/src/Gui/Icons/safe-mode-restart.svg @@ -0,0 +1,200 @@ + + + + + Safe Mode Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + + + FreeCAD LGPL2+ + + + + + Gabriel A. Zorril + + + September 26, 2024 + Safe Mode Icon + + + + + + + + + + + diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 7f450b6da1..73d671d747 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -70,6 +70,7 @@ #include #include #include +#include #include #include #include @@ -1654,6 +1655,22 @@ void MainWindow::delayedStartup() if (hGrp->GetBool("RecoveryEnabled", true)) { Application::Instance->checkForPreviousCrashes(); } + + if (SafeMode::SafeModeEnabled()) { + auto safeModePopup = QMessageBox( + QMessageBox::Information, + tr("Safe mode enabled"), + tr("FreeCAD is now running in safe mode."), + QMessageBox::Ok + ); + safeModePopup.setInformativeText( + tr( + "Safe mode temporarily disables your configurations and addons." + " Restart the application to exit safe mode." + ) + ); + safeModePopup.exec(); + } } void MainWindow::appendRecentFile(const QString& filename) @@ -2610,6 +2627,10 @@ void MainWindow::setWindowTitle(const QString& string) title = appname; } + if (SafeMode::SafeModeEnabled()) { + title = QString::fromUtf8("%1 (%2)").arg(title, tr("Safe Mode")); + } + if (!string.isEmpty()) { title = QString::fromUtf8("[*] %1 - %2").arg(string, title); } diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 546bf22503..f5306db0a2 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -761,7 +761,8 @@ MenuItem* StdWorkbench::setupMenuBar() const *help << "Std_OnlineHelp" << "Std_FreeCADWebsite" << "Std_FreeCADDonation" << "Std_FreeCADUserHub" << "Std_FreeCADPowerUserHub" << "Std_PythonHelp" << "Std_FreeCADForum" << "Std_FreeCADFAQ" - << "Std_ReportBug" << "Std_About" << "Std_WhatsThis"; + << "Std_ReportBug" << "Std_About" << "Std_WhatsThis" + << "Std_RestartInSafeMode"; return menuBar; }