1
0
mirror of synced 2025-02-17 18:59:21 +01:00

impr: Added comments everywhere in main application

This commit is contained in:
WerWolv 2023-02-17 12:03:53 +01:00
parent 279e085887
commit 21dc65f42a
8 changed files with 486 additions and 310 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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();

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);