Rewrite the crash reporter code

This commit is contained in:
Magnus Norddahl 2024-05-09 20:50:53 +02:00
commit 096b92f1b4
22 changed files with 1305 additions and 2475 deletions

View file

@ -33,6 +33,9 @@ set(ZWIDGET_SOURCES
src/widgets/listview/listview.cpp
src/widgets/tabwidget/tabwidget.cpp
src/window/window.cpp
src/systemdialogs/folder_browse_dialog.cpp
src/systemdialogs/open_file_dialog.cpp
src/systemdialogs/save_file_dialog.cpp
)
set(ZWIDGET_INCLUDES
@ -63,6 +66,9 @@ set(ZWIDGET_INCLUDES
include/zwidget/widgets/listview/listview.h
include/zwidget/widgets/tabwidget/tabwidget.h
include/zwidget/window/window.h
include/zwidget/systemdialogs/folder_browse_dialog.h
include/zwidget/systemdialogs/open_file_dialog.h
include/zwidget/systemdialogs/save_file_dialog.h
)
set(ZWIDGET_WIN32_SOURCES
@ -97,6 +103,7 @@ source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S
source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+")
source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+")
source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+")
source_group("src\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/systemdialogs/.+")
source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+")
source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+")
source_group("include\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/.+")
@ -116,6 +123,7 @@ source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_S
source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+")
source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+")
source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+")
source_group("include\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/systemdialogs/.+")
include_directories(include include/zwidget src)

View file

@ -129,6 +129,8 @@ public:
static Size GetScreenSize();
void* GetNativeHandle();
protected:
virtual void OnPaintFrame(Canvas* canvas);
virtual void OnPaint(Canvas* canvas) { }

View file

@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include <string>
class Widget;
/// \brief Displays the system folder browsing dialog
class BrowseFolderDialog
{
public:
/// \brief Constructs an browse folder dialog.
static std::unique_ptr<BrowseFolderDialog> Create(Widget*owner);
virtual ~BrowseFolderDialog() = default;
/// \brief Get the full path of the directory selected.
virtual std::string SelectedPath() const = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string& path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string& title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -0,0 +1,55 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class Widget;
/// \brief Displays the system open file dialog.
class OpenFileDialog
{
public:
/// \brief Constructs an open file dialog.
static std::unique_ptr<OpenFileDialog> Create(Widget* owner);
virtual ~OpenFileDialog() = default;
/// \brief Get the full path of the file selected.
///
/// If multiple files are selected, this returns the first file.
virtual std::string Filename() const = 0;
/// \brief Gets an array that contains one file name for each selected file.
virtual std::vector<std::string> Filenames() const = 0;
/// \brief Sets if multiple files can be selected or not.
/// \param multiselect = When true, multiple items can be selected.
virtual void SetMultiSelect(bool multiselect) = 0;
/// \brief Sets a string containing the full path of the file selected.
virtual void SetFilename(const std::string &filename) = 0;
/// \brief Sets the default extension to use.
virtual void SetDefaultExtension(const std::string& extension) = 0;
/// \brief Add a filter that determines what types of files are displayed.
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;
/// \brief Clears all filters.
virtual void ClearFilters() = 0;
/// \brief Sets a default filter, on a 0-based index.
virtual void SetFilterIndex(int filter_index) = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string &path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string &title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -0,0 +1,46 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class Widget;
/// \brief Displays the system save file dialog.
class SaveFileDialog
{
public:
/// \brief Constructs a save file dialog.
static std::unique_ptr<SaveFileDialog> Create(Widget *owner);
virtual ~SaveFileDialog() = default;
/// \brief Get the full path of the file selected.
virtual std::string Filename() const = 0;
/// \brief Sets a string containing the full path of the file selected.
virtual void SetFilename(const std::string &filename) = 0;
/// \brief Sets the default extension to use.
virtual void SetDefaultExtension(const std::string& extension) = 0;
/// \brief Add a filter that determines what types of files are displayed.
virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0;
/// \brief Clears all filters.
virtual void ClearFilters() = 0;
/// \brief Sets a default filter, on a 0-based index.
virtual void SetFilterIndex(int filter_index) = 0;
/// \brief Sets the initial directory that is displayed.
virtual void SetInitialDirectory(const std::string &path) = 0;
/// \brief Sets the text that appears in the title bar.
virtual void SetTitle(const std::string &title) = 0;
/// \brief Shows the file dialog.
/// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise.
virtual bool Show() = 0;
};

View file

@ -165,4 +165,6 @@ public:
virtual std::string GetClipboardText() = 0;
virtual void SetClipboardText(const std::string& text) = 0;
virtual void* GetNativeHandle() = 0;
};

View file

@ -733,6 +733,12 @@ Size Widget::GetScreenSize()
return DisplayWindow::GetScreenSize();
}
void* Widget::GetNativeHandle()
{
Widget* w = Window();
return w ? w->DispWindow->GetNativeHandle() : nullptr;
}
void Widget::SetStyleClass(const std::string& themeClass)
{
if (StyleClass != themeClass)

View file

@ -0,0 +1,188 @@
#include "systemdialogs/folder_browse_dialog.h"
#include "core/widget.h"
#include "window/window.h"
#include <stdexcept>
#if defined(WIN32)
#include <Shlobj.h>
namespace
{
static std::string from_utf16(const std::wstring& str)
{
if (str.empty()) return {};
int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
std::string result;
result.resize(needed);
needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
return result;
}
static std::wstring to_utf16(const std::string& str)
{
if (str.empty()) return {};
int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
std::wstring result;
result.resize(needed);
needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size());
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
return result;
}
template<typename T>
class ComPtr
{
public:
ComPtr() { Ptr = nullptr; }
ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); }
ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; }
~ComPtr() { reset(); }
ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; }
void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; }
T* get() { return Ptr; }
static IID GetIID() { return __uuidof(T); }
void** InitPtr() { return (void**)TypedInitPtr(); }
T** TypedInitPtr() { reset(); return &Ptr; }
operator T* () const { return Ptr; }
T* operator ->() const { return Ptr; }
T* Ptr;
};
}
class BrowseFolderDialogImpl : public BrowseFolderDialog
{
public:
BrowseFolderDialogImpl(Widget *owner) : owner(owner)
{
}
Widget *owner = nullptr;
std::string selected_path;
std::string initial_directory;
std::string title;
bool Show() override
{
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);
HRESULT result;
ComPtr<IFileOpenDialog> open_dialog;
result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr());
throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed");
result = open_dialog->SetTitle(title16.c_str());
throw_if_failed(result, "IFileOpenDialog.SetTitle failed");
result = open_dialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
throw_if_failed(result, "IFileOpenDialog.SetOptions((FOS_PICKFOLDERS) failed");
if (initial_directory16.length() > 0)
{
LPITEMIDLIST item_id_list = nullptr;
SFGAOF flags = 0;
result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags);
throw_if_failed(result, "SHParseDisplayName failed");
ComPtr<IShellItem> folder_item;
result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr());
ILFree(item_id_list);
throw_if_failed(result, "SHCreateItemFromParsingName failed");
/* This code requires Windows Vista or newer:
ComPtr<IShellItem> folder_item;
result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr());
throw_if_failed(result, "SHCreateItemFromParsingName failed");
*/
if (folder_item)
{
result = open_dialog->SetFolder(folder_item);
throw_if_failed(result, "IFileOpenDialog.SetFolder failed");
}
}
if (owner && owner->Window())
result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle());
else
result = open_dialog->Show(0);
if (SUCCEEDED(result))
{
ComPtr<IShellItem> chosen_folder;
result = open_dialog->GetResult(chosen_folder.TypedInitPtr());
throw_if_failed(result, "IFileOpenDialog.GetResult failed");
WCHAR *buffer = nullptr;
result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer);
throw_if_failed(result, "IShellItem.GetDisplayName failed");
std::wstring output_directory16;
if (buffer)
{
try
{
output_directory16 = buffer;
}
catch (...)
{
CoTaskMemFree(buffer);
throw;
}
}
CoTaskMemFree(buffer);
selected_path = from_utf16(output_directory16);
return true;
}
else
{
return false;
}
}
std::string BrowseFolderDialog::SelectedPath() const override
{
return selected_path;
}
void BrowseFolderDialog::SetInitialDirectory(const std::string& path) override
{
initial_directory = path;
}
void BrowseFolderDialog::SetTitle(const std::string& newtitle) override
{
title = newtitle;
}
void throw_if_failed(HRESULT result, const std::string &error)
{
if (FAILED(result))
throw std::runtime_error(error);
}
};
std::unique_ptr<BrowseFolderDialog> BrowseFolderDialog::Create(Widget* owner)
{
return std::make_unique<BrowseFolderDialogImpl>(owner);
}
#else
std::unique_ptr<BrowseFolderDialog> BrowseFolderDialog::Create(Widget* owner)
{
return {};
}
#endif

View file

@ -0,0 +1,286 @@
#include "systemdialogs/open_file_dialog.h"
#include "core/widget.h"
#include "window/window.h"
#include <stdexcept>
#if defined(WIN32)
#include <Shlobj.h>
namespace
{
static std::string from_utf16(const std::wstring& str)
{
if (str.empty()) return {};
int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
std::string result;
result.resize(needed);
needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
return result;
}
static std::wstring to_utf16(const std::string& str)
{
if (str.empty()) return {};
int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
std::wstring result;
result.resize(needed);
needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size());
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
return result;
}
template<typename T>
class ComPtr
{
public:
ComPtr() { Ptr = nullptr; }
ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); }
ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; }
~ComPtr() { reset(); }
ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; }
void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; }
T* get() { return Ptr; }
static IID GetIID() { return __uuidof(T); }
void** InitPtr() { return (void**)TypedInitPtr(); }
T** TypedInitPtr() { reset(); return &Ptr; }
operator T* () const { return Ptr; }
T* operator ->() const { return Ptr; }
T* Ptr;
};
}
class OpenFileDialogImpl : public OpenFileDialog
{
public:
OpenFileDialogImpl(Widget* owner) : owner(owner)
{
}
Widget* owner = nullptr;
std::string initial_directory;
std::string initial_filename;
std::string title;
std::vector<std::string> filenames;
bool multi_select = false;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
bool Show() override
{
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);
HRESULT result;
ComPtr<IFileOpenDialog> open_dialog;
result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr());
throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed");
result = open_dialog->SetTitle(title16.c_str());
throw_if_failed(result, "IFileOpenDialog.SetTitle failed");
if (!initial_filename.empty())
{
result = open_dialog->SetFileName(to_utf16(initial_filename).c_str());
throw_if_failed(result, "IFileOpenDialog.SetFileName failed");
}
FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST;
if (multi_select)
options |= FOS_ALLOWMULTISELECT;
result = open_dialog->SetOptions(options);
throw_if_failed(result, "IFileOpenDialog.SetOptions() failed");
if (!filters.empty())
{
std::vector<COMDLG_FILTERSPEC> filterspecs(filters.size());
std::vector<std::wstring> descriptions(filters.size());
std::vector<std::wstring> extensions(filters.size());
for (size_t i = 0; i < filters.size(); i++)
{
descriptions[i] = to_utf16(filters[i].description);
extensions[i] = to_utf16(filters[i].extension);
COMDLG_FILTERSPEC& spec = filterspecs[i];
spec.pszName = descriptions[i].c_str();
spec.pszSpec = extensions[i].c_str();
}
result = open_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data());
throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed");
if ((size_t)filterindex < filters.size())
{
result = open_dialog->SetFileTypeIndex(filterindex);
throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed");
}
}
if (!defaultext.empty())
{
result = open_dialog->SetDefaultExtension(to_utf16(defaultext).c_str());
throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed");
}
if (initial_directory16.length() > 0)
{
LPITEMIDLIST item_id_list = nullptr;
SFGAOF flags = 0;
result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags);
throw_if_failed(result, "SHParseDisplayName failed");
ComPtr<IShellItem> folder_item;
result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr());
ILFree(item_id_list);
throw_if_failed(result, "SHCreateItemFromParsingName failed");
/* This code requires Windows Vista or newer:
ComPtr<IShellItem> folder_item;
result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr());
throw_if_failed(result, "SHCreateItemFromParsingName failed");
*/
if (folder_item)
{
result = open_dialog->SetFolder(folder_item);
throw_if_failed(result, "IFileOpenDialog.SetFolder failed");
}
}
if (owner && owner->Window())
result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle());
else
result = open_dialog->Show(0);
if (SUCCEEDED(result))
{
ComPtr<IShellItemArray> items;
result = open_dialog->GetSelectedItems(items.TypedInitPtr());
throw_if_failed(result, "IFileOpenDialog.GetSelectedItems failed");
DWORD num_items = 0;
result = items->GetCount(&num_items);
throw_if_failed(result, "IShellItemArray.GetCount failed");
for (DWORD i = 0; i < num_items; i++)
{
ComPtr<IShellItem> item;
result = items->GetItemAt(i, item.TypedInitPtr());
throw_if_failed(result, "IShellItemArray.GetItemAt failed");
WCHAR* buffer = nullptr;
result = item->GetDisplayName(SIGDN_FILESYSPATH, &buffer);
throw_if_failed(result, "IShellItem.GetDisplayName failed");
std::wstring output16;
if (buffer)
{
try
{
output16 = buffer;
}
catch (...)
{
CoTaskMemFree(buffer);
throw;
}
}
CoTaskMemFree(buffer);
filenames.push_back(from_utf16(output16));
}
return true;
}
else
{
return false;
}
}
std::string Filename() const override
{
return !filenames.empty() ? filenames.front() : std::string();
}
std::vector<std::string> Filenames() const override
{
return filenames;
}
void SetMultiSelect(bool new_multi_select) override
{
multi_select = new_multi_select;
}
void SetFilename(const std::string& filename) override
{
initial_filename = filename;
}
void AddFilter(const std::string& filter_description, const std::string& filter_extension) override
{
Filter f;
f.description = filter_description;
f.extension = filter_extension;
filters.push_back(std::move(f));
}
void ClearFilters() override
{
filters.clear();
}
void SetFilterIndex(int filter_index) override
{
filterindex = filter_index;
}
void SetInitialDirectory(const std::string& path) override
{
initial_directory = path;
}
void SetTitle(const std::string& newtitle) override
{
title = newtitle;
}
void SetDefaultExtension(const std::string& extension) override
{
defaultext = extension;
}
void throw_if_failed(HRESULT result, const std::string& error)
{
if (FAILED(result))
throw std::runtime_error(error);
}
};
std::unique_ptr<OpenFileDialog> OpenFileDialog::Create(Widget* owner)
{
return std::make_unique<OpenFileDialogImpl>(owner);
}
#else
std::unique_ptr<OpenFileDialog> OpenFileDialog::Create(Widget* owner)
{
return {};
}
#endif

View file

@ -0,0 +1,263 @@
#include "systemdialogs/save_file_dialog.h"
#include "core/widget.h"
#include "window/window.h"
#include <stdexcept>
#if defined(WIN32)
#include <Shlobj.h>
namespace
{
static std::string from_utf16(const std::wstring& str)
{
if (str.empty()) return {};
int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
std::string result;
result.resize(needed);
needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
return result;
}
static std::wstring to_utf16(const std::string& str)
{
if (str.empty()) return {};
int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
std::wstring result;
result.resize(needed);
needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size());
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
return result;
}
template<typename T>
class ComPtr
{
public:
ComPtr() { Ptr = nullptr; }
ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); }
ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; }
~ComPtr() { reset(); }
ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; }
void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; }
T* get() { return Ptr; }
static IID GetIID() { return __uuidof(T); }
void** InitPtr() { return (void**)TypedInitPtr(); }
T** TypedInitPtr() { reset(); return &Ptr; }
operator T* () const { return Ptr; }
T* operator ->() const { return Ptr; }
T* Ptr;
};
}
class SaveFileDialogImpl : public SaveFileDialog
{
public:
SaveFileDialogImpl(Widget* owner) : owner(owner)
{
}
Widget* owner = nullptr;
std::string filename;
std::string initial_directory;
std::string initial_filename;
std::string title;
std::vector<std::string> filenames;
struct Filter
{
std::string description;
std::string extension;
};
std::vector<Filter> filters;
int filterindex = 0;
std::string defaultext;
bool Show() override
{
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);
HRESULT result;
ComPtr<IFileSaveDialog> save_dialog;
result = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_ALL, save_dialog.GetIID(), save_dialog.InitPtr());
throw_if_failed(result, "CoCreateInstance(FileSaveDialog) failed");
result = save_dialog->SetTitle(title16.c_str());
throw_if_failed(result, "IFileSaveDialog.SetTitle failed");
if (!initial_filename.empty())
{
result = save_dialog->SetFileName(to_utf16(initial_filename).c_str());
throw_if_failed(result, "IFileSaveDialog.SetFileName failed");
}
FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST;
result = save_dialog->SetOptions(options);
throw_if_failed(result, "IFileSaveDialog.SetOptions() failed");
if (!filters.empty())
{
std::vector<COMDLG_FILTERSPEC> filterspecs(filters.size());
std::vector<std::wstring> descriptions(filters.size());
std::vector<std::wstring> extensions(filters.size());
for (size_t i = 0; i < filters.size(); i++)
{
descriptions[i] = to_utf16(filters[i].description);
extensions[i] = to_utf16(filters[i].extension);
COMDLG_FILTERSPEC& spec = filterspecs[i];
spec.pszName = descriptions[i].c_str();
spec.pszSpec = extensions[i].c_str();
}
result = save_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data());
throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed");
if ((size_t)filterindex < filters.size())
{
result = save_dialog->SetFileTypeIndex(filterindex);
throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed");
}
}
if (!defaultext.empty())
{
result = save_dialog->SetDefaultExtension(to_utf16(defaultext).c_str());
throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed");
}
if (initial_directory16.length() > 0)
{
LPITEMIDLIST item_id_list = nullptr;
SFGAOF flags = 0;
result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags);
throw_if_failed(result, "SHParseDisplayName failed");
ComPtr<IShellItem> folder_item;
result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr());
ILFree(item_id_list);
throw_if_failed(result, "SHCreateItemFromParsingName failed");
/* This code requires Windows Vista or newer:
ComPtr<IShellItem> folder_item;
result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr());
throw_if_failed(result, "SHCreateItemFromParsingName failed");
*/
if (folder_item)
{
result = save_dialog->SetFolder(folder_item);
throw_if_failed(result, "IFileSaveDialog.SetFolder failed");
}
}
if (owner && owner->Window())
result = save_dialog->Show((HWND)owner->Window()->GetNativeHandle());
else
result = save_dialog->Show(0);
if (SUCCEEDED(result))
{
ComPtr<IShellItem> chosen_folder;
result = save_dialog->GetResult(chosen_folder.TypedInitPtr());
throw_if_failed(result, "IFileSaveDialog.GetResult failed");
WCHAR* buffer = nullptr;
result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer);
throw_if_failed(result, "IShellItem.GetDisplayName failed");
std::wstring output16;
if (buffer)
{
try
{
output16 = buffer;
}
catch (...)
{
CoTaskMemFree(buffer);
throw;
}
}
CoTaskMemFree(buffer);
filename = from_utf16(output16);
return true;
}
else
{
return false;
}
}
std::string Filename() const override
{
return filename;
}
void SetFilename(const std::string& filename) override
{
initial_filename = filename;
}
void AddFilter(const std::string& filter_description, const std::string& filter_extension) override
{
Filter f;
f.description = filter_description;
f.extension = filter_extension;
filters.push_back(std::move(f));
}
void ClearFilters() override
{
filters.clear();
}
void SetFilterIndex(int filter_index) override
{
filterindex = filter_index;
}
void SetInitialDirectory(const std::string& path) override
{
initial_directory = path;
}
void SetTitle(const std::string& newtitle) override
{
title = newtitle;
}
void SetDefaultExtension(const std::string& extension) override
{
defaultext = extension;
}
void throw_if_failed(HRESULT result, const std::string& error)
{
if (FAILED(result))
throw std::runtime_error(error);
}
};
std::unique_ptr<SaveFileDialog> SaveFileDialog::Create(Widget* owner)
{
return std::make_unique<SaveFileDialogImpl>(owner);
}
#else
std::unique_ptr<SaveFileDialog> SaveFileDialog::Create(Widget* owner)
{
return {};
}
#endif

View file

@ -1,4 +1,4 @@
#include <algorithm>
#include "widgets/lineedit/lineedit.h"
#include "core/utf8reader.h"
#include "core/colorf.h"

View file

@ -286,11 +286,10 @@ void SDL2DisplayWindow::RunLoop()
while (!ExitRunLoop)
{
SDL_Event event;
SDL_Event event = {};
int result = SDL_WaitEvent(&event);
if (result == 0)
throw std::runtime_error(std::string("SDL_WaitEvent failed:") + SDL_GetError());
DispatchEvent(event);
if (result == 1)
DispatchEvent(event); // Silently ignore if it fails and pray it doesn't busy loop, because SDL and Linux utterly sucks!
}
}

View file

@ -45,6 +45,8 @@ public:
std::string GetClipboardText() override;
void SetClipboardText(const std::string& text) override;
void* GetNativeHandle() override { return WindowHandle; }
static void DispatchEvent(const SDL_Event& event);
static SDL2DisplayWindow* FindEventWindow(const SDL_Event& event);

View file

@ -128,8 +128,6 @@ void Win32DisplayWindow::SetWindowFrame(const Rect& box)
void Win32DisplayWindow::SetClientFrame(const Rect& box)
{
// This function is currently unused but needs to be disabled because it contains Windows API calls that were only added in Windows 10.
#if 0
double dpiscale = GetDpiScale();
RECT rect = {};
@ -143,7 +141,6 @@ void Win32DisplayWindow::SetClientFrame(const Rect& box)
AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle));
SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
#endif
}
void Win32DisplayWindow::Show()
@ -271,22 +268,9 @@ int Win32DisplayWindow::GetPixelHeight() const
return box.bottom;
}
typedef UINT(WINAPI* GetDpiForWindow_t)(HWND);
double Win32DisplayWindow::GetDpiScale() const
{
static GetDpiForWindow_t pGetDpiForWindow = nullptr;
static bool done = false;
if (!done)
{
HMODULE hMod = GetModuleHandleA("User32.dll");
if (hMod != nullptr) pGetDpiForWindow = reinterpret_cast<GetDpiForWindow_t>(GetProcAddress(hMod, "GetDpiForWindow"));
done = true;
}
if (pGetDpiForWindow)
return pGetDpiForWindow(WindowHandle) / 96.0;
else
return 1.0;
return GetDpiForWindow(WindowHandle) / 96.0;
}
std::string Win32DisplayWindow::GetClipboardText()

View file

@ -55,6 +55,8 @@ public:
Point GetLParamPos(LPARAM lparam) const;
void* GetNativeHandle() override { return reinterpret_cast<void*>(WindowHandle); }
static void ProcessEvents();
static void RunLoop();
static void ExitLoop();

View file

@ -408,19 +408,14 @@ set( PLAT_WIN32_SOURCES
common/platform/win32/st_start.cpp
common/platform/win32/base_sysfb.cpp
common/platform/win32/win32basevideo.cpp
common/platform/win32/i_crash.cpp
)
if (HAVE_VULKAN)
list (APPEND PLAT_WIN32_SOURCES common/platform/win32/win32vulkanvideo.cpp )
endif()
# todo: implement an actual crash catcher for ARM
# for now this is purely experimental
if (NOT ${TARGET_ARCHITECTURE} MATCHES "arm" )
set (PLAT_WIN32_SOURCES ${PLAT_WIN32_SOURCES} common/platform/win32/i_crash.cpp )
endif()
if (MSVC AND ${TARGET_ARCHITECTURE} MATCHES "arm")
set (PLAT_WIN32_SOURCES ${PLAT_WIN32_SOURCES} common/platform/win32/i_crash_arm.cpp )
add_definitions( -DNO_SSE -D__ARM__ -DRAPIDJSON_ENDIAN=RAPIDJSON_LITTLEENDIAN)
endif()

File diff suppressed because it is too large Load diff

View file

@ -1,7 +0,0 @@
// Licensed to the Public Domain by Rachael Alexandersion (c) 2021
// This file is nothing more than a stub. Please replace with a proper copyright notice if this function is implemented for ARM. Or just use the regular i_crash.cpp
void DisplayCrashLog()
{
// stub
}

View file

@ -32,14 +32,13 @@
**
*/
// HEADER FILES ------------------------------------------------------------
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#include <objbase.h>
#include <commctrl.h>
#include <richedit.h>
#include <string>
#include <ShlObj.h>
#include <processenv.h>
#include <shellapi.h>
@ -80,8 +79,6 @@
#include "i_mainwindow.h"
// MACROS ------------------------------------------------------------------
// The main window's title.
#ifdef _M_X64
#define X64 " 64-bit"
@ -91,53 +88,25 @@
#define X64 ""
#endif
// TYPES -------------------------------------------------------------------
void InitCrashReporter(const std::wstring& reports_directory, const std::wstring& uploader_executable);
FString GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create);
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
void CreateCrashLog (const char *custominfo, DWORD customsize);
void DisplayCrashLog ();
void DestroyCustomCursor();
int GameMain();
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern EXCEPTION_POINTERS CrashPointers;
extern UINT TimerPeriod;
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// The command line arguments.
FArgs *Args;
HINSTANCE g_hInst;
HANDLE MainThread;
DWORD MainThreadID;
HANDLE StdOut;
bool FancyStdOut, AttachedStdOut;
// CODE --------------------------------------------------------------------
//==========================================================================
//
// I_SetIWADInfo
//
//==========================================================================
void I_SetIWADInfo()
{
}
//==========================================================================
//
// DoMain
//
//==========================================================================
int DoMain (HINSTANCE hInstance)
@ -167,7 +136,7 @@ int DoMain (HINSTANCE hInstance)
// handle instead of creating a console window.
StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (StdOut != NULL)
if (StdOut != nullptr)
{
// It seems that running from a shell always creates a std output
// for us, even if it doesn't go anywhere. (Running from Explorer
@ -177,7 +146,7 @@ int DoMain (HINSTANCE hInstance)
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(StdOut, &info))
{
StdOut = NULL;
StdOut = nullptr;
}
}
if (StdOut == nullptr)
@ -185,7 +154,7 @@ int DoMain (HINSTANCE hInstance)
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD foo; WriteFile(StdOut, "\n", 1, &foo, NULL);
DWORD foo; WriteFile(StdOut, "\n", 1, &foo, nullptr);
AttachedStdOut = true;
}
if (StdOut == nullptr && AllocConsole())
@ -217,7 +186,7 @@ int DoMain (HINSTANCE hInstance)
// Figure out what directory the program resides in.
WCHAR progbuff[1024];
if (GetModuleFileNameW(nullptr, progbuff, sizeof progbuff) == 0)
if (GetModuleFileNameW(nullptr, progbuff, 1024) == 0)
{
MessageBoxA(nullptr, "Fatal", "Could not determine program location.", MB_ICONEXCLAMATION|MB_OK);
exit(-1);
@ -242,7 +211,7 @@ int DoMain (HINSTANCE hInstance)
// element. DEVMODE is not one of those structures.
memset (&displaysettings, 0, sizeof(displaysettings));
displaysettings.dmSize = sizeof(displaysettings);
EnumDisplaySettings (NULL, ENUM_CURRENT_SETTINGS, &displaysettings);
EnumDisplaySettings (nullptr, ENUM_CURRENT_SETTINGS, &displaysettings);
x = (displaysettings.dmPelsWidth - width) / 2;
y = (displaysettings.dmPelsHeight - height) / 2;
@ -260,17 +229,17 @@ int DoMain (HINSTANCE hInstance)
WinWidth = cRect.right;
WinHeight = cRect.bottom;
CoInitialize (NULL);
atexit ([](){ CoUninitialize(); }); // beware of calling convention.
if (SUCCEEDED(CoInitialize(nullptr)))
atexit ([](){ CoUninitialize(); }); // beware of calling convention.
int ret = GameMain ();
if (mainwindow.CheckForRestart())
{
HMODULE hModule = GetModuleHandleW(NULL);
HMODULE hModule = GetModuleHandleW(nullptr);
WCHAR path[MAX_PATH];
GetModuleFileNameW(hModule, path, MAX_PATH);
ShellExecuteW(NULL, L"open", path, GetCommandLineW(), NULL, SW_SHOWNORMAL);
ShellExecuteW(nullptr, L"open", path, GetCommandLineW(), nullptr, SW_SHOWNORMAL);
}
DestroyCustomCursor();
@ -287,7 +256,7 @@ int DoMain (HINSTANCE hInstance)
if (StdOut != nullptr) WriteFile(StdOut, "Press any key to exit...", 24, &bytes, nullptr);
FlushConsoleInputBuffer(stdinput);
SetConsoleMode(stdinput, 0);
ReadConsole(stdinput, &bytes, 1, &bytes, NULL);
ReadConsole(stdinput, &bytes, 1, &bytes, nullptr);
}
else if (StdOut == nullptr)
{
@ -319,231 +288,13 @@ void I_ShowFatalError(const char *msg)
}
}
// Here is how the error logging system works.
//
// To catch exceptions that occur in secondary threads, CatchAllExceptions is
// set as the UnhandledExceptionFilter for this process. It records the state
// of the thread at the time of the crash using CreateCrashLog and then queues
// an APC on the primary thread. When the APC executes, it raises a software
// exception that gets caught by the __try/__except block in WinMain.
// I_GetEvent calls SleepEx to put the primary thread in a waitable state
// periodically so that the APC has a chance to execute.
//
// Exceptions on the primary thread are caught by the __try/__except block in
// WinMain. Not only does it record the crash information, it also shuts
// everything down and displays a dialog with the information present. If a
// console log is being produced, the information will also be appended to it.
//
// If a debugger is running, CatchAllExceptions never executes, so secondary
// thread exceptions will always be caught by the debugger. For the primary
// thread, IsDebuggerPresent is called to determine if a debugger is present.
// Note that this function is not present on Windows 95, so we cannot
// statically link to it.
//
// To make this work with MinGW, you will need to use inline assembly
// because GCC offers no native support for Windows' SEH.
//==========================================================================
//
// SleepForever
//
//==========================================================================
void SleepForever ()
{
Sleep (INFINITE);
}
//==========================================================================
//
// ExitMessedUp
//
// An exception occurred while exiting, so don't do any standard processing.
// Just die.
//
//==========================================================================
LONG WINAPI ExitMessedUp (LPEXCEPTION_POINTERS foo)
{
ExitProcess (1000);
}
//==========================================================================
//
// ExitFatally
//
//==========================================================================
void CALLBACK ExitFatally (ULONG_PTR dummy)
{
SetUnhandledExceptionFilter (ExitMessedUp);
I_ShutdownGraphics ();
mainwindow.RestoreConView ();
DisplayCrashLog ();
exit(-1);
}
#ifndef _M_ARM64
//==========================================================================
//
// CatchAllExceptions
//
//==========================================================================
namespace
{
CONTEXT MainThreadContext;
}
LONG WINAPI CatchAllExceptions (LPEXCEPTION_POINTERS info)
{
#ifdef _DEBUG
if (info->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
{
return EXCEPTION_CONTINUE_SEARCH;
}
#endif
static bool caughtsomething = false;
if (caughtsomething) return EXCEPTION_EXECUTE_HANDLER;
caughtsomething = true;
char *custominfo = (char *)HeapAlloc (GetProcessHeap(), 0, 16384);
CrashPointers = *info;
if (sysCallbacks.CrashInfo && custominfo) sysCallbacks.CrashInfo(custominfo, 16384, "\r\n");
CreateCrashLog (custominfo, (DWORD)strlen(custominfo));
// If the main thread crashed, then make it clean up after itself.
// Otherwise, put the crashing thread to sleep and signal the main thread to clean up.
if (GetCurrentThreadId() == MainThreadID)
{
#ifdef _M_X64
*info->ContextRecord = MainThreadContext;
#else
info->ContextRecord->Eip = (DWORD_PTR)ExitFatally;
#endif // _M_X64
}
else
{
#ifndef _M_X64
info->ContextRecord->Eip = (DWORD_PTR)SleepForever;
#else
info->ContextRecord->Rip = (DWORD_PTR)SleepForever;
#endif
QueueUserAPC (ExitFatally, MainThread, 0);
}
return EXCEPTION_CONTINUE_EXECUTION;
}
#else // !_M_ARM64
// stub this function for ARM64
LONG WINAPI CatchAllExceptions (LPEXCEPTION_POINTERS info)
{
return EXCEPTION_CONTINUE_EXECUTION;
}
#endif // !_M_ARM64
//==========================================================================
//
// infiniterecursion
//
// Debugging routine for testing the crash logger.
//
//==========================================================================
#ifdef _DEBUG
static void infiniterecursion(int foo)
{
if (foo)
{
infiniterecursion(foo);
}
}
#endif
// Setting this to 'true' allows getting the standard notification for a crash
// which offers the very important feature to open a debugger and see the crash in context right away.
CUSTOM_CVAR(Bool, disablecrashlog, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
SetUnhandledExceptionFilter(!*self ? CatchAllExceptions : nullptr);
}
//==========================================================================
//
// WinMain
//
//==========================================================================
int WINAPI wWinMain (HINSTANCE hInstance, HINSTANCE nothing, LPWSTR cmdline, int nCmdShow)
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE nothing, LPWSTR cmdline, int nCmdShow)
{
g_hInst = hInstance;
InitCommonControls (); // Load some needed controls and be pretty under XP
// We need to load riched20.dll so that we can create the control.
if (NULL == LoadLibraryA ("riched20.dll"))
{
// This should only happen on basic Windows 95 installations, but since we
// don't support Windows 95, we have no obligation to provide assistance in
// getting it installed.
MessageBoxA(NULL, "Could not load riched20.dll", GAMENAME " Error", MB_OK | MB_ICONSTOP);
return 0;
}
#if !defined(__GNUC__) && defined(_DEBUG)
if (__argc == 2 && __wargv != nullptr && wcscmp (__wargv[1], L"TestCrash") == 0)
{
__try
{
*(int *)0 = 0;
}
__except(CrashPointers = *GetExceptionInformation(),
CreateCrashLog ("TestCrash", 9), EXCEPTION_EXECUTE_HANDLER)
{
}
DisplayCrashLog ();
return 0;
}
if (__argc == 2 && __wargv != nullptr && wcscmp (__wargv[1], L"TestStackCrash") == 0)
{
__try
{
infiniterecursion(1);
}
__except(CrashPointers = *GetExceptionInformation(),
CreateCrashLog ("TestStackCrash", 14), EXCEPTION_EXECUTE_HANDLER)
{
}
DisplayCrashLog ();
return 0;
}
#endif
MainThread = INVALID_HANDLE_VALUE;
DuplicateHandle (GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &MainThread,
0, FALSE, DUPLICATE_SAME_ACCESS);
MainThreadID = GetCurrentThreadId();
#ifndef _DEBUG
if (MainThread != INVALID_HANDLE_VALUE)
{
#ifndef _M_ARM64
SetUnhandledExceptionFilter (CatchAllExceptions);
#endif
#ifdef _M_X64
static bool setJumpResult = false;
RtlCaptureContext(&MainThreadContext);
if (setJumpResult)
{
ExitFatally(0);
return 0;
}
setJumpResult = true;
#endif // _M_X64
}
#endif
InitCommonControls();
#if defined(_DEBUG) && defined(_MSC_VER)
// Uncomment this line to make the Visual C++ CRT check the heap before
@ -558,14 +309,24 @@ int WINAPI wWinMain (HINSTANCE hInstance, HINSTANCE nothing, LPWSTR cmdline, int
//_crtBreakAlloc = 227524;
#endif
int ret = DoMain (hInstance);
// Setup crash reporting, unless it is the crash reporter launching us in response to a crash
if (wcsstr(cmdline, L"-showcrashreport") == nullptr)
{
WCHAR exeFilename[1024] = {};
if (GetModuleFileName(0, exeFilename, 1023) != 0)
{
FString reportsDirectory = GetKnownFolder(CSIDL_LOCAL_APPDATA, FOLDERID_LocalAppData, true);
reportsDirectory += "/" GAMENAMELOWERCASE;
reportsDirectory += "/crashreports";
CreatePath(reportsDirectory.GetChars());
CloseHandle (MainThread);
MainThread = INVALID_HANDLE_VALUE;
return ret;
InitCrashReporter(reportsDirectory.WideString(), exeFilename);
}
}
return DoMain(hInstance);
}
// each platform has its own specific version of this function.
void I_SetWindowTitle(const char* caption)
{
mainwindow.SetWindowTitle(caption);

View file

@ -6,14 +6,16 @@
#include <zwidget/core/image.h>
#include <zwidget/widgets/pushbutton/pushbutton.h>
#include <zwidget/widgets/scrollbar/scrollbar.h>
#include <zwidget/systemdialogs/save_file_dialog.h>
#include <miniz.h>
bool ErrorWindow::ExecModal(const std::string& text, const std::string& log)
bool ErrorWindow::ExecModal(const std::string& text, const std::string& log, std::vector<uint8_t> minidump)
{
Size screenSize = GetScreenSize();
double windowWidth = 1200.0;
double windowHeight = 700.0;
auto window = std::make_unique<ErrorWindow>();
auto window = std::make_unique<ErrorWindow>(std::move(minidump));
window->SetText(text, log);
window->SetFrameGeometry((screenSize.width - windowWidth) * 0.5, (screenSize.height - windowHeight) * 0.5, windowWidth, windowHeight);
window->Show();
@ -23,7 +25,7 @@ bool ErrorWindow::ExecModal(const std::string& text, const std::string& log)
return window->Restart;
}
ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window)
ErrorWindow::ErrorWindow(std::vector<uint8_t> initminidump) : Widget(nullptr, WidgetType::Window), minidump(std::move(initminidump))
{
FStringf caption("Fatal Error - " GAMENAME " %s (%s)", GetVersionString(), GetGitTime());
SetWindowTitle(caption.GetChars());
@ -34,13 +36,21 @@ ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window)
LogView = new LogViewer(this);
ClipboardButton = new PushButton(this);
RestartButton = new PushButton(this);
ClipboardButton->OnClick = [=]() { OnClipboardButtonClicked(); };
RestartButton->OnClick = [=]() { OnRestartButtonClicked(); };
ClipboardButton->SetText("Copy to clipboard");
RestartButton->SetText("Restart");
if (minidump.empty())
{
RestartButton = new PushButton(this);
RestartButton->OnClick = [=]() { OnRestartButtonClicked(); };
RestartButton->SetText("Restart");
}
else
{
SaveReportButton = new PushButton(this);
SaveReportButton->OnClick = [=]() { OnSaveReportButtonClicked(); };
SaveReportButton->SetText("Save Report");
}
LogView->SetFocus();
}
@ -83,6 +93,37 @@ void ErrorWindow::OnRestartButtonClicked()
DisplayWindow::ExitLoop();
}
void ErrorWindow::OnSaveReportButtonClicked()
{
auto dialog = SaveFileDialog::Create(this);
dialog->AddFilter("Crash Report Zip Files", "*.zip");
dialog->AddFilter("All Files", "*.*");
dialog->SetFilename("CrashReport.zip");
dialog->SetDefaultExtension("zip");
if (dialog->Show())
{
std::string filename = dialog->Filename();
mz_zip_archive zip = {};
if (mz_zip_writer_init_heap(&zip, 0, 16 * 1024 * 1024))
{
mz_zip_writer_add_mem(&zip, "minidump.dmp", minidump.data(), minidump.size(), MZ_DEFAULT_COMPRESSION);
mz_zip_writer_add_mem(&zip, "log.txt", clipboardtext.data(), clipboardtext.size(), MZ_DEFAULT_COMPRESSION);
}
void* buffer = nullptr;
size_t buffersize = 0;
mz_zip_writer_finalize_heap_archive(&zip, &buffer, &buffersize);
mz_zip_writer_end(&zip);
std::unique_ptr<FileWriter> f(FileWriter::Open(filename.c_str()));
if (f)
{
f->Write(buffer, buffersize);
f->Close();
}
}
}
void ErrorWindow::OnClose()
{
Restart = false;
@ -96,7 +137,10 @@ void ErrorWindow::OnGeometryChanged()
double y = GetHeight() - 15.0 - ClipboardButton->GetPreferredHeight();
ClipboardButton->SetFrameGeometry(20.0, y, 170.0, ClipboardButton->GetPreferredHeight());
RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight());
if (RestartButton)
RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight());
else if (SaveReportButton)
SaveReportButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, SaveReportButton->GetPreferredHeight());
y -= 20.0;
LogView->SetFrameGeometry(Rect::xywh(0.0, 0.0, w, y));

View file

@ -10,9 +10,9 @@ class Scrollbar;
class ErrorWindow : public Widget
{
public:
static bool ExecModal(const std::string& text, const std::string& log);
static bool ExecModal(const std::string& text, const std::string& log, std::vector<uint8_t> minidump = {});
ErrorWindow();
ErrorWindow(std::vector<uint8_t> minidump);
bool Restart = false;
@ -25,11 +25,14 @@ private:
void OnClipboardButtonClicked();
void OnRestartButtonClicked();
void OnSaveReportButtonClicked();
LogViewer* LogView = nullptr;
PushButton* ClipboardButton = nullptr;
PushButton* RestartButton = nullptr;
PushButton* SaveReportButton = nullptr;
std::vector<uint8_t> minidump;
std::string clipboardtext;
};

View file

@ -29,6 +29,8 @@
#ifdef _WIN32
#include <direct.h>
#define WIN32_MEAN_AND_LEAN
#include <Windows.h>
#endif
#if defined(__unix__) || defined(__APPLE__)
@ -117,6 +119,7 @@
#include "screenjob.h"
#include "startscreen.h"
#include "shiftstate.h"
#include "common/widgets/errorwindow.h"
#ifdef __unix__
#include "i_system.h" // for SHARE_DIR
@ -3717,6 +3720,49 @@ static int D_DoomMain_Internal (void)
LoadHexFont(wad); // load hex font early so we have it during startup.
InitWidgetResources(wad);
if (Args->CheckParm("-showcrashreport"))
{
FString minidumpFilename = Args->GetArg(2);
FString logFilename = Args->GetArg(3);
FString logText;
{
FileReader fr;
if (fr.OpenFile(logFilename.GetChars()))
{
std::vector<char> data(fr.GetLength() + 1);
if (fr.Read(data.data(), data.size() - 1) == (FileReader::Size)data.size() - 1)
{
logText = data.data();
}
}
}
std::vector<uint8_t> minidump;
{
FileReader fr;
if (fr.OpenFile(minidumpFilename.GetChars()))
{
minidump.resize(fr.GetLength());
if (fr.Read(minidump.data(), minidump.size()) != (FileReader::Size)minidump.size())
{
minidump.clear();
}
}
}
FString text;
text.Format("%s fatally crashed!", GAMENAME);
ErrorWindow::ExecModal(text.GetChars(), logText.GetChars(), std::move(minidump));
// Crash reporter only uses -showcrashreport on Windows at the moment and there seems to be no abstraction available
#ifdef WIN32
DeleteFile(logFilename.WideString().c_str());
DeleteFile(minidumpFilename.WideString().c_str());
#endif
return 0;
}
C_InitConsole(80*8, 25*8, false);
I_DetectOS();