mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-03 13:32:27 +00:00
Add support to send crash reports from macOS
This commit is contained in:
parent
e80dfbbdaf
commit
99cb95357a
@ -606,7 +606,8 @@ crash::DataRecovery* App::dataRecovery() const
|
|||||||
#ifdef ENABLE_UI
|
#ifdef ENABLE_UI
|
||||||
void App::showNotification(INotificationDelegate* del)
|
void App::showNotification(INotificationDelegate* del)
|
||||||
{
|
{
|
||||||
m_mainWindow->showNotification(del);
|
if (m_mainWindow)
|
||||||
|
m_mainWindow->showNotification(del);
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::showBackupNotification(bool state)
|
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);
|
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
|
} // namespace app
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -117,6 +117,9 @@ namespace app {
|
|||||||
script::Engine* scriptEngine() { return m_engine.get(); }
|
script::Engine* scriptEngine() { return m_engine.get(); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
const std::string& memoryDumpFilename() const { return m_memoryDumpFilename; }
|
||||||
|
void memoryDumpFilename(const std::string& fn) { m_memoryDumpFilename = fn; }
|
||||||
|
|
||||||
// App Signals
|
// App Signals
|
||||||
obs::signal<void()> Exit;
|
obs::signal<void()> Exit;
|
||||||
obs::signal<void()> PaletteChange;
|
obs::signal<void()> PaletteChange;
|
||||||
@ -145,13 +148,16 @@ namespace app {
|
|||||||
#ifdef ENABLE_SCRIPTING
|
#ifdef ENABLE_SCRIPTING
|
||||||
std::unique_ptr<script::Engine> m_engine;
|
std::unique_ptr<script::Engine> m_engine;
|
||||||
#endif
|
#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_refresh_screen();
|
||||||
void app_rebuild_documents_tabs();
|
void app_rebuild_documents_tabs();
|
||||||
PixelFormat app_get_current_pixel_format();
|
PixelFormat app_get_current_pixel_format();
|
||||||
int app_get_color_to_clear_layer(doc::Layer* layer);
|
int app_get_color_to_clear_layer(doc::Layer* layer);
|
||||||
std::string memory_dump_filename();
|
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|
||||||
|
@ -421,11 +421,10 @@ public:
|
|||||||
|
|
||||||
// Links
|
// Links
|
||||||
locateFile()->Click.connect(base::Bind<void>(&OptionsWindow::onLocateConfigFile, this));
|
locateFile()->Click.connect(base::Bind<void>(&OptionsWindow::onLocateConfigFile, this));
|
||||||
#if _WIN32
|
if (!App::instance()->memoryDumpFilename().empty())
|
||||||
locateCrashFolder()->Click.connect(base::Bind<void>(&OptionsWindow::onLocateCrashFolder, this));
|
locateCrashFolder()->Click.connect(base::Bind<void>(&OptionsWindow::onLocateCrashFolder, this));
|
||||||
#else
|
else
|
||||||
locateCrashFolder()->setVisible(false);
|
locateCrashFolder()->setVisible(false);
|
||||||
#endif
|
|
||||||
|
|
||||||
// Undo preferences
|
// Undo preferences
|
||||||
limitUndo()->Click.connect(base::Bind<void>(&OptionsWindow::onLimitUndoCheck, this));
|
limitUndo()->Click.connect(base::Bind<void>(&OptionsWindow::onLimitUndoCheck, this));
|
||||||
@ -1017,7 +1016,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onLocateCrashFolder() {
|
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() {
|
void onLocateConfigFile() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -14,23 +15,92 @@
|
|||||||
#include "app/console.h"
|
#include "app/console.h"
|
||||||
#include "app/i18n/strings.h"
|
#include "app/i18n/strings.h"
|
||||||
#include "app/resource_finder.h"
|
#include "app/resource_finder.h"
|
||||||
|
#include "app/task.h"
|
||||||
#include "base/bind.h"
|
#include "base/bind.h"
|
||||||
#include "base/fs.h"
|
#include "base/fs.h"
|
||||||
#include "base/launcher.h"
|
#include "base/launcher.h"
|
||||||
#include "ui/alert.h"
|
#include "ui/alert.h"
|
||||||
|
#include "ui/system.h"
|
||||||
|
|
||||||
#include "send_crash.xml.h"
|
#include "send_crash.xml.h"
|
||||||
|
|
||||||
namespace app {
|
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()
|
void SendCrash::search()
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#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)) {
|
m_dumpFilename = SendCrash::DefaultMemoryDumpFilename();
|
||||||
App::instance()->showNotification(this);
|
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<std::string> 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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,15 +118,24 @@ void SendCrash::notificationClick()
|
|||||||
|
|
||||||
app::gen::SendCrash dlg;
|
app::gen::SendCrash dlg;
|
||||||
|
|
||||||
// The current version is a "development" version if the VERSION
|
#if _WIN32
|
||||||
// macro contains the "dev" word.
|
// 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);
|
bool isDev = (std::string(VERSION).find("dev") != std::string::npos);
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
dlg.official()->setVisible(false);
|
dlg.official()->setVisible(false);
|
||||||
dlg.devFilename()->setText(m_dumpFilename);
|
dlg.devFilename()->setText(m_dumpFilename);
|
||||||
dlg.devFilename()->Click.connect(base::Bind(&SendCrash::onClickDevFilename, this));
|
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.dev()->setVisible(false);
|
||||||
dlg.filename()->setText(m_dumpFilename);
|
dlg.filename()->setText(m_dumpFilename);
|
||||||
dlg.filename()->Click.connect(base::Bind(&SendCrash::onClickFilename, this));
|
dlg.filename()->Click.connect(base::Bind(&SendCrash::onClickFilename, this));
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -9,6 +10,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "app/notification_delegate.h"
|
#include "app/notification_delegate.h"
|
||||||
|
#include "app/task.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -16,6 +18,10 @@ namespace app {
|
|||||||
|
|
||||||
class SendCrash : public INotificationDelegate {
|
class SendCrash : public INotificationDelegate {
|
||||||
public:
|
public:
|
||||||
|
static std::string DefaultMemoryDumpFilename();
|
||||||
|
|
||||||
|
~SendCrash();
|
||||||
|
|
||||||
void search();
|
void search();
|
||||||
|
|
||||||
virtual std::string notificationText() override;
|
virtual std::string notificationText() override;
|
||||||
@ -25,6 +31,7 @@ namespace app {
|
|||||||
void onClickFilename();
|
void onClickFilename();
|
||||||
void onClickDevFilename();
|
void onClickDevFilename();
|
||||||
|
|
||||||
|
Task m_task;
|
||||||
std::string m_dumpFilename;
|
std::string m_dumpFilename;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019 Igara Studio S.A.
|
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -11,6 +11,7 @@
|
|||||||
#include "app/task.h"
|
#include "app/task.h"
|
||||||
|
|
||||||
#include "base/task.h"
|
#include "base/task.h"
|
||||||
|
#include "base/thread.h"
|
||||||
#include "base/thread_pool.h"
|
#include "base/thread_pool.h"
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
@ -32,4 +33,12 @@ void Task::run(base::task::func_t&& func)
|
|||||||
m_token = &m_task.start(tasks_pool);
|
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
|
} // namespace app
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019 Igara Studio S.A.
|
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -20,6 +20,7 @@ namespace app {
|
|||||||
~Task();
|
~Task();
|
||||||
|
|
||||||
void run(base::task::func_t&& func);
|
void run(base::task::func_t&& func);
|
||||||
|
void wait();
|
||||||
|
|
||||||
// Returns true when the task is completed (whether it was
|
// Returns true when the task is completed (whether it was
|
||||||
// canceled or not)
|
// canceled or not)
|
||||||
@ -27,6 +28,10 @@ namespace app {
|
|||||||
return m_task.completed();
|
return m_task.completed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool running() const {
|
||||||
|
return m_task.running();
|
||||||
|
}
|
||||||
|
|
||||||
bool canceled() const {
|
bool canceled() const {
|
||||||
if (m_token)
|
if (m_token)
|
||||||
return m_token->canceled();
|
return m_token->canceled();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019 Igara Studio S.A.
|
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2016 David Capello
|
// Copyright (C) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -59,7 +59,7 @@ int app_main(int argc, char* argv[])
|
|||||||
std::srand(static_cast<unsigned int>(std::time(nullptr)));
|
std::srand(static_cast<unsigned int>(std::time(nullptr)));
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
::CoInitialize(NULL);
|
::CoInitialize(nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -70,11 +70,12 @@ int app_main(int argc, char* argv[])
|
|||||||
os::ScopedHandle<os::System> system(os::create_system());
|
os::ScopedHandle<os::System> system(os::create_system());
|
||||||
app::App app;
|
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();
|
const std::string fn = app::SendCrash::DefaultMemoryDumpFilename();
|
||||||
if (!filename.empty())
|
if (!fn.empty())
|
||||||
memoryDump.setFileName(filename);
|
memoryDump.setFileName(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int code = app.initialize(options);
|
const int code = app.initialize(options);
|
||||||
|
Loading…
Reference in New Issue
Block a user