impr: Added comments everywhere in main application
This commit is contained in:
parent
279e085887
commit
21dc65f42a
@ -40,9 +40,7 @@ namespace hex {
|
||||
void exitGLFW();
|
||||
void exitImGui();
|
||||
|
||||
friend void *ImHexSettingsHandler_ReadOpenFn(ImGuiContext *ctx, ImGuiSettingsHandler *, const char *);
|
||||
friend void ImHexSettingsHandler_ReadLine(ImGuiContext *, ImGuiSettingsHandler *handler, void *, const char *line);
|
||||
friend void ImHexSettingsHandler_WriteAll(ImGuiContext *ctx, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf);
|
||||
void registerEventHandlers();
|
||||
|
||||
GLFWwindow *m_window = nullptr;
|
||||
|
||||
|
@ -90,29 +90,34 @@ namespace hex::init {
|
||||
}
|
||||
|
||||
bool WindowSplash::loop() {
|
||||
// Load splash screen image from romfs
|
||||
auto splash = romfs::get("splash.png");
|
||||
ImGui::Texture splashTexture = ImGui::Texture(reinterpret_cast<const ImU8 *>(splash.data()), splash.size());
|
||||
|
||||
// If the image couldn't be loaded correctly, something went wrong during the build process
|
||||
// Close the application since this would lead to errors later on anyway.
|
||||
if (!splashTexture.isValid()) {
|
||||
log::fatal("Could not load splash screen image!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Launch init tasks in background
|
||||
auto tasksSucceeded = processTasksAsync();
|
||||
|
||||
auto scale = ImHexApi::System::getGlobalScale();
|
||||
|
||||
// Splash window rendering loop
|
||||
while (!glfwWindowShouldClose(this->m_window)) {
|
||||
glfwPollEvents();
|
||||
|
||||
// Start a new ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Draw the splash screen background
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
{
|
||||
std::lock_guard guard(this->m_progressMutex);
|
||||
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
drawList->AddImage(splashTexture, ImVec2(0, 0), splashTexture.getSize() * scale);
|
||||
|
||||
@ -123,11 +128,16 @@ namespace hex::init {
|
||||
#else
|
||||
drawList->AddText(ImVec2(15, 140) * scale, ImColor(0xFF, 0xFF, 0xFF, 0xFF), hex::format("{0}", IMHEX_VERSION).c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Draw the task progress bar
|
||||
{
|
||||
std::lock_guard guard(this->m_progressMutex);
|
||||
drawList->AddRectFilled(ImVec2(0, splashTexture.getSize().y - 5) * scale, ImVec2(splashTexture.getSize().x * this->m_progress, splashTexture.getSize().y) * scale, 0xFFFFFFFF);
|
||||
drawList->AddText(ImVec2(15, splashTexture.getSize().y - 25) * scale, ImColor(0xFF, 0xFF, 0xFF, 0xFF), hex::format("[{}] {}...", "|/-\\"[ImU32(ImGui::GetTime() * 15) % 4], this->m_currTaskName).c_str());
|
||||
}
|
||||
|
||||
// Render the frame
|
||||
ImGui::Render();
|
||||
int displayWidth, displayHeight;
|
||||
glfwGetFramebufferSize(this->m_window, &displayWidth, &displayHeight);
|
||||
@ -138,6 +148,7 @@ namespace hex::init {
|
||||
|
||||
glfwSwapBuffers(this->m_window);
|
||||
|
||||
// Check if all background tasks have finished so the splash screen can be closed
|
||||
if (tasksSucceeded.wait_for(0s) == std::future_status::ready) {
|
||||
return tasksSucceeded.get();
|
||||
}
|
||||
@ -147,20 +158,25 @@ namespace hex::init {
|
||||
}
|
||||
|
||||
static void centerWindow(GLFWwindow *window) {
|
||||
// Get the primary monitor
|
||||
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
||||
if (!monitor)
|
||||
return;
|
||||
|
||||
// Get information about the monitor
|
||||
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
|
||||
if (!mode)
|
||||
return;
|
||||
|
||||
// Get the position of the monitor's viewport on the virtual screen
|
||||
int monitorX, monitorY;
|
||||
glfwGetMonitorPos(monitor, &monitorX, &monitorY);
|
||||
|
||||
// Get the window size
|
||||
int windowWidth, windowHeight;
|
||||
glfwGetWindowSize(window, &windowWidth, &windowHeight);
|
||||
|
||||
// Center the splash screen on the monitor
|
||||
glfwSetWindowPos(window, monitorX + (mode->width - windowWidth) / 2, monitorY + (mode->height - windowHeight) / 2);
|
||||
}
|
||||
|
||||
@ -174,22 +190,25 @@ namespace hex::init {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Configure used OpenGL version
|
||||
#if defined(OS_MACOS)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
|
||||
#else
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
#endif
|
||||
|
||||
// Make splash screen non-resizable, undecorated and transparent
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
|
||||
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
|
||||
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_FLOATING, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
|
||||
|
||||
// Create the splash screen window
|
||||
this->m_window = glfwCreateWindow(1, 400, "Starting ImHex...", nullptr, nullptr);
|
||||
if (this->m_window == nullptr) {
|
||||
log::fatal("Failed to create GLFW window!");
|
||||
@ -223,6 +242,7 @@ namespace hex::init {
|
||||
}
|
||||
|
||||
void WindowSplash::initImGui() {
|
||||
// Initialize ImGui
|
||||
IMGUI_CHECKVERSION();
|
||||
GImGui = ImGui::CreateContext();
|
||||
ImGui::StyleColorsDark();
|
||||
@ -239,31 +259,36 @@ namespace hex::init {
|
||||
|
||||
ImGui::GetStyle().ScaleAllSizes(ImHexApi::System::getGlobalScale());
|
||||
|
||||
io.Fonts->Clear();
|
||||
// Load fonts necessary for the splash screen
|
||||
{
|
||||
io.Fonts->Clear();
|
||||
|
||||
ImFontConfig cfg;
|
||||
cfg.OversampleH = cfg.OversampleV = 1, cfg.PixelSnapH = true;
|
||||
cfg.SizePixels = 13.0_scaled;
|
||||
io.Fonts->AddFontDefault(&cfg);
|
||||
ImFontConfig cfg;
|
||||
cfg.OversampleH = cfg.OversampleV = 1, cfg.PixelSnapH = true;
|
||||
cfg.SizePixels = 13.0_scaled;
|
||||
io.Fonts->AddFontDefault(&cfg);
|
||||
|
||||
cfg.MergeMode = true;
|
||||
cfg.MergeMode = true;
|
||||
|
||||
ImWchar fontAwesomeRange[] = {
|
||||
ICON_MIN_FA, ICON_MAX_FA, 0
|
||||
};
|
||||
std::uint8_t *px;
|
||||
int w, h;
|
||||
io.Fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data, font_awesome_compressed_size, 11.0_scaled, &cfg, fontAwesomeRange);
|
||||
io.Fonts->GetTexDataAsRGBA32(&px, &w, &h);
|
||||
ImWchar fontAwesomeRange[] = {
|
||||
ICON_MIN_FA, ICON_MAX_FA, 0
|
||||
};
|
||||
std::uint8_t *px;
|
||||
int w, h;
|
||||
io.Fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data, font_awesome_compressed_size, 11.0_scaled, &cfg, fontAwesomeRange);
|
||||
io.Fonts->GetTexDataAsRGBA32(&px, &w, &h);
|
||||
|
||||
// Create new font atlas
|
||||
GLuint tex;
|
||||
glGenTextures(1, &tex);
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA8, GL_UNSIGNED_INT, px);
|
||||
io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(tex));
|
||||
// Create new font atlas
|
||||
GLuint tex;
|
||||
glGenTextures(1, &tex);
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA8, GL_UNSIGNED_INT, px);
|
||||
io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(tex));
|
||||
}
|
||||
|
||||
// Don't save window settings for the splash screen
|
||||
io.IniFilename = nullptr;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <hex/helpers/net.hpp>
|
||||
#include <hex/helpers/fs.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <hex/helpers/file.hpp>
|
||||
|
||||
#include <fonts/fontawesome_font.h>
|
||||
#include <fonts/codicons_font.h>
|
||||
@ -27,23 +28,30 @@ namespace hex::init {
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
static bool checkForUpdates() {
|
||||
// documentation of the value above the setting definition
|
||||
int showCheckForUpdates = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.check_for_updates", 2);
|
||||
|
||||
// Check if we should check for updates
|
||||
if (showCheckForUpdates == 1){
|
||||
hex::Net net;
|
||||
|
||||
// Query the GitHub API for the latest release version
|
||||
auto releases = net.getJson(GitHubApiURL + "/releases/latest"s, 2000).get();
|
||||
if (releases.code != 200)
|
||||
return false;
|
||||
|
||||
// Check if the response is valid
|
||||
if (!releases.body.contains("tag_name") || !releases.body["tag_name"].is_string())
|
||||
return false;
|
||||
|
||||
// Convert the current version string to a format that can be compared to the latest release
|
||||
auto versionString = std::string(IMHEX_VERSION);
|
||||
size_t versionLength = std::min(versionString.find_first_of('-'), versionString.length());
|
||||
auto currVersion = "v" + versionString.substr(0, versionLength);
|
||||
|
||||
// Get the latest release version string
|
||||
auto latestVersion = releases.body["tag_name"].get<std::string_view>();
|
||||
|
||||
// Check if the latest release is different from the current version
|
||||
if (latestVersion != currVersion)
|
||||
ImHexApi::System::impl::addInitArgument("update-available", latestVersion.data());
|
||||
|
||||
@ -82,7 +90,7 @@ namespace hex::init {
|
||||
|
||||
using enum fs::ImHexPath;
|
||||
|
||||
// Create all folders
|
||||
// Try to create all default directories
|
||||
for (u32 path = 0; path < u32(fs::ImHexPath::END); path++) {
|
||||
for (auto &folder : fs::getDefaultPaths(static_cast<fs::ImHexPath>(path), true)) {
|
||||
try {
|
||||
@ -105,11 +113,13 @@ namespace hex::init {
|
||||
|
||||
const auto &fontFile = ImHexApi::System::getCustomFontPath();
|
||||
|
||||
// Setup basic font configuration
|
||||
auto fonts = IM_NEW(ImFontAtlas)();
|
||||
ImFontConfig cfg = {};
|
||||
cfg.OversampleH = cfg.OversampleV = 1, cfg.PixelSnapH = true;
|
||||
cfg.SizePixels = fontSize;
|
||||
|
||||
// Configure font glyph ranges that should be loaded from the default font and unifont
|
||||
ImVector<ImWchar> ranges;
|
||||
{
|
||||
ImFontGlyphRangesBuilder glyphRangesBuilder;
|
||||
@ -131,37 +141,51 @@ namespace hex::init {
|
||||
glyphRangesBuilder.BuildRanges(&ranges);
|
||||
}
|
||||
|
||||
// Glyph range for font awesome icons
|
||||
ImWchar fontAwesomeRange[] = {
|
||||
ICON_MIN_FA, ICON_MAX_FA, 0
|
||||
};
|
||||
|
||||
// Glyph range for codicons icons
|
||||
ImWchar codiconsRange[] = {
|
||||
ICON_MIN_VS, ICON_MAX_VS, 0
|
||||
};
|
||||
|
||||
// Load main font
|
||||
// If a custom font has been specified, load it, otherwise load the default ImGui font
|
||||
if (fontFile.empty()) {
|
||||
// Load default font if no custom one has been specified
|
||||
|
||||
fonts->Clear();
|
||||
fonts->AddFontDefault(&cfg);
|
||||
} else {
|
||||
// Load custom font
|
||||
fonts->AddFontFromFileTTF(hex::toUTF8String(fontFile).c_str(), 0, &cfg, ranges.Data);
|
||||
}
|
||||
|
||||
// Merge all fonts into one big font atlas
|
||||
cfg.MergeMode = true;
|
||||
|
||||
// Add font awesome and codicons icons to font atlas
|
||||
fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data, font_awesome_compressed_size, 0, &cfg, fontAwesomeRange);
|
||||
fonts->AddFontFromMemoryCompressedTTF(codicons_compressed_data, codicons_compressed_size, 0, &cfg, codiconsRange);
|
||||
|
||||
// Add unifont if unicode support is enabled
|
||||
if (loadUnicode)
|
||||
fonts->AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, 0, &cfg, ranges.Data);
|
||||
|
||||
// Try to build the font atlas
|
||||
if (!fonts->Build()) {
|
||||
// The main reason the font atlas failed to build is that the font is too big for the GPU to handle
|
||||
// If unicode support is enabled, therefor try to load the font atlas without unicode support
|
||||
// If that still didn't work, there's probably something else going on with the graphics drivers
|
||||
// Especially Intel GPU drivers are known to have various bugs
|
||||
|
||||
if (loadUnicode) {
|
||||
log::error("Failed to build font atlas! Disabling Unicode support.");
|
||||
ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.enable_unicode", false);
|
||||
IM_DELETE(fonts);
|
||||
|
||||
// Disable unicode support in settings
|
||||
ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.enable_unicode", false);
|
||||
|
||||
// Try to load the font atlas again
|
||||
return loadFontsImpl(false);
|
||||
} else {
|
||||
log::error("Failed to build font atlas! Check your Graphics driver!");
|
||||
@ -169,6 +193,7 @@ namespace hex::init {
|
||||
}
|
||||
}
|
||||
|
||||
// Configure ImGui to use the font atlas
|
||||
View::setFontAtlas(fonts);
|
||||
View::setFontConfig(cfg);
|
||||
|
||||
@ -180,6 +205,10 @@ namespace hex::init {
|
||||
}
|
||||
|
||||
bool deleteSharedData() {
|
||||
// This function is called when ImHex is closed. It deletes all shared data that was created by plugins
|
||||
// This is a bit of a hack but necessary because when ImHex gets closed, all plugins are unloaded in order for
|
||||
// destructors to be called correctly. To prevent crashes when ImHex exits, we need to delete all shared data
|
||||
|
||||
EventManager::clear();
|
||||
|
||||
while (ImHexApi::Provider::isValid())
|
||||
@ -255,12 +284,15 @@ namespace hex::init {
|
||||
}
|
||||
|
||||
bool loadPlugins() {
|
||||
// Load plugins
|
||||
for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Plugins)) {
|
||||
PluginManager::load(dir);
|
||||
}
|
||||
|
||||
// Get loaded plugins
|
||||
auto &plugins = PluginManager::getPlugins();
|
||||
|
||||
// If no plugins were loaded, ImHex wasn't installed properly. This will trigger an error popup later on
|
||||
if (plugins.empty()) {
|
||||
log::error("No plugins found!");
|
||||
|
||||
@ -269,6 +301,7 @@ namespace hex::init {
|
||||
}
|
||||
|
||||
const auto shouldLoadPlugin = [executablePath = hex::fs::getExecutablePath()](const Plugin &plugin) {
|
||||
// In debug builds, ignore all plugins that are not part of the executable directory
|
||||
#if !defined(DEBUG)
|
||||
return true;
|
||||
#endif
|
||||
@ -276,12 +309,14 @@ namespace hex::init {
|
||||
if (!executablePath.has_value())
|
||||
return true;
|
||||
|
||||
// In debug builds, ignore all plugins that are not part of the executable directory
|
||||
// Check if the plugin is somewhere in the same directory tree as the executable
|
||||
return !std::fs::relative(plugin.getPath(), executablePath->parent_path()).string().starts_with("..");
|
||||
};
|
||||
|
||||
u32 builtinPlugins = 0;
|
||||
u32 loadErrors = 0;
|
||||
|
||||
// Load the builtin plugin first, so it can initialize everything that's necessary for ImHex to work
|
||||
for (const auto &plugin : plugins) {
|
||||
if (!plugin.isBuiltinPlugin()) continue;
|
||||
|
||||
@ -290,15 +325,18 @@ namespace hex::init {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure there's only one built-in plugin
|
||||
builtinPlugins++;
|
||||
if (builtinPlugins > 1) continue;
|
||||
|
||||
// Initialize the plugin
|
||||
if (!plugin.initializePlugin()) {
|
||||
log::error("Failed to initialize plugin {}", hex::toUTF8String(plugin.getPath().filename()));
|
||||
loadErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
// Load all other plugins
|
||||
for (const auto &plugin : plugins) {
|
||||
if (plugin.isBuiltinPlugin()) continue;
|
||||
|
||||
@ -307,18 +345,22 @@ namespace hex::init {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Initialize the plugin
|
||||
if (!plugin.initializePlugin()) {
|
||||
log::error("Failed to initialize plugin {}", hex::toUTF8String(plugin.getPath().filename()));
|
||||
loadErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
// If no plugins were loaded successfully, ImHex wasn't installed properly. This will trigger an error popup later on
|
||||
if (loadErrors == plugins.size()) {
|
||||
log::error("No plugins loaded successfully!");
|
||||
ImHexApi::System::impl::addInitArgument("no-plugins");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ImHex requires exactly one built-in plugin
|
||||
// If no built-in plugin or more than one was found, something's wrong and we can't continue
|
||||
if (builtinPlugins == 0) {
|
||||
log::error("Built-in plugin not found!");
|
||||
ImHexApi::System::impl::addInitArgument("no-builtin-plugin");
|
||||
@ -340,8 +382,11 @@ namespace hex::init {
|
||||
|
||||
bool loadSettings() {
|
||||
try {
|
||||
// Try to load settings from file
|
||||
ContentRegistry::Settings::load();
|
||||
} catch (std::exception &e) {
|
||||
// If that fails, create a new settings file
|
||||
|
||||
log::error("Failed to load configuration! {}", e.what());
|
||||
|
||||
ContentRegistry::Settings::clear();
|
||||
|
@ -14,8 +14,6 @@ int main(int argc, char **argv, char **envp) {
|
||||
using namespace hex;
|
||||
ImHexApi::System::impl::setProgramArguments(argc, argv, envp);
|
||||
|
||||
bool shouldRestart = false;
|
||||
|
||||
// Check if ImHex is installed in portable mode
|
||||
{
|
||||
if (const auto executablePath = fs::getExecutablePath(); executablePath.has_value()) {
|
||||
@ -26,7 +24,9 @@ int main(int argc, char **argv, char **envp) {
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldRestart = false;
|
||||
do {
|
||||
// Register a event to handle restarting of ImHex
|
||||
EventManager::subscribe<RequestRestartImHex>([&]{ shouldRestart = true; });
|
||||
shouldRestart = false;
|
||||
|
||||
@ -38,15 +38,17 @@ int main(int argc, char **argv, char **envp) {
|
||||
|
||||
init::WindowSplash splashWindow;
|
||||
|
||||
// Add initialization tasks to run
|
||||
TaskManager::init();
|
||||
for (const auto &[name, task, async] : init::getInitTasks())
|
||||
splashWindow.addStartupTask(name, task, async);
|
||||
|
||||
// Draw the splash window while tasks are running
|
||||
if (!splashWindow.loop())
|
||||
ImHexApi::System::getInitArguments().insert({ "tasks-failed", {} });
|
||||
}
|
||||
|
||||
// Clean up
|
||||
// Clean up everything after the main window is closed
|
||||
ON_SCOPE_EXIT {
|
||||
for (const auto &[name, task, async] : init::getExitTasks())
|
||||
task();
|
||||
@ -65,7 +67,7 @@ int main(int argc, char **argv, char **envp) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Render the main window
|
||||
window.loop();
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ namespace hex {
|
||||
setenv("LD_LIBRARY_PATH", hex::format("{};{}", hex::getEnvironmentVariable("LD_LIBRARY_PATH").value_or(""), path.string().c_str()).c_str(), true);
|
||||
}
|
||||
|
||||
// Redirect stdout to log file if we're not running in a terminal
|
||||
if (!isatty(STDOUT_FILENO)) {
|
||||
log::redirectToFile();
|
||||
}
|
||||
@ -39,6 +40,7 @@ namespace hex {
|
||||
std::array<char, 128> buffer = { 0 };
|
||||
std::string result;
|
||||
|
||||
// Ask GNOME for the current theme
|
||||
// TODO: In the future maybe support more DEs instead of just GNOME
|
||||
FILE *pipe = popen("gsettings get org.gnome.desktop.interface gtk-theme 2>&1", "r");
|
||||
if (pipe == nullptr) return;
|
||||
|
@ -23,6 +23,7 @@ namespace hex {
|
||||
setenv("LD_LIBRARY_PATH", hex::format("{};{}", hex::getEnvironmentVariable("LD_LIBRARY_PATH").value_or(""), path.string().c_str()).c_str(), true);
|
||||
}
|
||||
|
||||
// Redirect stdout to log file if we're not running in a terminal
|
||||
if (!isatty(STDOUT_FILENO)) {
|
||||
log::redirectToFile();
|
||||
}
|
||||
|
@ -34,10 +34,12 @@ namespace hex {
|
||||
static ImGuiMouseCursor g_mouseCursorIcon;
|
||||
static Microsoft::WRL::ComPtr<ITaskbarList4> g_taskbarList;
|
||||
|
||||
// Custom Window procedure for receiving OS events
|
||||
static LRESULT commonWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (uMsg) {
|
||||
case WM_COPYDATA:
|
||||
{
|
||||
case WM_COPYDATA: {
|
||||
// Handle opening files in existing instance
|
||||
|
||||
auto message = reinterpret_cast<COPYDATASTRUCT *>(lParam);
|
||||
if (message == nullptr) break;
|
||||
|
||||
@ -49,8 +51,8 @@ namespace hex {
|
||||
EventManager::post<RequestOpenFile>(path);
|
||||
break;
|
||||
}
|
||||
case WM_SETTINGCHANGE:
|
||||
{
|
||||
case WM_SETTINGCHANGE: {
|
||||
// Handle Windows theme changes
|
||||
if (lParam == 0) break;
|
||||
|
||||
if (LPCTSTR(lParam) == std::string_view("ImmersiveColorSet")) {
|
||||
@ -66,120 +68,124 @@ namespace hex {
|
||||
return CallWindowProc((WNDPROC)g_oldWndProc, hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
// Custom window procedure for borderless window
|
||||
static LRESULT borderlessWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (uMsg) {
|
||||
case WM_NCACTIVATE:
|
||||
case WM_NCPAINT:
|
||||
// Handle Windows Aero Snap
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
case WM_NCCALCSIZE:
|
||||
{
|
||||
RECT &rect = *reinterpret_cast<RECT *>(lParam);
|
||||
RECT client = rect;
|
||||
case WM_NCCALCSIZE: {
|
||||
// Handle window resizing
|
||||
|
||||
CallWindowProc((WNDPROC)g_oldWndProc, hwnd, uMsg, wParam, lParam);
|
||||
RECT &rect = *reinterpret_cast<RECT *>(lParam);
|
||||
RECT client = rect;
|
||||
|
||||
if (IsMaximized(hwnd)) {
|
||||
WINDOWINFO windowInfo = { };
|
||||
windowInfo.cbSize = sizeof(WINDOWINFO);
|
||||
CallWindowProc((WNDPROC)g_oldWndProc, hwnd, uMsg, wParam, lParam);
|
||||
|
||||
GetWindowInfo(hwnd, &windowInfo);
|
||||
rect = RECT {
|
||||
.left = static_cast<LONG>(client.left + windowInfo.cyWindowBorders),
|
||||
.top = static_cast<LONG>(client.top + windowInfo.cyWindowBorders),
|
||||
.right = static_cast<LONG>(client.right - windowInfo.cyWindowBorders),
|
||||
.bottom = static_cast<LONG>(client.bottom - windowInfo.cyWindowBorders) + 1
|
||||
};
|
||||
} else {
|
||||
rect = client;
|
||||
}
|
||||
if (IsMaximized(hwnd)) {
|
||||
WINDOWINFO windowInfo = { };
|
||||
windowInfo.cbSize = sizeof(WINDOWINFO);
|
||||
|
||||
return 0;
|
||||
}
|
||||
case WM_SETCURSOR:
|
||||
{
|
||||
auto cursorPos = LOWORD(lParam);
|
||||
|
||||
switch (cursorPos) {
|
||||
case HTRIGHT:
|
||||
case HTLEFT:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_ResizeEW;
|
||||
break;
|
||||
case HTTOP:
|
||||
case HTBOTTOM:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_ResizeNS;
|
||||
break;
|
||||
case HTTOPLEFT:
|
||||
case HTBOTTOMRIGHT:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_ResizeNWSE;
|
||||
break;
|
||||
case HTTOPRIGHT:
|
||||
case HTBOTTOMLEFT:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_ResizeNESW;
|
||||
break;
|
||||
case HTCAPTION:
|
||||
case HTCLIENT:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_None;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
case WM_NCHITTEST:
|
||||
{
|
||||
POINT cursor = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
||||
|
||||
const POINT border {
|
||||
static_cast<LONG>((::GetSystemMetrics(SM_CXFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER)) * ImHexApi::System::getGlobalScale() / 1.5F),
|
||||
static_cast<LONG>((::GetSystemMetrics(SM_CYFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER)) * ImHexApi::System::getGlobalScale() / 1.5F)
|
||||
GetWindowInfo(hwnd, &windowInfo);
|
||||
rect = RECT {
|
||||
.left = static_cast<LONG>(client.left + windowInfo.cyWindowBorders),
|
||||
.top = static_cast<LONG>(client.top + windowInfo.cyWindowBorders),
|
||||
.right = static_cast<LONG>(client.right - windowInfo.cyWindowBorders),
|
||||
.bottom = static_cast<LONG>(client.bottom - windowInfo.cyWindowBorders) + 1
|
||||
};
|
||||
|
||||
RECT window;
|
||||
if (!::GetWindowRect(hwnd, &window)) {
|
||||
return HTNOWHERE;
|
||||
}
|
||||
|
||||
constexpr static auto RegionClient = 0b0000;
|
||||
constexpr static auto RegionLeft = 0b0001;
|
||||
constexpr static auto RegionRight = 0b0010;
|
||||
constexpr static auto RegionTop = 0b0100;
|
||||
constexpr static auto RegionBottom = 0b1000;
|
||||
|
||||
const auto result =
|
||||
RegionLeft * (cursor.x < (window.left + border.x)) |
|
||||
RegionRight * (cursor.x >= (window.right - border.x)) |
|
||||
RegionTop * (cursor.y < (window.top + border.y)) |
|
||||
RegionBottom * (cursor.y >= (window.bottom - border.y));
|
||||
|
||||
if (result != 0 && (ImGui::IsItemHovered() || ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId)))
|
||||
break;
|
||||
|
||||
switch (result) {
|
||||
case RegionLeft:
|
||||
return HTLEFT;
|
||||
case RegionRight:
|
||||
return HTRIGHT;
|
||||
case RegionTop:
|
||||
return HTTOP;
|
||||
case RegionBottom:
|
||||
return HTBOTTOM;
|
||||
case RegionTop | RegionLeft:
|
||||
return HTTOPLEFT;
|
||||
case RegionTop | RegionRight:
|
||||
return HTTOPRIGHT;
|
||||
case RegionBottom | RegionLeft:
|
||||
return HTBOTTOMLEFT;
|
||||
case RegionBottom | RegionRight:
|
||||
return HTBOTTOMRIGHT;
|
||||
case RegionClient:
|
||||
default:
|
||||
if ((cursor.y < (window.top + g_titleBarHeight * 2)) && !(ImGui::IsAnyItemHovered() || ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId)))
|
||||
return HTCAPTION;
|
||||
else break;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
rect = client;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
case WM_SETCURSOR: {
|
||||
// Handle mouse cursor icon
|
||||
auto cursorPos = LOWORD(lParam);
|
||||
|
||||
switch (cursorPos) {
|
||||
case HTRIGHT:
|
||||
case HTLEFT:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_ResizeEW;
|
||||
break;
|
||||
case HTTOP:
|
||||
case HTBOTTOM:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_ResizeNS;
|
||||
break;
|
||||
case HTTOPLEFT:
|
||||
case HTBOTTOMRIGHT:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_ResizeNWSE;
|
||||
break;
|
||||
case HTTOPRIGHT:
|
||||
case HTBOTTOMLEFT:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_ResizeNESW;
|
||||
break;
|
||||
case HTCAPTION:
|
||||
case HTCLIENT:
|
||||
g_mouseCursorIcon = ImGuiMouseCursor_None;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
case WM_NCHITTEST: {
|
||||
// Handle window resizing and moving
|
||||
|
||||
POINT cursor = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
||||
|
||||
const POINT border {
|
||||
static_cast<LONG>((::GetSystemMetrics(SM_CXFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER)) * ImHexApi::System::getGlobalScale() / 1.5F),
|
||||
static_cast<LONG>((::GetSystemMetrics(SM_CYFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER)) * ImHexApi::System::getGlobalScale() / 1.5F)
|
||||
};
|
||||
|
||||
RECT window;
|
||||
if (!::GetWindowRect(hwnd, &window)) {
|
||||
return HTNOWHERE;
|
||||
}
|
||||
|
||||
constexpr static auto RegionClient = 0b0000;
|
||||
constexpr static auto RegionLeft = 0b0001;
|
||||
constexpr static auto RegionRight = 0b0010;
|
||||
constexpr static auto RegionTop = 0b0100;
|
||||
constexpr static auto RegionBottom = 0b1000;
|
||||
|
||||
const auto result =
|
||||
RegionLeft * (cursor.x < (window.left + border.x)) |
|
||||
RegionRight * (cursor.x >= (window.right - border.x)) |
|
||||
RegionTop * (cursor.y < (window.top + border.y)) |
|
||||
RegionBottom * (cursor.y >= (window.bottom - border.y));
|
||||
|
||||
if (result != 0 && (ImGui::IsItemHovered() || ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId)))
|
||||
break;
|
||||
|
||||
switch (result) {
|
||||
case RegionLeft:
|
||||
return HTLEFT;
|
||||
case RegionRight:
|
||||
return HTRIGHT;
|
||||
case RegionTop:
|
||||
return HTTOP;
|
||||
case RegionBottom:
|
||||
return HTBOTTOM;
|
||||
case RegionTop | RegionLeft:
|
||||
return HTTOPLEFT;
|
||||
case RegionTop | RegionRight:
|
||||
return HTTOPRIGHT;
|
||||
case RegionBottom | RegionLeft:
|
||||
return HTBOTTOMLEFT;
|
||||
case RegionBottom | RegionRight:
|
||||
return HTBOTTOMRIGHT;
|
||||
case RegionClient:
|
||||
default:
|
||||
if ((cursor.y < (window.top + g_titleBarHeight * 2)) && !(ImGui::IsAnyItemHovered() || ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId)))
|
||||
return HTCAPTION;
|
||||
else break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -199,7 +205,6 @@ namespace hex {
|
||||
|
||||
// Attach to parent console if one exists
|
||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||
|
||||
// Redirect cin, cout and cerr to that console
|
||||
freopen("CONIN$", "r", stdin);
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
@ -230,24 +235,32 @@ namespace hex {
|
||||
|
||||
HANDLE globalMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, UniqueMutexId);
|
||||
if (!globalMutex) {
|
||||
// If no ImHex instance is running, create a new global mutex
|
||||
globalMutex = CreateMutex(nullptr, FALSE, UniqueMutexId);
|
||||
} else {
|
||||
// If an ImHex instance is already running, send the file path to it and exit
|
||||
|
||||
if (ImHexApi::System::getProgramArguments().argc > 1) {
|
||||
// Find the ImHex Window and send the file path as a message to it
|
||||
::EnumWindows([](HWND hWnd, LPARAM) -> BOOL {
|
||||
auto &programArgs = ImHexApi::System::getProgramArguments();
|
||||
|
||||
// Get the window name
|
||||
auto length = ::GetWindowTextLength(hWnd);
|
||||
std::string windowName(length + 1, '\x00');
|
||||
::GetWindowText(hWnd, windowName.data(), windowName.size());
|
||||
|
||||
// Check if the window is visible and if it's an ImHex window
|
||||
if (::IsWindowVisible(hWnd) && length != 0) {
|
||||
if (windowName.starts_with("ImHex")) {
|
||||
// Create the message
|
||||
COPYDATASTRUCT message = {
|
||||
.dwData = 0,
|
||||
.cbData = static_cast<DWORD>(std::strlen(programArgs.argv[1])) + 1,
|
||||
.lpData = programArgs.argv[1]
|
||||
};
|
||||
|
||||
// Send the message
|
||||
SendMessage(hWnd, WM_COPYDATA, reinterpret_cast<WPARAM>(hWnd), reinterpret_cast<LPARAM>(&message));
|
||||
|
||||
return FALSE;
|
||||
@ -255,8 +268,7 @@ namespace hex {
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
},
|
||||
0);
|
||||
}, 0);
|
||||
|
||||
std::exit(0);
|
||||
}
|
||||
@ -271,7 +283,7 @@ namespace hex {
|
||||
|
||||
ImGui_ImplGlfw_SetBorderlessWindowMode(borderlessWindowMode);
|
||||
|
||||
|
||||
// Set up the correct window procedure based on the borderless window mode state
|
||||
if (borderlessWindowMode) {
|
||||
g_oldWndProc = ::SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)borderlessWindowProc);
|
||||
|
||||
@ -287,7 +299,7 @@ namespace hex {
|
||||
g_oldWndProc = ::SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)commonWindowProc);
|
||||
}
|
||||
|
||||
// Catch heap corruption
|
||||
// Add a custom exception handler to detect heap corruptions
|
||||
{
|
||||
::AddVectoredExceptionHandler(TRUE, [](PEXCEPTION_POINTERS exception) -> LONG {
|
||||
if ((exception->ExceptionRecord->ExceptionCode & 0xF000'0000) == 0xC000'0000) {
|
||||
@ -302,40 +314,42 @@ namespace hex {
|
||||
});
|
||||
}
|
||||
|
||||
if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {
|
||||
CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskbarList4, &g_taskbarList);
|
||||
// Set up a taskbar progress handler
|
||||
{
|
||||
if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {
|
||||
CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskbarList4, &g_taskbarList);
|
||||
}
|
||||
|
||||
EventManager::subscribe<EventSetTaskBarIconState>([hwnd](u32 state, u32 type, u32 progress){
|
||||
using enum ImHexApi::System::TaskProgressState;
|
||||
switch (ImHexApi::System::TaskProgressState(state)) {
|
||||
case Reset:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_NOPROGRESS);
|
||||
g_taskbarList->SetProgressValue(hwnd, 0, 0);
|
||||
break;
|
||||
case Flash:
|
||||
FlashWindow(hwnd, true);
|
||||
break;
|
||||
case Progress:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_INDETERMINATE);
|
||||
g_taskbarList->SetProgressValue(hwnd, progress, 100);
|
||||
break;
|
||||
}
|
||||
|
||||
using enum ImHexApi::System::TaskProgressType;
|
||||
switch (ImHexApi::System::TaskProgressType(type)) {
|
||||
case Normal:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_NORMAL);
|
||||
break;
|
||||
case Warning:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_PAUSED);
|
||||
break;
|
||||
case Error:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_ERROR);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
EventManager::subscribe<EventSetTaskBarIconState>([hwnd](u32 state, u32 type, u32 progress){
|
||||
using enum ImHexApi::System::TaskProgressState;
|
||||
switch (ImHexApi::System::TaskProgressState(state)) {
|
||||
case Reset:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_NOPROGRESS);
|
||||
g_taskbarList->SetProgressValue(hwnd, 0, 0);
|
||||
break;
|
||||
case Flash:
|
||||
FlashWindow(hwnd, true);
|
||||
break;
|
||||
case Progress:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_INDETERMINATE);
|
||||
g_taskbarList->SetProgressValue(hwnd, progress, 100);
|
||||
break;
|
||||
}
|
||||
|
||||
using enum ImHexApi::System::TaskProgressType;
|
||||
switch (ImHexApi::System::TaskProgressType(type)) {
|
||||
case Normal:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_NORMAL);
|
||||
break;
|
||||
case Warning:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_PAUSED);
|
||||
break;
|
||||
case Error:
|
||||
g_taskbarList->SetProgressState(hwnd, TBPF_ERROR);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Window::beginNativeWindowFrame() {
|
||||
@ -350,6 +364,7 @@ namespace hex {
|
||||
ImGui::SetMouseCursor(g_mouseCursorIcon);
|
||||
}
|
||||
|
||||
// Translate ImGui mouse cursors to Win32 mouse cursors
|
||||
switch (ImGui::GetMouseCursor()) {
|
||||
case ImGuiMouseCursor_Arrow:
|
||||
SetCursor(LoadCursor(nullptr, IDC_ARROW));
|
||||
@ -386,6 +401,8 @@ namespace hex {
|
||||
}
|
||||
|
||||
void Window::drawTitleBar() {
|
||||
// In borderless window mode, we draw our own title bar
|
||||
|
||||
if (!ImHexApi::System::isBorderlessWindowModeEnabled()) return;
|
||||
|
||||
auto startX = ImGui::GetCursorPosX();
|
||||
@ -399,6 +416,7 @@ namespace hex {
|
||||
|
||||
auto &titleBarButtons = ContentRegistry::Interface::getTitleBarButtons();
|
||||
|
||||
// Draw custom title bar buttons
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - buttonSize.x * (4 + titleBarButtons.size()));
|
||||
for (const auto &[icon, tooltip, callback] : titleBarButtons) {
|
||||
if (ImGui::TitleBarButton(icon.c_str(), buttonSize)) {
|
||||
@ -407,6 +425,7 @@ namespace hex {
|
||||
ImGui::InfoTooltip(LangEntry(tooltip));
|
||||
}
|
||||
|
||||
// Draw minimize, restore and maximize buttons
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - buttonSize.x * 3);
|
||||
if (ImGui::TitleBarButton(ICON_VS_CHROME_MINIMIZE, buttonSize))
|
||||
glfwIconifyWindow(this->m_window);
|
||||
@ -421,7 +440,7 @@ namespace hex {
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, 0xFF7A70F1);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, 0xFF2311E8);
|
||||
|
||||
|
||||
// Draw close button
|
||||
if (ImGui::TitleBarButton(ICON_VS_CHROME_CLOSE, buttonSize)) {
|
||||
ImHexApi::Common::closeImHex();
|
||||
}
|
||||
|
@ -41,45 +41,22 @@ namespace hex {
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
void *ImHexSettingsHandler_ReadOpenFn(ImGuiContext *ctx, ImGuiSettingsHandler *, const char *) {
|
||||
return ctx; // Unused, but the return value has to be non-null
|
||||
}
|
||||
|
||||
void ImHexSettingsHandler_ReadLine(ImGuiContext *, ImGuiSettingsHandler *, void *, const char *line) {
|
||||
for (auto &[name, view] : ContentRegistry::Views::getEntries()) {
|
||||
std::string format = view->getUnlocalizedName() + "=%d";
|
||||
sscanf(line, format.c_str(), &view->getWindowOpenState());
|
||||
}
|
||||
for (auto &[name, function, detached] : ContentRegistry::Tools::getEntries()) {
|
||||
std::string format = name + "=%d";
|
||||
sscanf(line, format.c_str(), &detached);
|
||||
}
|
||||
}
|
||||
|
||||
void ImHexSettingsHandler_WriteAll(ImGuiContext *, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf) {
|
||||
buf->appendf("[%s][General]\n", handler->TypeName);
|
||||
|
||||
for (auto &[name, view] : ContentRegistry::Views::getEntries()) {
|
||||
buf->appendf("%s=%d\n", name.c_str(), view->getWindowOpenState());
|
||||
}
|
||||
for (auto &[name, function, detached] : ContentRegistry::Tools::getEntries()) {
|
||||
buf->appendf("%s=%d\n", name.c_str(), detached);
|
||||
}
|
||||
|
||||
buf->append("\n");
|
||||
}
|
||||
|
||||
static void signalHandler(int signalNumber, std::string signalName) {
|
||||
// Custom signal handler to print various information and a stacktrace when the application crashes
|
||||
static void signalHandler(int signalNumber, const std::string &signalName) {
|
||||
log::fatal("Terminating with signal '{}' ({})", signalName, signalNumber);
|
||||
|
||||
// Trigger an event so that plugins can handle crashes
|
||||
EventManager::post<EventAbnormalTermination>(signalNumber);
|
||||
|
||||
// Detect if the crash was due to an uncaught exception
|
||||
if (std::uncaught_exceptions() > 0) {
|
||||
log::fatal("Uncaught exception thrown!");
|
||||
}
|
||||
|
||||
// Reset the signal handler to the default handler
|
||||
std::signal(signalNumber, SIG_DFL);
|
||||
|
||||
// Print stack trace
|
||||
for (const auto &stackFrame : stacktrace::getStackTrace()) {
|
||||
if (stackFrame.line == 0)
|
||||
log::fatal(" {}", stackFrame.function);
|
||||
@ -87,7 +64,7 @@ namespace hex {
|
||||
log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function);
|
||||
}
|
||||
|
||||
|
||||
// Trigger a breakpoint if we're in a debug build or raise the signal again for the default handler to handle it
|
||||
#if defined(DEBUG)
|
||||
assert(!"Debug build, triggering breakpoint");
|
||||
#else
|
||||
@ -107,6 +84,7 @@ namespace hex {
|
||||
});
|
||||
};
|
||||
|
||||
// Handle fatal error popups for errors detected during initialization
|
||||
{
|
||||
for (const auto &[argument, value] : ImHexApi::System::getInitArguments()) {
|
||||
if (argument == "no-plugins") {
|
||||
@ -119,13 +97,36 @@ namespace hex {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the window
|
||||
this->initGLFW();
|
||||
this->initImGui();
|
||||
this->setupNativeWindow();
|
||||
this->registerEventHandlers();
|
||||
|
||||
auto logoData = romfs::get("logo.png");
|
||||
this->m_logoTexture = ImGui::Texture(reinterpret_cast<const ImU8 *>(logoData.data()), logoData.size());
|
||||
|
||||
ContentRegistry::Settings::store();
|
||||
EventManager::post<EventSettingsChanged>();
|
||||
EventManager::post<EventWindowInitialized>();
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
EventManager::unsubscribe<EventProviderDeleted>(this);
|
||||
EventManager::unsubscribe<RequestCloseImHex>(this);
|
||||
EventManager::unsubscribe<RequestUpdateWindowTitle>(this);
|
||||
EventManager::unsubscribe<EventAbnormalTermination>(this);
|
||||
EventManager::unsubscribe<RequestOpenPopup>(this);
|
||||
|
||||
this->exitImGui();
|
||||
this->exitGLFW();
|
||||
}
|
||||
|
||||
void Window::registerEventHandlers() {
|
||||
// Initialize default theme
|
||||
EventManager::post<RequestChangeTheme>("Dark");
|
||||
|
||||
// Handle the close window request by telling GLFW to shut down
|
||||
EventManager::subscribe<RequestCloseImHex>(this, [this](bool noQuestions) {
|
||||
glfwSetWindowShouldClose(this->m_window, GLFW_TRUE);
|
||||
|
||||
@ -133,10 +134,13 @@ namespace hex {
|
||||
EventManager::post<EventWindowClosing>(this->m_window);
|
||||
});
|
||||
|
||||
// Handle updating the window title
|
||||
EventManager::subscribe<RequestUpdateWindowTitle>(this, [this]() {
|
||||
std::string title = "ImHex";
|
||||
|
||||
if (ProjectFile::hasPath()) {
|
||||
// If a project is open, show the project name instead of the file name
|
||||
|
||||
title += " - Project " + hex::limitStringLength(ProjectFile::getPath().stem().string(), 32);
|
||||
|
||||
if (ImHexApi::Provider::isDirty())
|
||||
@ -161,6 +165,7 @@ namespace hex {
|
||||
|
||||
constexpr static auto CrashBackupFileName = "crash_backup.hexproj";
|
||||
|
||||
// Save a backup project when the application crashes
|
||||
EventManager::subscribe<EventAbnormalTermination>(this, [this](int) {
|
||||
ImGui::SaveIniSettingsToDisk(hex::toUTF8String(this->m_imguiSettingsPath).c_str());
|
||||
|
||||
@ -173,21 +178,28 @@ namespace hex {
|
||||
}
|
||||
});
|
||||
|
||||
// Handle opening popups
|
||||
EventManager::subscribe<RequestOpenPopup>(this, [this](auto name) {
|
||||
std::scoped_lock lock(this->m_popupMutex);
|
||||
|
||||
this->m_popupsToOpen.push_back(name);
|
||||
});
|
||||
|
||||
#define HANDLE_SIGNAL(name) \
|
||||
std::signal(name, [](int signalNumber){ \
|
||||
signalHandler(signalNumber, #name); \
|
||||
});
|
||||
HANDLE_SIGNAL(SIGSEGV)
|
||||
HANDLE_SIGNAL(SIGILL)
|
||||
HANDLE_SIGNAL(SIGABRT)
|
||||
HANDLE_SIGNAL(SIGFPE)
|
||||
#undef HANDLE_SIGNAL
|
||||
// Register signal handlers
|
||||
{
|
||||
#define HANDLE_SIGNAL(name) \
|
||||
std::signal(name, [](int signalNumber){ \
|
||||
signalHandler(signalNumber, #name); \
|
||||
});
|
||||
|
||||
HANDLE_SIGNAL(SIGSEGV)
|
||||
HANDLE_SIGNAL(SIGILL)
|
||||
HANDLE_SIGNAL(SIGABRT)
|
||||
HANDLE_SIGNAL(SIGFPE)
|
||||
|
||||
#undef HANDLE_SIGNAL
|
||||
}
|
||||
|
||||
std::set_terminate([]{
|
||||
try {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
@ -200,56 +212,55 @@ namespace hex {
|
||||
}
|
||||
EventManager::post<EventAbnormalTermination>(0);
|
||||
});
|
||||
|
||||
auto logoData = romfs::get("logo.png");
|
||||
this->m_logoTexture = ImGui::Texture(reinterpret_cast<const ImU8 *>(logoData.data()), logoData.size());
|
||||
|
||||
ContentRegistry::Settings::store();
|
||||
EventManager::post<EventSettingsChanged>();
|
||||
EventManager::post<EventWindowInitialized>();
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
EventManager::unsubscribe<EventProviderDeleted>(this);
|
||||
EventManager::unsubscribe<RequestCloseImHex>(this);
|
||||
EventManager::unsubscribe<RequestUpdateWindowTitle>(this);
|
||||
EventManager::unsubscribe<EventAbnormalTermination>(this);
|
||||
EventManager::unsubscribe<RequestOpenPopup>(this);
|
||||
|
||||
this->exitImGui();
|
||||
this->exitGLFW();
|
||||
}
|
||||
|
||||
void Window::loop() {
|
||||
this->m_lastFrameTime = glfwGetTime();
|
||||
while (!glfwWindowShouldClose(this->m_window)) {
|
||||
if (!glfwGetWindowAttrib(this->m_window, GLFW_VISIBLE) || glfwGetWindowAttrib(this->m_window, GLFW_ICONIFIED)) {
|
||||
// If the application is minimized or not visible, don't render anything
|
||||
glfwWaitEvents();
|
||||
} else {
|
||||
glfwPollEvents();
|
||||
|
||||
bool frameRateUnlocked = ImGui::IsPopupOpen(ImGuiID(0), ImGuiPopupFlags_AnyPopupId) || TaskManager::getRunningTaskCount() > 0 || this->m_mouseButtonDown || this->m_hadEvent || !this->m_pressedKeys.empty();
|
||||
const double timeout = std::max(0.0, (1.0 / 5.0) - (glfwGetTime() - this->m_lastFrameTime));
|
||||
this->m_hadEvent = false;
|
||||
// If no events have been received in a while, lower the frame rate
|
||||
{
|
||||
// If the mouse is down, the mouse is moving or a popup is open, we don't want to lower the frame rate
|
||||
bool frameRateUnlocked =
|
||||
ImGui::IsPopupOpen(ImGuiID(0), ImGuiPopupFlags_AnyPopupId) ||
|
||||
TaskManager::getRunningTaskCount() > 0 ||
|
||||
this->m_mouseButtonDown ||
|
||||
this->m_hadEvent ||
|
||||
!this->m_pressedKeys.empty();
|
||||
|
||||
if ((this->m_lastFrameTime - this->m_frameRateUnlockTime) > 5 && this->m_frameRateTemporarilyUnlocked && !frameRateUnlocked) {
|
||||
this->m_frameRateTemporarilyUnlocked = false;
|
||||
}
|
||||
// Calculate the time until the next frame
|
||||
const double timeout = std::max(0.0, (1.0 / 5.0) - (glfwGetTime() - this->m_lastFrameTime));
|
||||
|
||||
if (frameRateUnlocked || this->m_frameRateTemporarilyUnlocked) {
|
||||
if (!this->m_frameRateTemporarilyUnlocked) {
|
||||
this->m_frameRateTemporarilyUnlocked = true;
|
||||
this->m_frameRateUnlockTime = this->m_lastFrameTime;
|
||||
// If the frame rate has been unlocked for 5 seconds, lock it again
|
||||
if ((this->m_lastFrameTime - this->m_frameRateUnlockTime) > 5 && this->m_frameRateTemporarilyUnlocked && !frameRateUnlocked) {
|
||||
this->m_frameRateTemporarilyUnlocked = false;
|
||||
}
|
||||
} else {
|
||||
glfwWaitEventsTimeout(timeout);
|
||||
|
||||
// If the frame rate is locked, wait for events with a timeout
|
||||
if (frameRateUnlocked || this->m_frameRateTemporarilyUnlocked) {
|
||||
if (!this->m_frameRateTemporarilyUnlocked) {
|
||||
this->m_frameRateTemporarilyUnlocked = true;
|
||||
this->m_frameRateUnlockTime = this->m_lastFrameTime;
|
||||
}
|
||||
} else {
|
||||
glfwWaitEventsTimeout(timeout);
|
||||
}
|
||||
|
||||
this->m_hadEvent = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Render frame
|
||||
this->frameBegin();
|
||||
this->frame();
|
||||
this->frameEnd();
|
||||
|
||||
// Limit frame rate
|
||||
const auto targetFps = ImHexApi::System::getTargetFPS();
|
||||
if (targetFps <= 200) {
|
||||
auto leftoverFrameTime = i64((this->m_lastFrameTime + 1 / targetFps - glfwGetTime()) * 1000);
|
||||
@ -262,11 +273,12 @@ namespace hex {
|
||||
}
|
||||
|
||||
void Window::frameBegin() {
|
||||
|
||||
// Start new ImGui Frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Handle all undocked floating windows
|
||||
ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->WorkPos);
|
||||
ImGui::SetNextWindowSize(ImHexApi::System::getMainWindowSize() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing()));
|
||||
@ -279,6 +291,7 @@ namespace hex {
|
||||
|
||||
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
// Render main dock space
|
||||
if (ImGui::Begin("ImHexDockSpace", nullptr, windowFlags)) {
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
ImGui::PopStyleVar();
|
||||
@ -290,26 +303,31 @@ namespace hex {
|
||||
auto footerHeight = ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().FramePadding.y * 2 + 1_scaled;
|
||||
auto dockSpaceSize = ImVec2(ImHexApi::System::getMainWindowSize().x - sidebarWidth, ImGui::GetContentRegionAvail().y - footerHeight);
|
||||
|
||||
auto dockId = ImGui::DockSpace(ImGui::GetID("ImHexMainDock"), dockSpaceSize);
|
||||
ImHexApi::System::impl::setMainDockSpaceId(dockId);
|
||||
// Render footer
|
||||
{
|
||||
|
||||
drawList->AddRectFilled(ImGui::GetWindowPos(), ImGui::GetWindowPos() + ImGui::GetWindowSize() - ImVec2(dockSpaceSize.x, footerHeight - ImGui::GetStyle().FramePadding.y - 1_scaled), ImGui::GetColorU32(ImGuiCol_MenuBarBg));
|
||||
auto dockId = ImGui::DockSpace(ImGui::GetID("ImHexMainDock"), dockSpaceSize);
|
||||
ImHexApi::System::impl::setMainDockSpaceId(dockId);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::SetCursorPosX(8);
|
||||
for (const auto &callback : ContentRegistry::Interface::getFooterItems()) {
|
||||
auto prevIdx = drawList->_VtxCurrentIdx;
|
||||
callback();
|
||||
auto currIdx = drawList->_VtxCurrentIdx;
|
||||
drawList->AddRectFilled(ImGui::GetWindowPos(), ImGui::GetWindowPos() + ImGui::GetWindowSize() - ImVec2(dockSpaceSize.x, footerHeight - ImGui::GetStyle().FramePadding.y - 1_scaled), ImGui::GetColorU32(ImGuiCol_MenuBarBg));
|
||||
|
||||
// Only draw separator if something was actually drawn
|
||||
if (prevIdx != currIdx) {
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
ImGui::Separator();
|
||||
ImGui::SetCursorPosX(8);
|
||||
for (const auto &callback : ContentRegistry::Interface::getFooterItems()) {
|
||||
auto prevIdx = drawList->_VtxCurrentIdx;
|
||||
callback();
|
||||
auto currIdx = drawList->_VtxCurrentIdx;
|
||||
|
||||
// Only draw separator if something was actually drawn
|
||||
if (prevIdx != currIdx) {
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render sidebar
|
||||
{
|
||||
ImGui::SetCursorPos(sidebarPos);
|
||||
|
||||
@ -355,6 +373,7 @@ namespace hex {
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
// Render main menu
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
|
||||
@ -388,7 +407,7 @@ namespace hex {
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
// Draw toolbar
|
||||
// Render toolbar
|
||||
if (ImGui::BeginMenuBar()) {
|
||||
|
||||
for (const auto &callback : ContentRegistry::Interface::getToolbarItems()) {
|
||||
@ -430,6 +449,7 @@ namespace hex {
|
||||
}
|
||||
};
|
||||
|
||||
// No plugins error popup
|
||||
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F));
|
||||
if (ImGui::BeginPopupModal("No Plugins", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
|
||||
ImGui::TextUnformatted("No ImHex plugins loaded (including the built-in plugin)!");
|
||||
@ -443,6 +463,7 @@ namespace hex {
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// No built-in plugin error popup
|
||||
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F));
|
||||
if (ImGui::BeginPopupModal("No Builtin Plugin", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
|
||||
ImGui::TextUnformatted("The ImHex built-in plugins could not be loaded!");
|
||||
@ -456,6 +477,7 @@ namespace hex {
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// Multiple built-in plugins error popup
|
||||
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F));
|
||||
if (ImGui::BeginPopupModal("Multiple Builtin Plugins", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
|
||||
ImGui::TextUnformatted("ImHex found and attempted to load multiple built-in plugins!");
|
||||
@ -470,6 +492,8 @@ namespace hex {
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
// Open popups when plugins requested it
|
||||
{
|
||||
std::scoped_lock lock(this->m_popupMutex);
|
||||
this->m_popupsToOpen.remove_if([](const auto &name) {
|
||||
@ -482,6 +506,7 @@ namespace hex {
|
||||
});
|
||||
}
|
||||
|
||||
// Run all deferred calls
|
||||
TaskManager::runDeferredCalls();
|
||||
|
||||
EventManager::post<EventFrameBegin>();
|
||||
@ -489,26 +514,32 @@ namespace hex {
|
||||
|
||||
void Window::frame() {
|
||||
auto &io = ImGui::GetIO();
|
||||
|
||||
// Loop through all views and draw them
|
||||
for (auto &[name, view] : ContentRegistry::Views::getEntries()) {
|
||||
ImGui::GetCurrentContext()->NextWindowData.ClearFlags();
|
||||
|
||||
// Draw always visible views
|
||||
view->drawAlwaysVisible();
|
||||
|
||||
// Skip views that shouldn't be processed currently
|
||||
if (!view->shouldProcess())
|
||||
continue;
|
||||
|
||||
// Draw view
|
||||
if (view->isAvailable()) {
|
||||
float fontScaling = std::max(1.0F, ImHexApi::System::getFontSize() / ImHexApi::System::DefaultFontSize) * ImHexApi::System::getGlobalScale();
|
||||
ImGui::SetNextWindowSizeConstraints(view->getMinSize() * fontScaling, view->getMaxSize() * fontScaling);
|
||||
view->drawContent();
|
||||
}
|
||||
|
||||
// Handle per-view shortcuts
|
||||
if (view->getWindowOpenState()) {
|
||||
auto window = ImGui::FindWindowByName(view->getName().c_str());
|
||||
bool hasWindow = window != nullptr;
|
||||
bool focused = false;
|
||||
|
||||
|
||||
// Get the currently focused view
|
||||
if (hasWindow && !(window->Flags & ImGuiWindowFlags_Popup)) {
|
||||
ImGui::Begin(View::toWindowName(name).c_str());
|
||||
|
||||
@ -516,12 +547,14 @@ namespace hex {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// Pass on currently pressed keys to the shortcut handler
|
||||
for (const auto &key : this->m_pressedKeys) {
|
||||
ShortcutManager::process(view, io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, focused, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle global shortcuts
|
||||
for (const auto &key : this->m_pressedKeys) {
|
||||
ShortcutManager::processGlobals(io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, key);
|
||||
}
|
||||
@ -532,9 +565,12 @@ namespace hex {
|
||||
void Window::frameEnd() {
|
||||
EventManager::post<EventFrameEnd>();
|
||||
|
||||
// Clean up all tasks that are done
|
||||
TaskManager::collectGarbage();
|
||||
|
||||
this->endNativeWindowFrame();
|
||||
|
||||
// Render UI
|
||||
ImGui::Render();
|
||||
|
||||
int displayWidth, displayHeight;
|
||||
@ -562,6 +598,7 @@ namespace hex {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// Set up used OpenGL version
|
||||
#if defined(OS_MACOS)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
@ -576,6 +613,7 @@ namespace hex {
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
|
||||
// Create window
|
||||
this->m_windowTitle = "ImHex";
|
||||
this->m_window = glfwCreateWindow(1280_scaled, 720_scaled, this->m_windowTitle.c_str(), nullptr, nullptr);
|
||||
|
||||
@ -589,6 +627,7 @@ namespace hex {
|
||||
glfwMakeContextCurrent(this->m_window);
|
||||
glfwSwapInterval(1);
|
||||
|
||||
// Center window
|
||||
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
||||
if (monitor != nullptr) {
|
||||
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
|
||||
@ -603,6 +642,7 @@ namespace hex {
|
||||
}
|
||||
}
|
||||
|
||||
// Set up initial window position
|
||||
{
|
||||
int x = 0, y = 0;
|
||||
glfwGetWindowPos(this->m_window, &x, &y);
|
||||
@ -610,6 +650,7 @@ namespace hex {
|
||||
ImHexApi::System::impl::setMainWindowPosition(x, y);
|
||||
}
|
||||
|
||||
// Set up initial window size
|
||||
{
|
||||
int width = 0, height = 0;
|
||||
glfwGetWindowSize(this->m_window, &width, &height);
|
||||
@ -617,6 +658,7 @@ namespace hex {
|
||||
ImHexApi::System::impl::setMainWindowSize(width, height);
|
||||
}
|
||||
|
||||
// Register window move callback
|
||||
glfwSetWindowPosCallback(this->m_window, [](GLFWwindow *window, int x, int y) {
|
||||
ImHexApi::System::impl::setMainWindowPosition(x, y);
|
||||
|
||||
@ -629,6 +671,7 @@ namespace hex {
|
||||
win->processEvent();
|
||||
});
|
||||
|
||||
// Register window resize callback
|
||||
glfwSetWindowSizeCallback(this->m_window, [](GLFWwindow *window, int width, int height) {
|
||||
if (!glfwGetWindowAttrib(window, GLFW_ICONIFIED))
|
||||
ImHexApi::System::impl::setMainWindowSize(width, height);
|
||||
@ -642,6 +685,7 @@ namespace hex {
|
||||
win->processEvent();
|
||||
});
|
||||
|
||||
// Register mouse handling callback
|
||||
glfwSetMouseButtonCallback(this->m_window, [](GLFWwindow *window, int button, int action, int mods) {
|
||||
hex::unused(button, mods);
|
||||
|
||||
@ -654,6 +698,7 @@ namespace hex {
|
||||
win->processEvent();
|
||||
});
|
||||
|
||||
// Register scrolling callback
|
||||
glfwSetScrollCallback(this->m_window, [](GLFWwindow *window, double xOffset, double yOffset) {
|
||||
hex::unused(xOffset, yOffset);
|
||||
|
||||
@ -661,6 +706,7 @@ namespace hex {
|
||||
win->processEvent();
|
||||
});
|
||||
|
||||
// Register key press callback
|
||||
glfwSetKeyCallback(this->m_window, [](GLFWwindow *window, int key, int scancode, int action, int mods) {
|
||||
hex::unused(mods);
|
||||
|
||||
@ -676,28 +722,7 @@ namespace hex {
|
||||
win->processEvent();
|
||||
});
|
||||
|
||||
glfwSetDropCallback(this->m_window, [](GLFWwindow *, int count, const char **paths) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
auto path = std::fs::path(reinterpret_cast<const char8_t *>(paths[i]));
|
||||
|
||||
bool handled = false;
|
||||
for (const auto &[extensions, handler] : ContentRegistry::FileHandler::getEntries()) {
|
||||
for (const auto &extension : extensions) {
|
||||
if (path.extension() == extension) {
|
||||
if (!handler(path))
|
||||
log::error("Handler for extensions '{}' failed to process file!", extension);
|
||||
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
EventManager::post<RequestOpenFile>(path);
|
||||
}
|
||||
});
|
||||
|
||||
// Register cursor position callback
|
||||
glfwSetCursorPosCallback(this->m_window, [](GLFWwindow *window, double x, double y) {
|
||||
hex::unused(x, y);
|
||||
|
||||
@ -705,10 +730,39 @@ namespace hex {
|
||||
win->processEvent();
|
||||
});
|
||||
|
||||
// Register window close callback
|
||||
glfwSetWindowCloseCallback(this->m_window, [](GLFWwindow *window) {
|
||||
EventManager::post<EventWindowClosing>(window);
|
||||
});
|
||||
|
||||
// Register file drop callback
|
||||
glfwSetDropCallback(this->m_window, [](GLFWwindow *, int count, const char **paths) {
|
||||
// Loop over all dropped files
|
||||
for (int i = 0; i < count; i++) {
|
||||
auto path = std::fs::path(reinterpret_cast<const char8_t *>(paths[i]));
|
||||
|
||||
// Check if a custom file handler can handle the file
|
||||
bool handled = false;
|
||||
for (const auto &[extensions, handler] : ContentRegistry::FileHandler::getEntries()) {
|
||||
for (const auto &extension : extensions) {
|
||||
if (path.extension() == extension) {
|
||||
// Pass the file to the handler and check if it was successful
|
||||
if (!handler(path)) {
|
||||
log::error("Handler for extensions '{}' failed to process file!", extension);
|
||||
break;
|
||||
}
|
||||
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no custom handler was found, just open the file regularly
|
||||
if (!handled)
|
||||
EventManager::post<RequestOpenFile>(path);
|
||||
}
|
||||
});
|
||||
|
||||
glfwSetWindowSizeLimits(this->m_window, 720_scaled, 480_scaled, GLFW_DONT_CARE, GLFW_DONT_CARE);
|
||||
|
||||
glfwShowWindow(this->m_window);
|
||||
@ -719,6 +773,7 @@ namespace hex {
|
||||
|
||||
auto fonts = View::getFontAtlas();
|
||||
|
||||
// Initialize ImGui and all other ImGui extensions
|
||||
GImGui = ImGui::CreateContext(fonts);
|
||||
GImPlot = ImPlot::CreateContext();
|
||||
GImNodes = ImNodes::CreateContext();
|
||||
@ -726,6 +781,7 @@ namespace hex {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
|
||||
// Configure window alpha and rounding to make them not stand out when detached
|
||||
style.Alpha = 1.0F;
|
||||
style.WindowRounding = 0.0F;
|
||||
|
||||
@ -735,6 +791,7 @@ namespace hex {
|
||||
io.ConfigWindowsMoveFromTitleBarOnly = true;
|
||||
io.FontGlobalScale = 1.0F;
|
||||
|
||||
// Disable multi-window support on Wayland since it doesn't support it
|
||||
if (glfwGetPrimaryMonitor() != nullptr) {
|
||||
auto sessionType = hex::getEnvironmentVariable("XDG_SESSION_TYPE");
|
||||
bool multiWindowEnabled = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.multi_windows", 1) != 0;
|
||||
@ -751,8 +808,9 @@ namespace hex {
|
||||
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
|
||||
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkCreationOnSnap);
|
||||
|
||||
// Allow ImNodes links to always be detached without holding down any button
|
||||
{
|
||||
static bool always = true;
|
||||
static bool always = true;
|
||||
ImNodes::GetIO().LinkDetachWithModifierClick.Modifier = &always;
|
||||
}
|
||||
|
||||
@ -767,25 +825,51 @@ namespace hex {
|
||||
style.IndentSpacing = 10.0F;
|
||||
|
||||
// Install custom settings handler
|
||||
ImGuiSettingsHandler handler;
|
||||
handler.TypeName = "ImHex";
|
||||
handler.TypeHash = ImHashStr("ImHex");
|
||||
handler.ReadOpenFn = ImHexSettingsHandler_ReadOpenFn;
|
||||
handler.ReadLineFn = ImHexSettingsHandler_ReadLine;
|
||||
handler.WriteAllFn = ImHexSettingsHandler_WriteAll;
|
||||
handler.UserData = this;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(handler);
|
||||
{
|
||||
ImGuiSettingsHandler handler;
|
||||
handler.TypeName = "ImHex";
|
||||
handler.TypeHash = ImHashStr("ImHex");
|
||||
|
||||
for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) {
|
||||
if (std::fs::exists(dir) && fs::isPathWritable(dir)) {
|
||||
this->m_imguiSettingsPath = dir / "interface.ini";
|
||||
io.IniFilename = nullptr;
|
||||
break;
|
||||
handler.ReadOpenFn = [](ImGuiContext *ctx, ImGuiSettingsHandler *, const char *) -> void* { return ctx; };
|
||||
|
||||
handler.ReadLineFn = [](ImGuiContext *, ImGuiSettingsHandler *, void *, const char *line) {
|
||||
for (auto &[name, view] : ContentRegistry::Views::getEntries()) {
|
||||
std::string format = view->getUnlocalizedName() + "=%d";
|
||||
sscanf(line, format.c_str(), &view->getWindowOpenState());
|
||||
}
|
||||
for (auto &[name, function, detached] : ContentRegistry::Tools::getEntries()) {
|
||||
std::string format = name + "=%d";
|
||||
sscanf(line, format.c_str(), &detached);
|
||||
}
|
||||
};
|
||||
|
||||
handler.WriteAllFn = [](ImGuiContext *, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf) {
|
||||
buf->appendf("[%s][General]\n", handler->TypeName);
|
||||
|
||||
for (auto &[name, view] : ContentRegistry::Views::getEntries()) {
|
||||
buf->appendf("%s=%d\n", name.c_str(), view->getWindowOpenState());
|
||||
}
|
||||
for (auto &[name, function, detached] : ContentRegistry::Tools::getEntries()) {
|
||||
buf->appendf("%s=%d\n", name.c_str(), detached);
|
||||
}
|
||||
|
||||
buf->append("\n");
|
||||
};
|
||||
|
||||
handler.UserData = this;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(handler);
|
||||
|
||||
for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) {
|
||||
if (std::fs::exists(dir) && fs::isPathWritable(dir)) {
|
||||
this->m_imguiSettingsPath = dir / "interface.ini";
|
||||
io.IniFilename = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->m_imguiSettingsPath.empty() && fs::exists(this->m_imguiSettingsPath))
|
||||
ImGui::LoadIniSettingsFromDisk(hex::toUTF8String(this->m_imguiSettingsPath).c_str());
|
||||
if (!this->m_imguiSettingsPath.empty() && fs::exists(this->m_imguiSettingsPath))
|
||||
ImGui::LoadIniSettingsFromDisk(hex::toUTF8String(this->m_imguiSettingsPath).c_str());
|
||||
}
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(this->m_window, true);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user