| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | #include <FCConfig.h>
|
| |
|
| | #if defined(_MSC_VER)
|
| | # include <windows.h>
|
| | # include <dbghelp.h>
|
| | #endif
|
| |
|
| | #if HAVE_CONFIG_H
|
| | # include <config.h>
|
| | #endif
|
| |
|
| | #include <cstdio>
|
| | #include <map>
|
| | #include <stdexcept>
|
| |
|
| | #include <QApplication>
|
| | #include <QLocale>
|
| | #include <QMessageBox>
|
| |
|
| |
|
| | #include <App/Application.h>
|
| | #include <Base/ConsoleObserver.h>
|
| | #include <Base/Interpreter.h>
|
| | #include <Base/Parameter.h>
|
| | #include <Base/Exception.h>
|
| | #include <Gui/Application.h>
|
| |
|
| |
|
| | void PrintInitHelp();
|
| |
|
| | const char sBanner[]
|
| | = "(C) 2001-2025 FreeCAD contributors\n"
|
| | "FreeCAD is free and open-source software licensed under the terms of LGPL2+ license.\n\n";
|
| |
|
| | #if defined(_MSC_VER)
|
| | void InitMiniDumpWriter(const std::string&);
|
| | #endif
|
| |
|
| | class Redirection
|
| | {
|
| | public:
|
| | Redirection(FILE* f)
|
| | : fi(Base::FileInfo::getTempFileName())
|
| | , file(f)
|
| | {
|
| | #ifdef WIN32
|
| | FILE* ptr = _wfreopen(fi.toStdWString().c_str(), L"w", file);
|
| | #else
|
| | FILE* ptr = freopen(fi.filePath().c_str(), "w", file);
|
| | #endif
|
| | if (!ptr) {
|
| | std::cerr << "Failed to reopen file" << std::endl;
|
| | }
|
| | }
|
| | ~Redirection()
|
| | {
|
| | fclose(file);
|
| | fi.deleteFile();
|
| | }
|
| |
|
| | private:
|
| | Base::FileInfo fi;
|
| | FILE* file;
|
| | };
|
| |
|
| | static bool inGuiMode()
|
| | {
|
| |
|
| | if (App::Application::Config()["Console"] == "1") {
|
| | return false;
|
| | }
|
| | return App::Application::Config()["RunMode"] == "Gui"
|
| | || App::Application::Config()["RunMode"] == "Internal";
|
| | }
|
| |
|
| | static void displayInfo(const QString& msg, bool preformatted = true)
|
| | {
|
| | if (inGuiMode()) {
|
| | QString appName = QString::fromStdString(App::Application::Config()["ExeName"]);
|
| | QMessageBox msgBox;
|
| | msgBox.setIcon(QMessageBox::Information);
|
| | msgBox.setWindowTitle(appName);
|
| | msgBox.setDetailedText(msg);
|
| | msgBox.setText(preformatted ? QStringLiteral("<pre>%1</pre>").arg(msg) : msg);
|
| | msgBox.exec();
|
| | }
|
| | else {
|
| | std::cout << msg.toStdString();
|
| | }
|
| | }
|
| |
|
| | static void displayCritical(const QString& msg, bool preformatted = true)
|
| | {
|
| | if (inGuiMode()) {
|
| | QString appName = QString::fromStdString(App::Application::Config()["ExeName"]);
|
| | QString title = QObject::tr("Initialization of %1 failed").arg(appName);
|
| | QString text = preformatted ? QStringLiteral("<pre>%1</pre>").arg(msg) : msg;
|
| | QMessageBox::critical(nullptr, title, text);
|
| | }
|
| | else {
|
| | std::cerr << msg.toStdString();
|
| | }
|
| | }
|
| |
|
| | int main(int argc, char** argv)
|
| | {
|
| | #if defined(FC_OS_LINUX) || defined(FC_OS_BSD)
|
| | setlocale(LC_ALL, "");
|
| | setlocale(LC_NUMERIC, "C");
|
| |
|
| |
|
| |
|
| |
|
| | (void)QLocale::system();
|
| |
|
| |
|
| | putenv("LC_NUMERIC=C");
|
| | putenv("PYTHONPATH=");
|
| | #elif defined(FC_OS_MACOSX)
|
| | (void)QLocale::system();
|
| | putenv("PYTHONPATH=");
|
| | #elif defined(__MINGW32__)
|
| | const char* mingw_prefix = getenv("MINGW_PREFIX");
|
| | const char* py_home = getenv("PYTHONHOME");
|
| | if (!py_home && mingw_prefix) {
|
| | _putenv_s("PYTHONHOME", mingw_prefix);
|
| | }
|
| | #else
|
| | _putenv("PYTHONPATH=");
|
| |
|
| |
|
| | const char* fc_py_home = getenv("FC_PYTHONHOME");
|
| | if (fc_py_home) {
|
| | _putenv_s("PYTHONHOME", fc_py_home);
|
| | }
|
| | else {
|
| | _putenv("PYTHONHOME=");
|
| | }
|
| | #endif
|
| |
|
| | #if defined(FC_OS_WIN32)
|
| |
|
| |
|
| | _putenv("COIN_FORCE_FREETYPE_OFF=1");
|
| |
|
| | int argc_ = argc;
|
| | QVector<QByteArray> data;
|
| | QVector<char*> argv_;
|
| |
|
| |
|
| | {
|
| | QCoreApplication app(argc, argv);
|
| | QStringList args = app.arguments();
|
| | for (QStringList::iterator it = args.begin(); it != args.end(); ++it) {
|
| | data.push_back(it->toUtf8());
|
| | argv_.push_back(data.back().data());
|
| | }
|
| | argv_.push_back(0);
|
| | }
|
| | #endif
|
| |
|
| |
|
| | App::Application::Config()["ExeName"] = "FreeCAD";
|
| | App::Application::Config()["ExeVendor"] = "FreeCAD";
|
| | App::Application::Config()["AppDataSkipVendor"] = "true";
|
| | App::Application::Config()["MaintainerUrl"] = "https://freecad.org";
|
| |
|
| |
|
| | App::Application::Config()["CopyrightInfo"] = sBanner;
|
| | App::Application::Config()["AppIcon"] = "freecad";
|
| | App::Application::Config()["SplashScreen"] = "freecadsplash";
|
| | App::Application::Config()["AboutImage"] = App::Application::isDevelopmentVersion()
|
| | ? "freecadaboutdev"
|
| | : "freecadabout";
|
| | App::Application::Config()["StartWorkbench"] = "PartDesignWorkbench";
|
| |
|
| | App::Application::Config()["SplashAlignment"] = "Bottom|Left";
|
| | App::Application::Config()["SplashTextColor"] = "#418FDE";
|
| | App::Application::Config()["SplashWarningColor"] = "#CA333B";
|
| | App::Application::Config()["SplashInfoColor"] = "#000000";
|
| | App::Application::Config()["SplashInfoPosition"] = "6,75";
|
| | App::Application::Config()["DesktopFileName"] = "org.freecad.FreeCAD";
|
| |
|
| | try {
|
| |
|
| |
|
| | App::Application::Config()["RunMode"] = "Gui";
|
| | App::Application::Config()["Console"] = "0";
|
| | App::Application::Config()["LoggingConsole"] = "1";
|
| |
|
| |
|
| | #if defined(FC_OS_WIN32)
|
| | App::Application::init(argc_, argv_.data());
|
| | #else
|
| | App::Application::init(argc, argv);
|
| | #endif
|
| |
|
| | QGuiApplication::setDesktopFileName(
|
| | QString::fromStdString(App::Application::Config()["DesktopFileName"])
|
| | );
|
| |
|
| | #if defined(_MSC_VER)
|
| |
|
| | std::string dmpfile = App::Application::getUserAppDataDir();
|
| | dmpfile += "crash.dmp";
|
| | InitMiniDumpWriter(dmpfile);
|
| | #endif
|
| | std::map<std::string, std::string>::iterator it = App::Application::Config().find(
|
| | "NavigationStyle"
|
| | );
|
| | if (it != App::Application::Config().end()) {
|
| | ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
| | "User parameter:BaseApp/Preferences/View"
|
| | );
|
| |
|
| | std::string style = hGrp->GetASCII("NavigationStyle", it->second.c_str());
|
| | hGrp->SetASCII("NavigationStyle", style.c_str());
|
| | }
|
| |
|
| | Gui::Application::initApplication();
|
| |
|
| |
|
| | if (App::Application::Config()["RunMode"] == "Gui") {
|
| | Base::Interpreter().replaceStdOutput();
|
| | }
|
| | }
|
| | catch (const Base::UnknownProgramOption& e) {
|
| | QApplication app(argc, argv);
|
| | QString msg = QString::fromLatin1(e.what());
|
| | displayCritical(msg);
|
| | exit(1);
|
| | }
|
| | catch (const Base::ProgramInformation& e) {
|
| | QApplication app(argc, argv);
|
| | QString msg = QString::fromUtf8(e.what());
|
| | if (msg == QLatin1String(App::Application::verboseVersionEmitMessage)) {
|
| | QString data;
|
| | QTextStream str(&data);
|
| | const std::map<std::string, std::string> config = App::Application::Config();
|
| |
|
| | App::Application::getVerboseCommonInfo(str, config);
|
| | Gui::Application::getVerboseDPIStyleInfo(str);
|
| | App::Application::getVerboseAddOnsInfo(str, config);
|
| |
|
| | msg = data;
|
| | }
|
| | displayInfo(msg);
|
| | exit(0);
|
| | }
|
| | catch (const Base::Exception& e) {
|
| |
|
| | QApplication app(argc, argv);
|
| | QString appName = QString::fromStdString(App::Application::Config()["ExeName"]);
|
| | QString msg;
|
| | msg = QObject::tr(
|
| | "While initializing %1 the following exception occurred: '%2'\n\n"
|
| | "Python is searching for its files in the following directories:\n%3\n\n"
|
| | "Python version information:\n%4\n"
|
| | )
|
| | .arg(
|
| | appName,
|
| | QString::fromUtf8(e.what()),
|
| | QString::fromStdString(Base::Interpreter().getPythonPath()),
|
| | QString::fromLatin1(Py_GetVersion())
|
| | );
|
| | const char* pythonhome = getenv("PYTHONHOME");
|
| | if (pythonhome) {
|
| | msg += QObject::tr("\nThe environment variable PYTHONHOME is set to '%1'.")
|
| | .arg(QString::fromUtf8(pythonhome));
|
| | msg += QObject::tr(
|
| | "\nSetting this environment variable might cause Python to fail. "
|
| | "Please contact your administrator to unset it on your system.\n\n"
|
| | );
|
| | }
|
| | else {
|
| | msg += QObject::tr(
|
| | "\nPlease contact the application's support team for more information.\n\n"
|
| | );
|
| | }
|
| |
|
| | displayCritical(msg, false);
|
| | exit(100);
|
| | }
|
| | catch (...) {
|
| |
|
| | QApplication app(argc, argv);
|
| | QString appName = QString::fromStdString(App::Application::Config()["ExeName"]);
|
| | QString msg = QObject::tr(
|
| | "Unknown runtime error occurred while initializing %1.\n\n"
|
| | "Please contact the application's support team for more information.\n\n"
|
| | )
|
| | .arg(appName);
|
| | displayCritical(msg, false);
|
| | exit(101);
|
| | }
|
| |
|
| |
|
| | Base::RedirectStdOutput stdcout;
|
| | Base::RedirectStdLog stdclog;
|
| | Base::RedirectStdError stdcerr;
|
| | std::streambuf* oldcout = std::cout.rdbuf(&stdcout);
|
| | std::streambuf* oldclog = std::clog.rdbuf(&stdclog);
|
| | std::streambuf* oldcerr = std::cerr.rdbuf(&stdcerr);
|
| |
|
| | try {
|
| | if (inGuiMode()) {
|
| | Gui::Application::runApplication();
|
| | }
|
| | else {
|
| | App::Application::runApplication();
|
| | }
|
| | }
|
| | catch (const Base::SystemExitException& e) {
|
| | exit(e.getExitCode());
|
| | }
|
| | catch (const Base::Exception& e) {
|
| | e.reportException();
|
| | exit(1);
|
| | }
|
| | catch (const std::exception& e) {
|
| | Base::Console().error("Application unexpectedly terminated: %s\n", e.what());
|
| | exit(1);
|
| | }
|
| | catch (...) {
|
| | Base::Console().error("Application unexpectedly terminated\n");
|
| | exit(1);
|
| | }
|
| |
|
| | std::cout.rdbuf(oldcout);
|
| | std::clog.rdbuf(oldclog);
|
| | std::cerr.rdbuf(oldcerr);
|
| |
|
| |
|
| | Base::Console().log("%s terminating...\n", App::Application::Config()["ExeName"].c_str());
|
| |
|
| |
|
| | App::Application::destruct();
|
| |
|
| | Base::Console().log("%s completely terminated\n", App::Application::Config()["ExeName"].c_str());
|
| |
|
| | return 0;
|
| | }
|
| |
|
| | #if defined(_MSC_VER)
|
| |
|
| | typedef BOOL(__stdcall* tMDWD)(
|
| | IN HANDLE hProcess,
|
| | IN DWORD ProcessId,
|
| | IN HANDLE hFile,
|
| | IN MINIDUMP_TYPE DumpType,
|
| | IN CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
|
| | OPTIONAL IN CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
|
| | OPTIONAL IN CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam OPTIONAL
|
| | );
|
| |
|
| | static tMDWD s_pMDWD;
|
| | static HMODULE s_hDbgHelpMod;
|
| | static MINIDUMP_TYPE s_dumpTyp = MiniDumpNormal;
|
| | static std::wstring s_szMiniDumpFileName;
|
| |
|
| | # include <Base/StackWalker.h>
|
| | class MyStackWalker: public StackWalker
|
| | {
|
| | DWORD threadId;
|
| |
|
| | public:
|
| | MyStackWalker()
|
| | : StackWalker()
|
| | , threadId(GetCurrentThreadId())
|
| | {
|
| | std::string name = App::Application::Config()["UserAppData"] + "crash.log";
|
| | Base::Console().attachObserver(new Base::ConsoleObserverFile(name.c_str()));
|
| | }
|
| | MyStackWalker(DWORD dwProcessId, HANDLE hProcess)
|
| | : StackWalker(dwProcessId, hProcess)
|
| | {}
|
| | virtual void OnOutput(LPCSTR szText)
|
| | {
|
| | Base::Console().log("Id: %ld: %s", threadId, szText);
|
| |
|
| | }
|
| | };
|
| |
|
| | static LONG __stdcall MyCrashHandlerExceptionFilter(EXCEPTION_POINTERS* pEx)
|
| | {
|
| | # ifdef _M_IX86
|
| | if (pEx->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
|
| |
|
| | static char MyStack[1024 * 128];
|
| |
|
| |
|
| |
|
| |
|
| | __asm mov eax, offset MyStack[1024 * 128];
|
| | __asm mov esp, eax;
|
| | }
|
| | # endif
|
| | MyStackWalker sw;
|
| | sw.ShowCallstack(GetCurrentThread(), pEx->ContextRecord);
|
| | Base::Console().log("*** Unhandled Exception!\n");
|
| | Base::Console().log(" ExpCode: 0x%8.8X\n", pEx->ExceptionRecord->ExceptionCode);
|
| | Base::Console().log(" ExpFlags: %d\n", pEx->ExceptionRecord->ExceptionFlags);
|
| | Base::Console().log(" ExpAddress: 0x%8.8X\n", pEx->ExceptionRecord->ExceptionAddress);
|
| |
|
| | bool bFailed = true;
|
| | HANDLE hFile;
|
| | hFile = CreateFileW(
|
| | s_szMiniDumpFileName.c_str(),
|
| | GENERIC_WRITE,
|
| | 0,
|
| | NULL,
|
| | CREATE_ALWAYS,
|
| | FILE_ATTRIBUTE_NORMAL,
|
| | NULL
|
| | );
|
| | if (hFile != INVALID_HANDLE_VALUE) {
|
| | MINIDUMP_EXCEPTION_INFORMATION stMDEI;
|
| | stMDEI.ThreadId = GetCurrentThreadId();
|
| | stMDEI.ExceptionPointers = pEx;
|
| | stMDEI.ClientPointers = true;
|
| |
|
| | if (s_pMDWD(GetCurrentProcess(), GetCurrentProcessId(), hFile, s_dumpTyp, &stMDEI, NULL, NULL)) {
|
| | bFailed = false;
|
| | }
|
| | CloseHandle(hFile);
|
| | }
|
| |
|
| | if (bFailed) {
|
| | return EXCEPTION_CONTINUE_SEARCH;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | return EXCEPTION_CONTINUE_SEARCH;
|
| | }
|
| |
|
| | void InitMiniDumpWriter(const std::string& filename)
|
| | {
|
| | if (s_hDbgHelpMod != NULL) {
|
| | return;
|
| | }
|
| | Base::FileInfo fi(filename);
|
| | s_szMiniDumpFileName = fi.toStdWString();
|
| |
|
| |
|
| |
|
| | s_hDbgHelpMod = LoadLibraryA(("dbghelp.dll"));
|
| | if (s_hDbgHelpMod != NULL) {
|
| | s_pMDWD = (tMDWD)GetProcAddress(s_hDbgHelpMod, "MiniDumpWriteDump");
|
| | }
|
| |
|
| |
|
| | SetUnhandledExceptionFilter(MyCrashHandlerExceptionFilter);
|
| |
|
| |
|
| |
|
| |
|
| | }
|
| | #endif
|
| |
|