diff --git a/src/app/app.cpp b/src/app/app.cpp index 97055a5fb..620d8a851 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -606,7 +606,8 @@ crash::DataRecovery* App::dataRecovery() const #ifdef ENABLE_UI void App::showNotification(INotificationDelegate* del) { - m_mainWindow->showNotification(del); + if (m_mainWindow) + m_mainWindow->showNotification(del); } void App::showBackupNotification(bool state) @@ -709,18 +710,4 @@ int app_get_color_to_clear_layer(Layer* layer) return color_utils::color_for_layer(color, layer); } -std::string memory_dump_filename() -{ -#ifdef _WIN32 - static const char* kDefaultCrashName = PACKAGE "-crash-" VERSION ".dmp"; - - app::ResourceFinder rf; - rf.includeUserDir(kDefaultCrashName); - return rf.getFirstOrCreateDefault(); - -#else - return ""; -#endif -} - } // namespace app diff --git a/src/app/app.h b/src/app/app.h index ea4dce1fa..f255fae2a 100644 --- a/src/app/app.h +++ b/src/app/app.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2019 Igara Studio S.A. +// Copyright (C) 2018-2020 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -117,6 +117,9 @@ namespace app { script::Engine* scriptEngine() { return m_engine.get(); } #endif + const std::string& memoryDumpFilename() const { return m_memoryDumpFilename; } + void memoryDumpFilename(const std::string& fn) { m_memoryDumpFilename = fn; } + // App Signals obs::signal Exit; obs::signal PaletteChange; @@ -145,13 +148,16 @@ namespace app { #ifdef ENABLE_SCRIPTING std::unique_ptr m_engine; #endif + + // Set the memory dump filename to show in the Preferences dialog + // or the "send crash" dialog. It's set by the SendCrash class. + std::string m_memoryDumpFilename; }; void app_refresh_screen(); void app_rebuild_documents_tabs(); PixelFormat app_get_current_pixel_format(); int app_get_color_to_clear_layer(doc::Layer* layer); - std::string memory_dump_filename(); } // namespace app diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index 84175cd4d..cc71e95d5 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -421,11 +421,10 @@ public: // Links locateFile()->Click.connect(base::Bind(&OptionsWindow::onLocateConfigFile, this)); -#if _WIN32 - locateCrashFolder()->Click.connect(base::Bind(&OptionsWindow::onLocateCrashFolder, this)); -#else - locateCrashFolder()->setVisible(false); -#endif + if (!App::instance()->memoryDumpFilename().empty()) + locateCrashFolder()->Click.connect(base::Bind(&OptionsWindow::onLocateCrashFolder, this)); + else + locateCrashFolder()->setVisible(false); // Undo preferences limitUndo()->Click.connect(base::Bind(&OptionsWindow::onLimitUndoCheck, this)); @@ -1017,7 +1016,8 @@ private: } void onLocateCrashFolder() { - app::launcher::open_folder(base::get_file_path(app::memory_dump_filename())); + app::launcher::open_folder( + base::get_file_path(App::instance()->memoryDumpFilename())); } void onLocateConfigFile() { diff --git a/src/app/send_crash.cpp b/src/app/send_crash.cpp index dc69ff0ca..6d38a1594 100644 --- a/src/app/send_crash.cpp +++ b/src/app/send_crash.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -14,23 +15,92 @@ #include "app/console.h" #include "app/i18n/strings.h" #include "app/resource_finder.h" +#include "app/task.h" #include "base/bind.h" #include "base/fs.h" #include "base/launcher.h" #include "ui/alert.h" +#include "ui/system.h" #include "send_crash.xml.h" namespace app { +// static +std::string SendCrash::DefaultMemoryDumpFilename() +{ +#ifdef _WIN32 + const char* kDefaultCrashName = PACKAGE "-crash-" VERSION ".dmp"; + ResourceFinder rf; + rf.includeUserDir(kDefaultCrashName); + return rf.getFirstOrCreateDefault(); +#else + return std::string(); +#endif +} + +SendCrash::~SendCrash() +{ + if (m_task.running()) { + m_task.cancel(); + m_task.wait(); + } +} + void SendCrash::search() { #ifdef _WIN32 - m_dumpFilename = memory_dump_filename(); + // On Windows we use one mini-dump to report bugs, then we can open + // this .dmp file locally along with the .exe + .pdb to check the + // stack trace and detect the cause of the bug. - if (base::is_file(m_dumpFilename)) { - App::instance()->showNotification(this); + m_dumpFilename = SendCrash::DefaultMemoryDumpFilename(); + if (!m_dumpFilename.empty() && + base::is_file(m_dumpFilename)) { + auto app = App::instance(); + app->memoryDumpFilename(fn); + app->showNotification(this); } + +#elif defined(__APPLE__) + // On macOS we can show the possibility to send the latest crash + // report from ~/Library/Logs/DiagnosticReports which is the + // location where crash reports (.crash files) are located. + + m_task.run( + [this](base::task_token&){ + ResourceFinder rf; + rf.includeHomeDir("Library/Logs/DiagnosticReports"); + std::string dir = rf.defaultFilename(); + if (base::is_directory(dir)) { + std::vector candidates; + int n = std::strlen(PACKAGE); + for (const auto& fn : base::list_files(dir)) { + // Cancel everything + if (m_task.canceled()) + return; + + if (base::utf8_icmp(PACKAGE, fn, n) == 0) { + candidates.push_back(fn); + } + } + std::sort(candidates.begin(), candidates.end()); + if (!candidates.empty()) { + std::string fn = base::join_path(dir, candidates.back()); + if (base::is_file(fn)) { + ui::execute_from_ui_thread( + [this, fn]{ + m_dumpFilename = fn; + if (auto app = App::instance()) { + app->memoryDumpFilename(fn); + app->showNotification(this); + } + }); + } + } + } + }); + #endif } @@ -48,15 +118,24 @@ void SendCrash::notificationClick() app::gen::SendCrash dlg; - // The current version is a "development" version if the VERSION - // macro contains the "dev" word. +#if _WIN32 + // Only on Windows, if the current version is a "development" + // version (i.e. the VERSION macro contains the "dev" word), the + // .dmp file is useless for us. This is because we need the .exe + + // .pdb + source code used in the compilation process to make some + // sense of the .dmp file. + bool isDev = (std::string(VERSION).find("dev") != std::string::npos); if (isDev) { dlg.official()->setVisible(false); dlg.devFilename()->setText(m_dumpFilename); dlg.devFilename()->Click.connect(base::Bind(&SendCrash::onClickDevFilename, this)); } - else { + else +#endif // On other platforms the crash file might be useful even in + // the -dev version (e.g. on macOS it's a text file with stack + // traces). + { dlg.dev()->setVisible(false); dlg.filename()->setText(m_dumpFilename); dlg.filename()->Click.connect(base::Bind(&SendCrash::onClickFilename, this)); diff --git a/src/app/send_crash.h b/src/app/send_crash.h index 73d54e477..d65181623 100644 --- a/src/app/send_crash.h +++ b/src/app/send_crash.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -9,6 +10,7 @@ #pragma once #include "app/notification_delegate.h" +#include "app/task.h" #include @@ -16,6 +18,10 @@ namespace app { class SendCrash : public INotificationDelegate { public: + static std::string DefaultMemoryDumpFilename(); + + ~SendCrash(); + void search(); virtual std::string notificationText() override; @@ -25,6 +31,7 @@ namespace app { void onClickFilename(); void onClickDevFilename(); + Task m_task; std::string m_dumpFilename; }; diff --git a/src/app/task.cpp b/src/app/task.cpp index 5ae0cc61d..f055dd27b 100644 --- a/src/app/task.cpp +++ b/src/app/task.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2020 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -11,6 +11,7 @@ #include "app/task.h" #include "base/task.h" +#include "base/thread.h" #include "base/thread_pool.h" namespace app { @@ -32,4 +33,12 @@ void Task::run(base::task::func_t&& func) m_token = &m_task.start(tasks_pool); } +void Task::wait() +{ + // TODO wait a condition variable + while (!m_task.completed()) { + base::this_thread::sleep_for(0.1); + } +} + } // namespace app diff --git a/src/app/task.h b/src/app/task.h index 31d5516b4..ec216d682 100644 --- a/src/app/task.h +++ b/src/app/task.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2020 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -20,6 +20,7 @@ namespace app { ~Task(); void run(base::task::func_t&& func); + void wait(); // Returns true when the task is completed (whether it was // canceled or not) @@ -27,6 +28,10 @@ namespace app { return m_task.completed(); } + bool running() const { + return m_task.running(); + } + bool canceled() const { if (m_token) return m_token->canceled(); diff --git a/src/main/main.cpp b/src/main/main.cpp index 2a3f88945..e93966186 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of @@ -59,7 +59,7 @@ int app_main(int argc, char* argv[]) std::srand(static_cast(std::time(nullptr))); #ifdef _WIN32 - ::CoInitialize(NULL); + ::CoInitialize(nullptr); #endif try { @@ -70,11 +70,12 @@ int app_main(int argc, char* argv[]) os::ScopedHandle system(os::create_system()); app::App app; - // Change the name of the memory dump file + // Change the memory dump filename to save on disk (.dmp + // file). Note: Only useful on Windows. { - const std::string filename = app::memory_dump_filename(); - if (!filename.empty()) - memoryDump.setFileName(filename); + const std::string fn = app::SendCrash::DefaultMemoryDumpFilename(); + if (!fn.empty()) + memoryDump.setFileName(fn); } const int code = app.initialize(options);