New Crash Report UI
This commit is contained in:
parent
7f9d1d843e
commit
d3b70878be
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
4
dependencies/CMakeLists.txt
vendored
4
dependencies/CMakeLists.txt
vendored
@ -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)
|
||||
|
20
dependencies/zenity/CMakeLists.txt
vendored
20
dependencies/zenity/CMakeLists.txt
vendored
@ -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
dependencies/zenity/src
vendored
1
dependencies/zenity/src
vendored
@ -1 +0,0 @@
|
||||
Subproject commit a7496461161c917878d58131711425e7c8e59436
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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()) {
|
||||
|
@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
std::string get_logs_folder();
|
||||
void setup_logger();
|
||||
void show_report(const char *log_filename);
|
@ -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());
|
||||
|
@ -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]));
|
||||
}
|
||||
}
|
@ -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
46
launcher/src/util/env.cpp
Normal 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());
|
||||
}
|
@ -9,3 +9,6 @@ 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 setup_path();
|
||||
void setup_home();
|
@ -29,3 +29,6 @@ bool is_exit_status_success(int status);
|
||||
|
||||
// Get Exit Status String
|
||||
std::string get_exit_status_string(int status);
|
||||
|
||||
// Open URL
|
||||
void open_url(const std::string &url);
|
@ -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());
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -3,7 +3,3 @@
|
||||
#include <string>
|
||||
|
||||
#define CHANGELOG_FILE "CHANGELOG.md"
|
||||
|
||||
extern "C" {
|
||||
void open_url(const std::string &url);
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user