New Crash Report UI

This commit is contained in:
TheBrokenRail 2024-11-21 21:45:57 -05:00
parent 7f9d1d843e
commit d3b70878be
22 changed files with 208 additions and 163 deletions

3
.gitmodules vendored
View File

@ -1,9 +1,6 @@
[submodule "dependencies/glfw/src"]
path = dependencies/glfw/src
url = https://github.com/glfw/glfw.git
[submodule "dependencies/zenity/src"]
path = dependencies/zenity/src
url = https://gitea.thebrokenrail.com/minecraft-pi-reborn/zenity.git
[submodule "dependencies/LIEF/src"]
path = dependencies/LIEF/src
url = https://github.com/lief-project/LIEF.git

View File

@ -8,10 +8,6 @@ endif()
if(BUILD_ARM_COMPONENTS AND NOT MCPI_OPEN_SOURCE_ONLY)
add_subdirectory(minecraft-pi)
endif()
# Zenity (Minimal Build)
if(BUILD_NATIVE_COMPONENTS)
add_subdirectory(zenity)
endif()
# LIEF
if(BUILD_NATIVE_COMPONENTS OR BUILD_MEDIA_LAYER_CORE)
add_subdirectory(LIEF)

View File

@ -1,20 +0,0 @@
project(zenity)
# Silence Warnings
add_compile_options(-w)
## Zenity
# Download
set(MESSAGE_QUIET TRUE)
add_subdirectory(src EXCLUDE_FROM_ALL)
unset(MESSAGE_QUIET)
# Ensure Build
add_custom_target(zenity-build ALL DEPENDS zenity)
# Install
install(TARGETS zenity DESTINATION "${MCPI_BIN_DIR}")
# License
install(FILES src/COPYING DESTINATION "${MCPI_LEGAL_DIR}/zenity")

@ -1 +0,0 @@
Subproject commit a7496461161c917878d58131711425e7c8e59436

View File

@ -27,7 +27,7 @@ The AppImage requires Debian Bullseye or higher. This is equivalent to Ubuntu 20
It also requires some additional packages. To install them, run:
```sh
sudo apt install -y libfuse2 libgtk-3-0 libopenal1 libglib2.0-0
sudo apt install -y libfuse2 libopenal1 libglib2.0-0
```
</details>

View File

@ -9,6 +9,7 @@ add_executable(launcher
src/bootstrap/debug.cpp
src/util/util.cpp
src/util/sdk.cpp
src/util/env.cpp
src/logger/logger.cpp
src/logger/crash-report.cpp
src/options/parser.cpp

View File

@ -75,28 +75,21 @@ int ConfigurationUI::draw_bottom() {
ImGui::SameLine();
}
// Right-Align Buttons
const ImGuiStyle &style = ImGui::GetStyle();
const char *bottom_row_text[] = {"Quit", "Launch"};
float width_needed = 0;
for (const char *text : bottom_row_text) {
if (width_needed > 0) {
width_needed += style.ItemSpacing.x;
int ret = 0;
draw_right_aligned_buttons({"Quit", "Launch"}, [&ret](const int id, const bool was_clicked) {
if (id == 0) {
// Quit
if (was_clicked) {
ret = -1;
}
ImGui::SetItemTooltip("Changes Will Not Be Saved!");
} else if (was_clicked) {
// Launch
ret = 1;
}
width_needed += ImGui::CalcTextSize(text).x + style.FramePadding.x * 2.f;
}
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width_needed);
// Quit
if (ImGui::Button(bottom_row_text[0])) {
return -1;
}
ImGui::SetItemTooltip("Changes Will Not Be Saved!");
ImGui::SameLine();
// Launch
if (ImGui::Button(bottom_row_text[1])) {
return 1;
}
});
// Return
return 0;
return ret;
}
// Main Tab

View File

@ -1,35 +1,84 @@
#include <fstream>
#include <libreborn/libreborn.h>
#include "logger.h"
#include "../ui/frame.h"
// UI
struct CrashReport final : Frame {
explicit CrashReport(const char *filename): Frame("Crash Report", 640, 480) {
// Open File
std::ifstream stream(filename, std::ios_base::binary | std::ios_base::ate);
if (stream) {
// Read File
const std::streamoff size = stream.tellg();
stream.seekg(0, std::ifstream::beg);
log.resize(size);
stream.read(log.data(), size);
// Close File
stream.close();
}
}
bool first_render = true;
int render() override {
// Text
ImGui::TextWrapped("%s", MCPI_APP_TITLE " has crashed!");
ImGui::Spacing();
ImGui::TextWrapped("Need help? Consider asking on the Discord server!");
ImGui::Spacing();
ImGui::TextWrapped("If you believe this is a problem with " MCPI_APP_TITLE " itself, please upload this crash report to the #bugs Discord channel.");
// Log
if (ImGui::BeginChild("Log", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() /* Leave Room For Bottom Row */), ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar)) {
ImGui::PushFont(monospace);
ImGui::TextUnformatted(log.data(), log.data() + log.size());
ImGui::PopFont();
if (first_render) {
ImGui::SetScrollHereY(1.0f);
first_render = false;
}
}
ImGui::EndChild();
// Buttons
if (ImGui::Button("Join Discord")) {
open_url(MCPI_DISCORD_INVITE);
}
ImGui::SameLine();
if (ImGui::Button("View All Logs")) {
open_url("file://" + get_logs_folder());
}
ImGui::SameLine();
// Right-Aligned
int ret = 0;
const std::string &log_ref = log;
draw_right_aligned_buttons({"Copy", "Quit"}, [&ret, &log_ref](const int id, const bool was_clicked) {
if (was_clicked) {
if (id == 0) {
// Copy Log
ImGui::SetClipboardText(log_ref.c_str());
} else {
// Exit
ret = 1;
}
}
});
return ret;
}
std::string log;
};
// Show Crash Report Dialog
#define DIALOG_TITLE "Crash Report"
#define CRASH_REPORT_DIALOG_WIDTH "640"
#define CRASH_REPORT_DIALOG_HEIGHT "480"
void show_report(const char *log_filename) {
// Fork
pid_t pid = fork();
const pid_t pid = fork();
if (pid == 0) {
// Child
setsid();
ALLOC_CHECK(freopen("/dev/null", "w", stdout));
ALLOC_CHECK(freopen("/dev/null", "w", stderr));
ALLOC_CHECK(freopen("/dev/null", "r", stdin));
const char *command[] = {
"zenity",
"--title", DIALOG_TITLE,
"--name", MCPI_APP_ID,
"--width", CRASH_REPORT_DIALOG_WIDTH,
"--height", CRASH_REPORT_DIALOG_HEIGHT,
"--text-info",
"--text", MCPI_APP_TITLE " has crashed!\n\nNeed help? Consider asking on the <a href=\"" MCPI_DISCORD_INVITE "\">Discord server</a>! <i>If you believe this is a problem with " MCPI_APP_TITLE " itself, please upload this crash report to the #bugs Discord channel.</i>",
"--filename", log_filename,
"--no-wrap",
"--font", "Monospace",
"--save-filename", MCPI_VARIANT_NAME "-crash-report.log",
"--ok-label", "Exit",
nullptr
};
safe_execvpe(command, (const char *const *) environ);
CrashReport ui(log_filename);
ui.run();
exit(EXIT_SUCCESS);
}
}

View File

@ -3,7 +3,6 @@
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <cstdint>
#include <csignal>
#include <poll.h>
#include <sys/ioctl.h>
@ -26,12 +25,18 @@ static void exit_handler(__attribute__((unused)) int signal) {
// Log File
static std::string log_filename;
static int log_fd;
static void setup_log_file() {
// Get Log Directory
std::string get_logs_folder() {
const std::string home = std::string(getenv(_MCPI_HOME_ENV)) + get_home_subdirectory_for_game_data();
ensure_directory(home.c_str());
const std::string logs = home + "/logs";
ensure_directory(logs.c_str());
return logs;
}
static void setup_log_file() {
// Get Log Directory
const std::string home = std::string(getenv(_MCPI_HOME_ENV)) + get_home_subdirectory_for_game_data();
ensure_directory(home.c_str());
const std::string logs = get_logs_folder();
// Get Timestamp
time_t raw_time;
@ -129,7 +134,7 @@ void setup_logger() {
// Close Log File
close(log_fd);
unsetenv(_MCPI_LOG_FD_ENV);
reborn_set_log(-1);
// Show Crash Log
if (is_crash && !reborn_is_headless()) {

View File

@ -1,4 +1,7 @@
#pragma once
#include <string>
std::string get_logs_folder();
void setup_logger();
void show_report(const char *log_filename);

View File

@ -24,47 +24,11 @@ static void setup_environment(const options_t &options) {
bind_to_env(_MCPI_FORCE_HEADLESS_ENV, options.force_headless);
bind_to_env(_MCPI_FORCE_NON_HEADLESS_ENV, options.force_non_headless);
// GTK Dark Mode
set_and_print_env("GTK_THEME", "Adwaita:dark");
// Configure PATH
{
// Get Binary Directory
const std::string binary_directory = get_binary_directory();
std::string new_path = binary_directory + "/bin";
// Add Existing PATH
{
const char *value = getenv("PATH");
if (value != nullptr && strlen(value) > 0) {
new_path += std::string(":") + value;
}
}
// Set And Free
set_and_print_env("PATH", new_path.c_str());
}
setup_path();
// Setup MCPI_HOME
const char *custom_profile_directory = getenv(MCPI_PROFILE_DIRECTORY_ENV);
if (custom_profile_directory != nullptr) {
// Custom Directory
custom_profile_directory = realpath(custom_profile_directory, nullptr);
ALLOC_CHECK(custom_profile_directory);
set_and_print_env(_MCPI_HOME_ENV, custom_profile_directory);
free((void *) custom_profile_directory);
} else if (!reborn_is_server()) {
// Ensure $HOME
const char *home = getenv("HOME");
if (home == nullptr) {
ERR("$HOME Is Not Set");
}
set_and_print_env(_MCPI_HOME_ENV, home);
} else {
// Set Home To Current Directory, So World Data Is Stored There
char *launch_directory = getcwd(nullptr, 0);
ALLOC_CHECK(launch_directory);
set_and_print_env(_MCPI_HOME_ENV, launch_directory);
free(launch_directory);
}
setup_home();
// Create If Needed
const std::string minecraft_folder = std::string(getenv(_MCPI_HOME_ENV)) + get_home_subdirectory_for_game_data();
ensure_directory(minecraft_folder.c_str());

View File

@ -12,8 +12,9 @@ Frame::Frame(const char *title, const int width, const int height) {
// Create Window
init_glfw();
window = create_glfw_window(title, width, height);
// V-Sync
glfwSwapInterval(1);
// Disable V-Sync
// (On Wayland, This Fixes Issues With The Clipboard)
glfwSwapInterval(0);
// Setup ImGui Context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
@ -26,9 +27,11 @@ Frame::Frame(const char *title, const int width, const int height) {
ImGui_ImplOpenGL2_Init();
}
Frame::~Frame() {
// Shutdown ImGui
ImGui_ImplOpenGL2_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
// Cleanup GLFW
cleanup_glfw(window);
}
@ -85,3 +88,24 @@ void Frame::setup_style(const float scale) {
style.ScaleAllSizes(scale);
patch_colors(style);
}
// Right-Aligned Buttons
void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback) {
// Calculate Position
const ImGuiStyle &style = ImGui::GetStyle();
float width_needed = 0;
for (const char *text : buttons) {
if (width_needed > 0) {
width_needed += style.ItemSpacing.x;
}
width_needed += ImGui::CalcTextSize(text).x + style.FramePadding.x * 2.0f;
}
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width_needed);
// Draw
for (std::vector<const char *>::size_type id = 0; id < buttons.size(); id++) {
if (id > 0) {
ImGui::SameLine();
}
callback(int(id), ImGui::Button(buttons[id]));
}
}

View File

@ -1,22 +1,29 @@
#pragma once
#include <vector>
#include <functional>
#include <imgui.h>
#include <string>
#include <GLFW/glfw3.h>
// UI Frame
struct Frame {
Frame(const char *title, int width, int height);
virtual ~Frame();
// Prevent Copying
Frame(const Frame &) = delete;
Frame &operator=(const Frame &) = delete;
// Run
int run();
virtual int render() = 0;
// Properties
protected:
// API For Sub-Classes
ImFont *monospace = nullptr;
static void draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback);
private:
// Properties
GLFWwindow *window = nullptr;
// Internal
// Internal Methods
float get_scale();
void setup_style(float scale);
static void patch_colors(ImGuiStyle &style);

46
launcher/src/util/env.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "util.h"
#include <libreborn/libreborn.h>
// $PATH
void setup_path() {
// Get Binary Directory
const std::string binary_directory = get_binary_directory();
std::string new_path = binary_directory + "/bin";
// Add Existing PATH
const char *value = getenv("PATH");
if (value != nullptr && strlen(value) > 0) {
new_path += std::string(":") + value;
}
// Set And Free
set_and_print_env("PATH", new_path.c_str());
}
// Profile Directory
void setup_home() {
const char *custom_profile_directory = getenv(MCPI_PROFILE_DIRECTORY_ENV);
std::string home;
if (custom_profile_directory != nullptr) {
// Custom Directory
home = safe_realpath(custom_profile_directory);
} else if (!reborn_is_server()) {
// Ensure $HOME
const char *value = getenv("HOME");
if (value == nullptr) {
ERR("$HOME Is Not Set");
}
home = value;
// Flatpak
#ifdef MCPI_IS_FLATPAK_BUILD
home += "/.var/app/" MCPI_APP_ID;
#endif
} else {
// Set Home To Current Directory, So World Data Is Stored There
char *launch_directory = getcwd(nullptr, 0);
ALLOC_CHECK(launch_directory);
home = launch_directory;
free(launch_directory);
}
// Set
set_and_print_env(_MCPI_HOME_ENV, home.c_str());
}

View File

@ -8,4 +8,7 @@ void chop_last_component(std::string &str);
std::string safe_realpath(const std::string &path);
std::string get_binary_directory();
void copy_sdk(const std::string &binary_directory, bool log_with_debug);
void copy_sdk(const std::string &binary_directory, bool log_with_debug);
void setup_path();
void setup_home();

View File

@ -28,4 +28,7 @@ std::vector<unsigned char> *run_command(const char *const command[], int *exit_s
bool is_exit_status_success(int status);
// Get Exit Status String
std::string get_exit_status_string(int status);
std::string get_exit_status_string(int status);
// Open URL
void open_url(const std::string &url);

View File

@ -101,6 +101,10 @@ void poll_fds(const std::vector<int> &fds, const std::function<void(int, size_t,
on_data(int(i), size_t(bytes_read), buf);
} else {
// File Descriptor No Longer Accessible
if (poll_fd.fd == STDIN_FILENO) {
// This Shouldn't Happen
IMPOSSIBLE();
}
poll_fd.events = 0;
open_fds--;
}
@ -186,3 +190,14 @@ bool is_exit_status_success(const int status) {
return false;
}
}
// Open URL
void open_url(const std::string &url) {
int return_code;
const char *command[] = {"xdg-open", url.c_str(), nullptr};
const std::vector<unsigned char> *output = run_command(command, &return_code);
delete output;
if (!is_exit_status_success(return_code)) {
WARN("Unable To Open URL: %s", url.c_str());
}
}

View File

@ -39,7 +39,7 @@ int reborn_get_log_fd() {
void reborn_set_log(const int fd) {
// Set Variable
log_fd = -1;
set_and_print_env(_MCPI_LOG_FD_ENV, std::to_string(fd).c_str());
set_and_print_env(_MCPI_LOG_FD_ENV, fd >= 0 ? std::to_string(fd).c_str() : nullptr);
}
// Debug Logging

View File

@ -2,8 +2,4 @@
#include <string>
#define CHANGELOG_FILE "CHANGELOG.md"
extern "C" {
void open_url(const std::string &url);
}
#define CHANGELOG_FILE "CHANGELOG.md"

View File

@ -9,7 +9,6 @@
#include <streambuf>
#include <GLES/gl.h>
#include <SDL/SDL.h>
#include <media-layer/core.h>
#include <libreborn/libreborn.h>
@ -209,7 +208,7 @@ static AppPlatform_readAssetFile_return_value AppPlatform_readAssetFile_injectio
return ret;
}
// Read File
std::streamoff len = stream.tellg();
const std::streamoff len = stream.tellg();
char *buf = new char[len];
ALLOC_CHECK(buf);
stream.seekg(0, std::ifstream::beg);

View File

@ -38,29 +38,6 @@ static std::string extra_version_info =
;
static std::string extra_version_info_full = !extra_version_info.empty() ? (" (" + extra_version_info + ")") : "";
// Profile Directory
static std::string profile_directory_suffix =
#ifdef MCPI_IS_FLATPAK_BUILD
"/.var/app/" MCPI_APP_ID +
#endif
std::string(get_home_subdirectory_for_game_data())
;
static std::string get_profile_directory_url() {
std::string directory;
if (getenv(MCPI_PROFILE_DIRECTORY_ENV) != nullptr) {
// Using Custom Directory
directory = home_get();
} else {
// Determine Proper Directory
const char *home = getenv("HOME");
if (home == nullptr) {
IMPOSSIBLE();
}
directory = home + profile_directory_suffix;
}
return std::string("file://") + directory;
}
// Info Data
struct info_line {
std::string (*get_text)();
@ -80,7 +57,7 @@ static info_line info[] = {
.get_text = []() {
return std::string("Profile Directory");
},
.button_url = get_profile_directory_url(),
.button_url = std::string("file://") + home_get(),
.button_text = "Open"
},
{
@ -157,17 +134,6 @@ static void position_info(Font *font, const int width, const int height) {
}
}
// Open URL
void open_url(const std::string &url) {
int return_code;
const char *command[] = {"xdg-open", url.c_str(), nullptr};
const std::vector<unsigned char> *output = run_command(command, &return_code);
delete output;
if (!is_exit_status_success(return_code)) {
WARN("Unable To Open URL: %s", url.c_str());
}
}
// Create VTable
struct InfoScreen final : CustomScreen {
// Buttons

View File

@ -59,8 +59,7 @@ run_build() {
"libxinerama-dev:$1" \
"libxrandr-dev:$1" \
"libxext-dev:$1" \
`# Zenity Dependencies` \
"libgtk-3-dev:$1" \
`# QEMU Dependencies` \
"libglib2.0-dev:$1" \
`# AppStream Verification` \
appstream