impr: Refactored forwarder executable and add lots more information to it

This commit is contained in:
WerWolv
2023-09-27 14:14:27 +02:00
parent b3ef615158
commit e80c7bff1c
29 changed files with 208 additions and 134 deletions

48
main/gui/CMakeLists.txt Normal file
View File

@@ -0,0 +1,48 @@
project(main)
add_executable(main ${APPLICATION_TYPE}
source/main.cpp
source/crash_handlers.cpp
source/window/window.cpp
source/window/win_window.cpp
source/window/macos_window.cpp
source/window/linux_window.cpp
source/messaging/common.cpp
source/messaging/linux.cpp
source/messaging/macos.cpp
source/messaging/win.cpp
source/init/splash_window.cpp
source/init/tasks.cpp
${IMHEX_ICON}
)
target_include_directories(main PUBLIC include)
setupCompilerFlags(main)
set(LIBROMFS_RESOURCE_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/romfs)
set(LIBROMFS_PROJECT_NAME imhex)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../lib/external/libromfs ${CMAKE_CURRENT_BINARY_DIR}/main/gui/libromfs EXCLUDE_FROM_ALL)
set_target_properties(${LIBROMFS_LIBRARY} PROPERTIES POSITION_INDEPENDENT_CODE ON)
set_target_properties(main PROPERTIES
OUTPUT_NAME ${IMHEX_APPLICATION_NAME}
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/..
CXX_VISIBILITY_PRESET hidden
POSITION_INDEPENDENT_CODE ON)
add_compile_definitions(IMHEX_PROJECT_NAME="${PROJECT_NAME}")
target_link_libraries(main PRIVATE libromfs-imhex libimhex ${FMT_LIBRARIES})
if (WIN32)
target_link_libraries(main PRIVATE usp10 wsock32 ws2_32 Dwmapi.lib)
else ()
target_link_libraries(main PRIVATE pthread)
endif ()
if (APPLE)
add_compile_definitions(GL_SILENCE_DEPRECATION)
endif ()

View File

@@ -0,0 +1,7 @@
#pragma once
namespace hex::crash {
void setupCrashHandlers();
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include <functional>
#include <future>
#include <vector>
#include <mutex>
struct GLFWwindow;
namespace hex::init {
using TaskFunction = std::function<bool()>;
enum FrameResult{ success, failure, wait };
struct Highlight {
ImVec2 start;
size_t count;
ImColor color;
};
class WindowSplash {
public:
WindowSplash();
~WindowSplash();
bool loop();
FrameResult fullFrame();
void startStartupTasks();
void addStartupTask(const std::string &taskName, const TaskFunction &task, bool async) {
this->m_tasks.emplace_back(taskName, task, async);
}
private:
GLFWwindow *m_window;
std::mutex m_progressMutex;
std::atomic<float> m_progress = 0;
std::list<std::string> m_currTaskNames;
void initGLFW();
void initImGui();
void initMyself();
void exitGLFW();
void exitImGui();
std::future<bool> processTasksAsync();
std::vector<std::tuple<std::string, TaskFunction, bool>> m_tasks;
std::string m_gpuVendor;
ImGui::Texture splashBackgroundTexture;
ImGui::Texture splashTextTexture;
std::future<bool> tasksSucceeded;
std::array<Highlight, 3> highlights;
float progressLerp = 0.0F;
};
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include <functional>
#include <string>
#include <vector>
namespace hex::init {
struct Task {
std::string name;
std::function<bool()> function;
bool async;
};
std::vector<Task> getInitTasks();
std::vector<Task> getExitTasks();
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include<string>
#include<vector>
#include<hex/helpers/types.hpp>
/**
* Cross-instance (cross-process) messaging system
* As of now, this system may not be stable for use beyond its current use:
* forwarding providers opened in new instances
*/
namespace hex::messaging {
/**
* @brief Setup everything to be able to send/receive messages
*/
void setupMessaging();
/**
* @brief Internal method - setup platform-specific things to be able to send messages
* @return if this instance has been determined to be the main instance
*/
bool setupNative();
/**
* @brief Internal method - send a message to another Imhex instance in a platform-specific way
*/
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &args);
/**
* @brief Internal method - called by platform-specific code when a event has been received
*/
void messageReceived(const std::string &eventName, const std::vector<u8> &args);
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <filesystem>
#include <memory>
#include <string>
#include <list>
#include <vector>
#include <hex/ui/view.hpp>
struct GLFWwindow;
struct ImGuiSettingsHandler;
namespace hex {
std::fs::path getImGuiSettingsPath();
void nativeErrorMessage(const std::string &message);
class Window {
public:
Window();
~Window();
void loop();
void fullFrame();
static void initNative();
private:
void setupNativeWindow();
void beginNativeWindowFrame();
void endNativeWindowFrame();
void drawTitleBar();
void drawTitleBarBorderless();
void drawTitleBarBorder();
void frameBegin();
void frame();
void frameEnd();
void processEvent() { this->m_hadEvent = true; }
void initGLFW();
void initImGui();
void exitGLFW();
void exitImGui();
void registerEventHandlers();
GLFWwindow *m_window = nullptr;
std::string m_windowTitle;
double m_lastFrameTime = 0;
ImGui::Texture m_logoTexture;
std::mutex m_popupMutex;
std::list<std::string> m_popupsToOpen;
std::vector<int> m_pressedKeys;
bool m_buttonDown = false;
bool m_hadEvent = false;
bool m_frameRateTemporarilyUnlocked = false;
double m_frameRateUnlockTime = 0;
ImGui::ImHexCustomData m_imguiCustomData;
};
}

BIN
main/gui/romfs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
main/gui/romfs/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -0,0 +1,208 @@
#include <hex/api/project_file_manager.hpp>
#include <hex/api/task.hpp>
#include <init/tasks.hpp>
#include <hex/helpers/logger.hpp>
#include <hex/helpers/fs.hpp>
#include <hex/helpers/stacktrace.hpp>
#include <wolv/io/fs.hpp>
#include <wolv/utils/string.hpp>
#include <window.hpp>
#include <nlohmann/json.hpp>
#include <llvm/Demangle/Demangle.h>
#include <csignal>
#include <exception>
#include <typeinfo>
#if defined (OS_MACOS)
#include <sys/utsname.h>
#endif
namespace hex::crash {
constexpr static auto CrashBackupFileName = "crash_backup.hexproj";
constexpr static auto Signals = { SIGSEGV, SIGILL, SIGABRT, SIGFPE };
void resetCrashHandlers();
static void sendNativeMessage(const std::string& message) {
hex::nativeErrorMessage(hex::format("ImHex crashed during its loading.\nError: {}", message));
}
// Function that decides what should happen on a crash
// (either sending a message or saving a crash file, depending on when the crash occurred)
using CrashCallback = void (*) (const std::string&);
static CrashCallback crashCallback = sendNativeMessage;
static void saveCrashFile(const std::string& message) {
log::fatal(message);
nlohmann::json crashData {
{ "logFile", wolv::util::toUTF8String(hex::log::impl::getFile().getPath()) },
{ "project", wolv::util::toUTF8String(ProjectFile::getPath()) },
};
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) {
wolv::io::File file(path / "crash.json", wolv::io::File::Mode::Create);
if (file.isValid()) {
file.writeString(crashData.dump(4));
file.close();
log::info("Wrote crash.json file to {}", wolv::util::toUTF8String(file.getPath()));
return;
}
}
log::warn("Could not write crash.json file!");
}
static void printStackTrace() {
for (const auto &stackFrame : stacktrace::getStackTrace()) {
if (stackFrame.line == 0)
log::fatal(" {}", stackFrame.function);
else
log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function);
}
}
extern "C" void triggerSafeShutdown(int signalNumber = 0) {
// Trigger an event so that plugins can handle crashes
EventManager::post<EventAbnormalTermination>(signalNumber);
// Run exit tasks
for (const auto &[name, task, async] : init::getExitTasks())
task();
// Terminate all asynchronous tasks
TaskManager::exit();
// 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)
if (signalNumber == 0) {
#if defined(OS_WINDOWS)
__debugbreak();
#else
raise(SIGTRAP);
#endif
} else {
std::exit(signalNumber);
}
#else
if (signalNumber == 0)
std::abort();
else
std::exit(signalNumber);
#endif
}
void handleCrash(const std::string &msg) {
// Call the crash callback
crashCallback(msg);
// Print the stacktrace to the console or log file
printStackTrace();
// Flush all streams
fflush(stdout);
fflush(stderr);
}
// Custom signal handler to print various information and a stacktrace when the application crashes
static void signalHandler(int signalNumber, const std::string &signalName) {
// Reset crash handlers, so we can't have a recursion if this code crashes
resetCrashHandlers();
// Actually handle the crash
handleCrash(hex::format("Received signal '{}' ({})", signalName, signalNumber));
// Detect if the crash was due to an uncaught exception
if (std::uncaught_exceptions() > 0) {
log::fatal("Uncaught exception thrown!");
}
triggerSafeShutdown(signalNumber);
}
static void uncaughtExceptionHandler() {
// Reset crash handlers, so we can't have a recursion if this code crashes
resetCrashHandlers();
handleCrash("Uncaught exception!");
// Print the current exception info
try {
std::rethrow_exception(std::current_exception());
} catch (std::exception &ex) {
std::string exceptionStr = hex::format("{}()::what() -> {}", llvm::itaniumDemangle(typeid(ex).name(), nullptr, nullptr, nullptr), ex.what());
log::fatal("Program terminated with uncaught exception: {}", exceptionStr);
}
triggerSafeShutdown();
}
// Setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex
void setupCrashHandlers() {
// 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
}
// Configure the uncaught exception handler
std::set_terminate(uncaughtExceptionHandler);
// Save a backup project when the application crashes
// We need to save the project no mater if it is dirty,
// because this save is responsible for telling us which files
// were opened in case there wasn't a project
// Only do it when ImHex has finished its loading
EventManager::subscribe<EventImHexStartupFinished>([] {
EventManager::subscribe<EventAbnormalTermination>([](int) {
// Save ImGui settings
auto imguiSettingsPath = hex::getImGuiSettingsPath();
if (!imguiSettingsPath.empty())
ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(imguiSettingsPath).c_str());
// Create crash backup if any providers are open
if (ImHexApi::Provider::isValid()) {
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) {
if (ProjectFile::store(path / CrashBackupFileName, false))
break;
}
}
});
});
// Change the crash callback when ImHex has finished startup
EventManager::subscribe<EventImHexStartupFinished>([]{
crashCallback = saveCrashFile;
});
}
void resetCrashHandlers() {
std::set_terminate(nullptr);
for (auto signal : Signals)
std::signal(signal, SIG_DFL);
}
}

View File

@@ -0,0 +1,477 @@
#include "window.hpp"
#include "init/splash_window.hpp"
#include <hex/api/imhex_api.hpp>
#include <hex/api/task.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/utils_macos.hpp>
#include <hex/helpers/fmt.hpp>
#include <hex/helpers/logger.hpp>
#include <romfs/romfs.hpp>
#include <imgui.h>
#include <imgui_internal.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <imgui_impl_opengl3_loader.h>
#include <fonts/fontawesome_font.h>
#include <GLFW/glfw3.h>
#include <wolv/utils/guards.hpp>
#include <unistd.h>
#include <chrono>
#include <future>
#include <numeric>
#include <random>
using namespace std::literals::chrono_literals;
namespace hex::init {
struct GlfwError {
int errorCode = 0;
std::string desc;
};
GlfwError lastGlfwError;
WindowSplash::WindowSplash() : m_window(nullptr) {
this->initGLFW();
this->initImGui();
this->initMyself();
ImHexApi::System::impl::setGPUVendor(reinterpret_cast<const char *>(glGetString(GL_VENDOR)));
}
WindowSplash::~WindowSplash() {
this->exitImGui();
this->exitGLFW();
}
std::future<bool> WindowSplash::processTasksAsync() {
return std::async(std::launch::async, [this] {
bool status = true;
std::atomic<u32> tasksCompleted = 0;
// Loop over all registered init tasks
for (const auto &[name, task, async] : this->m_tasks) {
// Construct a new task callback
auto runTask = [&, task = task, name = name] {
try {
// Save an iterator to the current task name
decltype(this->m_currTaskNames)::iterator taskNameIter;
{
std::lock_guard guard(this->m_progressMutex);
this->m_currTaskNames.push_back(name + "...");
taskNameIter = std::prev(this->m_currTaskNames.end());
}
// When the task finished, increment the progress bar
ON_SCOPE_EXIT {
tasksCompleted++;
this->m_progress = float(tasksCompleted) / this->m_tasks.size();
};
// Execute the actual task and track the amount of time it took to run
auto startTime = std::chrono::high_resolution_clock::now();
bool taskStatus = task();
auto endTime = std::chrono::high_resolution_clock::now();
log::info("Task '{}' finished {} in {} ms",
name,
taskStatus ? "successfully" : "unsuccessfully",
std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
);
// Track the overall status of the tasks
status = status && taskStatus;
// Erase the task name from the list of running tasks
{
std::lock_guard guard(this->m_progressMutex);
this->m_currTaskNames.erase(taskNameIter);
}
} catch (const std::exception &e) {
log::error("Init task '{}' threw an exception: {}", name, e.what());
status = false;
} catch (...) {
log::error("Init task '{}' threw an unidentifiable exception", name);
status = false;
}
};
// If the task can be run asynchronously, run it in a separate thread
// otherwise run it in this thread and wait for it to finish
if (async) {
std::thread([runTask]{ runTask(); }).detach();
} else {
runTask();
}
}
// Check every 100ms if all tasks have run
while (tasksCompleted < this->m_tasks.size()) {
std::this_thread::sleep_for(100ms);
}
// Small extra delay so the last progress step is visible
std::this_thread::sleep_for(100ms);
return status;
});
}
FrameResult WindowSplash::fullFrame() {
glfwPollEvents();
// Start a new ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
auto scale = ImHexApi::System::getGlobalScale();
// Draw the splash screen background
auto drawList = ImGui::GetBackgroundDrawList();
{
// Draw the splash screen background
drawList->AddImage(this->splashBackgroundTexture, ImVec2(0, 0), this->splashBackgroundTexture.getSize() * scale);
{
// Function to highlight a given number of bytes at a position in the splash screen
const auto highlightBytes = [&](ImVec2 start, size_t count, ImColor color, float opacity) {
// Dimensions and number of bytes that are drawn. Taken from the splash screen image
const auto hexSize = ImVec2(29, 18) * scale;
const auto hexSpacing = ImVec2(17.4, 15) * scale;
const auto hexStart = ImVec2(27, 127) * scale;
const auto hexCount = ImVec2(13, 7);
bool isStart = true;
color.Value.w *= opacity;
// Loop over all the bytes on the splash screen
for (u32 y = u32(start.y); y < u32(hexCount.y); y += 1) {
for (u32 x = u32(start.x); x < u32(hexCount.x); x += 1) {
if (count-- == 0)
return;
// Find the start position of the byte to draw
auto pos = hexStart + ImVec2(float(x), float(y)) * (hexSize + hexSpacing);
// Fill the rectangle in the byte with the given color
drawList->AddRectFilled(pos + ImVec2(0, -hexSpacing.y / 2), pos + hexSize + ImVec2(0, hexSpacing.y / 2), color);
// Add some extra color on the right if the current byte isn't the last byte, and we didn't reach the right side of the image
if (count > 0 && x != u32(hexCount.x) - 1)
drawList->AddRectFilled(pos + ImVec2(hexSize.x, -hexSpacing.y / 2), pos + hexSize + ImVec2(hexSpacing.x, hexSpacing.y / 2), color);
// Add some extra color on the left if this is the first byte we're highlighting
if (isStart) {
isStart = false;
drawList->AddRectFilled(pos - hexSpacing / 2, pos + ImVec2(0, hexSize.y + hexSpacing.y / 2), color);
}
// Add some extra color on the right if this is the last byte
if (count == 0) {
drawList->AddRectFilled(pos + ImVec2(hexSize.x, -hexSpacing.y / 2), pos + hexSize + hexSpacing / 2, color);
}
}
start.x = 0;
}
};
// Draw all highlights, slowly fading them in as the init tasks progress
for (const auto &highlight : this->highlights)
highlightBytes(highlight.start, highlight.count, highlight.color, this->progressLerp);
}
this->progressLerp += (this->m_progress - this->progressLerp) * 0.1F;
// Draw the splash screen foreground
drawList->AddImage(this->splashTextTexture, ImVec2(0, 0), this->splashTextTexture.getSize() * scale);
// Draw the "copyright" notice
drawList->AddText(ImVec2(35, 85) * scale, ImColor(0xFF, 0xFF, 0xFF, 0xFF), hex::format("WerWolv\n2020 - {0}", &__DATE__[7]).c_str());
// Draw version information
// In debug builds, also display the current commit hash and branch
#if defined(DEBUG)
const static auto VersionInfo = hex::format("{0} : {1} {2}@{3}", ImHexApi::System::getImHexVersion(), ICON_FA_CODE_BRANCH, ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash());
#else
const static auto VersionInfo = hex::format("{0}", ImHexApi::System::getImHexVersion());
#endif
drawList->AddText(ImVec2((this->splashBackgroundTexture.getSize().x * scale - ImGui::CalcTextSize(VersionInfo.c_str()).x) / 2, 105 * scale), ImColor(0xFF, 0xFF, 0xFF, 0xFF), VersionInfo.c_str());
}
// Draw the task progress bar
{
std::lock_guard guard(this->m_progressMutex);
const auto progressBackgroundStart = ImVec2(99, 357) * scale;
const auto progressBackgroundSize = ImVec2(442, 30) * scale;
const auto progressStart = progressBackgroundStart + ImVec2(0, 20) * scale;
const auto progressSize = ImVec2(progressBackgroundSize.x * this->m_progress, 10 * scale);
// Draw progress bar
drawList->AddRectFilled(progressStart, progressStart + progressSize, 0xD0FFFFFF);
// Draw task names separated by | characters
if (!this->m_currTaskNames.empty()) {
drawList->PushClipRect(progressBackgroundStart, progressBackgroundStart + progressBackgroundSize, true);
drawList->AddText(progressStart + ImVec2(5, -20) * scale, ImColor(0xFF, 0xFF, 0xFF, 0xFF), hex::format("{}", fmt::join(this->m_currTaskNames, " | ")).c_str());
drawList->PopClipRect();
}
}
// Render the frame
ImGui::Render();
int displayWidth, displayHeight;
glfwGetFramebufferSize(this->m_window, &displayWidth, &displayHeight);
glViewport(0, 0, displayWidth, displayHeight);
glClearColor(0.00F, 0.00F, 0.00F, 0.00F);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(this->m_window);
// Check if all background tasks have finished so the splash screen can be closed
if (this->tasksSucceeded.wait_for(0s) == std::future_status::ready) {
if (this->tasksSucceeded.get()) {
log::debug("All tasks finished with success !");
return FrameResult::success;
} else {
log::warn("All tasks finished, but some failed");
return FrameResult::failure;
}
}
return FrameResult::wait;
}
bool WindowSplash::loop() {
// Splash window rendering loop
while (true) {
auto res = this->fullFrame();
if (res == FrameResult::success) return true;
else if (res == FrameResult::failure) return false;
}
}
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);
}
void WindowSplash::initGLFW() {
glfwSetErrorCallback([](int errorCode, const char *desc) {
lastGlfwError.errorCode = errorCode;
lastGlfwError.desc = std::string(desc);
log::error("GLFW Error [{}] : {}", errorCode, desc);
});
if (!glfwInit()) {
log::fatal("Failed to initialize GLFW!");
std::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);
// Create the splash screen window
this->m_window = glfwCreateWindow(1, 400, "Starting ImHex...", nullptr, nullptr);
if (this->m_window == nullptr) {
hex::nativeErrorMessage(hex::format(
"Failed to create GLFW window: [{}] {}.\n"
"You may not have a renderer available.\n"
"The most common cause of this is using a virtual machine\n"
"You may want to try a release artifact ending with 'NoGPU'"
, lastGlfwError.errorCode, lastGlfwError.desc));
std::exit(EXIT_FAILURE);
}
// Calculate native scale factor for hidpi displays
{
float xScale = 0, yScale = 0;
glfwGetWindowContentScale(this->m_window, &xScale, &yScale);
auto meanScale = std::midpoint(xScale, yScale);
if (meanScale <= 0.0F)
meanScale = 1.0F;
#if defined(OS_MACOS)
meanScale /= getBackingScaleFactor();
#endif
ImHexApi::System::impl::setGlobalScale(meanScale);
ImHexApi::System::impl::setNativeScale(meanScale);
log::info("Native scaling set to: {:.1f}", meanScale);
}
glfwSetWindowSize(this->m_window, 640_scaled, 400_scaled);
centerWindow(this->m_window);
glfwMakeContextCurrent(this->m_window);
glfwSwapInterval(1);
}
void WindowSplash::initImGui() {
// Initialize ImGui
IMGUI_CHECKVERSION();
GImGui = ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForOpenGL(this->m_window, true);
#if defined(OS_MACOS)
ImGui_ImplOpenGL3_Init("#version 150");
#else
ImGui_ImplOpenGL3_Init("#version 130");
#endif
auto &io = ImGui::GetIO();
ImGui::GetStyle().ScaleAllSizes(ImHexApi::System::getGlobalScale());
// 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);
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->GetTexDataAsAlpha8(&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_R8, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, px);
io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(tex));
}
// Don't save window settings for the splash screen
io.IniFilename = nullptr;
}
/**
* @brief Initialize resources for the splash window
*/
void WindowSplash::initMyself() {
// Load splash screen image from romfs
this->splashBackgroundTexture = ImGui::Texture(romfs::get("splash_background.png").span());
this->splashTextTexture = ImGui::Texture(romfs::get("splash_text.png").span());
// 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 (!this->splashBackgroundTexture.isValid() || !this->splashTextTexture.isValid()) {
log::fatal("Could not load splash screen image!");
std::exit(EXIT_FAILURE);
}
std::mt19937 rng(std::random_device{}());
u32 lastPos = 0;
u32 lastCount = 0;
for (auto &highlight : this->highlights) {
auto newPos = lastPos + lastCount + (rng() % 40);
auto newCount = (rng() % 7) + 3;
highlight.start.x = newPos % 13;
highlight.start.y = newPos / 13;
highlight.count = newCount;
{
float r, g, b;
ImGui::ColorConvertHSVtoRGB(
(rng() % 360) / 100.0F,
(25 + rng() % 70) / 100.0F,
(85 + rng() % 10) / 100.0F,
r, g, b);
highlight.color = ImColor(r, g, b, 0x50 / 255.0F);
}
lastPos = newPos;
lastCount = newCount;
}
}
void WindowSplash::startStartupTasks() {
// Launch init tasks in the background
this->tasksSucceeded = processTasksAsync();
}
void WindowSplash::exitGLFW() {
glfwDestroyWindow(this->m_window);
glfwTerminate();
}
void WindowSplash::exitImGui() {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
}

View File

@@ -0,0 +1,625 @@
#include "init/tasks.hpp"
#include <imgui.h>
#include <romfs/romfs.hpp>
#include <hex/helpers/http_requests.hpp>
#include <hex/helpers/fs.hpp>
#include <hex/helpers/logger.hpp>
#include <hex/api_urls.hpp>
#include <hex/api/content_registry.hpp>
#include <hex/api/project_file_manager.hpp>
#include <hex/api/theme_manager.hpp>
#include <hex/api/plugin_manager.hpp>
#include <hex/api/layout_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/ui/view.hpp>
#include <hex/ui/popup.hpp>
#include <fonts/fontawesome_font.h>
#include <fonts/codicons_font.h>
#include <fonts/unifont_font.h>
#include <filesystem>
#include <nlohmann/json.hpp>
#include <wolv/io/fs.hpp>
#include <wolv/io/file.hpp>
#include <wolv/hash/uuid.hpp>
namespace hex::init {
using namespace std::literals::string_literals;
static bool checkForUpdates() {
int checkForUpdates = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.server_contact", 2);
// Check if we should check for updates
if (checkForUpdates == 1){
HttpRequest request("GET", GitHubApiURL + "/releases/latest"s);
request.setTimeout(500);
// Query the GitHub API for the latest release version
auto response = request.execute().get();
if (response.getStatusCode() != 200)
return false;
nlohmann::json releases;
try {
releases = nlohmann::json::parse(response.getData());
} catch (std::exception &e) {
return false;
}
// Check if the response is valid
if (!releases.contains("tag_name") || !releases["tag_name"].is_string())
return false;
// Convert the current version string to a format that can be compared to the latest release
auto versionString = ImHexApi::System::getImHexVersion();
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["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());
// Check if there is a telemetry uuid
std::string uuid = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", "");
if(uuid.empty()) {
// Generate a new uuid
uuid = wolv::hash::generateUUID();
// Save
ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", uuid);
}
TaskManager::createBackgroundTask("Sending statistics...", [uuid, versionString](auto&) {
// Make telemetry request
nlohmann::json telemetry = {
{ "uuid", uuid },
{ "format_version", "1" },
{ "imhex_version", versionString },
{ "imhex_commit", fmt::format("{}@{}", ImHexApi::System::getCommitHash(true), ImHexApi::System::getCommitBranch()) },
{ "install_type", ImHexApi::System::isPortableVersion() ? "Portable" : "Installed" },
{ "os", ImHexApi::System::getOSName() },
{ "os_version", ImHexApi::System::getOSVersion() },
{ "arch", ImHexApi::System::getArchitecture() },
{ "gpu_vendor", ImHexApi::System::getGPUVendor() }
};
HttpRequest telemetryRequest("POST", ImHexApiURL + "/telemetry"s);
telemetryRequest.setTimeout(500);
telemetryRequest.setBody(telemetry.dump());
telemetryRequest.addHeader("Content-Type", "application/json");
// Execute request
telemetryRequest.execute();
});
}
return true;
}
bool setupEnvironment() {
hex::log::debug("Using romfs: '{}'", romfs::name());
return true;
}
bool createDirectories() {
bool result = true;
using enum fs::ImHexPath;
// 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 {
wolv::io::fs::createDirectories(folder);
} catch (...) {
log::error("Failed to create folder {}!", wolv::util::toUTF8String(folder));
result = false;
}
}
}
if (!result)
ImHexApi::System::impl::addInitArgument("folder-creation-error");
return result;
}
bool migrateConfig(){
// Check if there is a new config in folder
auto configPaths = hex::fs::getDefaultPaths(hex::fs::ImHexPath::Config, false);
// There should always be exactly one config path on Linux
std::fs::path newConfigPath = configPaths[0];
wolv::io::File newConfigFile(newConfigPath / "settings.json", wolv::io::File::Mode::Read);
if (!newConfigFile.isValid()) {
// find an old config
std::fs::path oldConfigPath;
for (const auto &dir : hex::fs::appendPath(hex::fs::getDataPaths(), "config")) {
wolv::io::File oldConfigFile(dir / "settings.json", wolv::io::File::Mode::Read);
if (oldConfigFile.isValid()) {
oldConfigPath = dir;
break;
}
}
if (!oldConfigPath.empty()) {
log::info("Found config file in {}! Migrating to {}", oldConfigPath.string(), newConfigPath.string());
std::fs::rename(oldConfigPath / "settings.json", newConfigPath / "settings.json");
wolv::io::File oldIniFile(oldConfigPath / "interface.ini", wolv::io::File::Mode::Read);
if (oldIniFile.isValid()) {
std::fs::rename(oldConfigPath / "interface.ini", newConfigPath / "interface.ini");
}
std::fs::remove(oldConfigPath);
}
}
return true;
}
static bool loadFontsImpl(bool loadUnicode) {
const float defaultFontSize = ImHexApi::System::DefaultFontSize * std::round(ImHexApi::System::getGlobalScale());
// Load font related settings
{
std::fs::path fontFile = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_path", "");
if (!fontFile.empty()) {
if (!wolv::io::fs::exists(fontFile) || !wolv::io::fs::isRegularFile(fontFile)) {
log::warn("Custom font file {} not found! Falling back to default font.", wolv::util::toUTF8String(fontFile));
fontFile.clear();
}
log::info("Loading custom font from {}", wolv::util::toUTF8String(fontFile));
}
// If no custom font has been specified, search for a file called "font.ttf" in one of the resource folders
if (fontFile.empty()) {
for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Resources)) {
auto path = dir / "font.ttf";
if (wolv::io::fs::exists(path)) {
log::info("Loading custom font from {}", wolv::util::toUTF8String(path));
fontFile = path;
break;
}
}
}
ImHexApi::System::impl::setCustomFontPath(fontFile);
// If a custom font has been loaded now, also load the font size
float fontSize = defaultFontSize;
if (!fontFile.empty()) {
fontSize = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_size", 13) * ImHexApi::System::getGlobalScale();
}
ImHexApi::System::impl::setFontSize(fontSize);
}
float fontSize = ImHexApi::System::getFontSize();
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;
fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;
// Configure font glyph ranges that should be loaded from the default font and unifont
static ImVector<ImWchar> ranges;
{
ImFontGlyphRangesBuilder glyphRangesBuilder;
{
constexpr static ImWchar controlCodeRange[] = { 0x0001, 0x001F, 0 };
constexpr static ImWchar extendedAsciiRange[] = { 0x007F, 0x00FF, 0 };
glyphRangesBuilder.AddRanges(controlCodeRange);
glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesDefault());
glyphRangesBuilder.AddRanges(extendedAsciiRange);
}
if (loadUnicode) {
constexpr static ImWchar fullRange[] = { 0x0100, 0xFFEF, 0 };
glyphRangesBuilder.AddRanges(fullRange);
} else {
glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesJapanese());
glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesChineseFull());
glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesCyrillic());
glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesKorean());
glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesThai());
glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesVietnamese());
}
glyphRangesBuilder.BuildRanges(&ranges);
}
// Glyph range for font awesome icons
static ImWchar fontAwesomeRange[] = {
ICON_MIN_FA, ICON_MAX_FA, 0
};
// Glyph range for codicons icons
static 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()) {
fonts->Clear();
fonts->AddFontDefault(&cfg);
} else {
auto font = fonts->AddFontFromFileTTF(wolv::util::toUTF8String(fontFile).c_str(), 0, &cfg, ranges.Data);
if (font == nullptr) {
log::warn("Failed to load custom font! Falling back to default font.");
ImHexApi::System::impl::setFontSize(defaultFontSize);
cfg.SizePixels = defaultFontSize;
fonts->Clear();
fonts->AddFontDefault(&cfg);
}
}
// Merge all fonts into one big font atlas
cfg.MergeMode = true;
// Add font awesome and codicons icons to font atlas
cfg.GlyphOffset = ImVec2(0, 3_scaled);
fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data, font_awesome_compressed_size, 0, &cfg, fontAwesomeRange);
fonts->AddFontFromMemoryCompressedTTF(codicons_compressed_data, codicons_compressed_size, 0, &cfg, codiconsRange);
cfg.GlyphOffset = ImVec2(0, 0);
// Add unifont if unicode support is enabled
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.");
IM_DELETE(fonts);
// Disable unicode support in settings
ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.load_all_unicode_chars", false);
// Try to load the font atlas again
return loadFontsImpl(false);
} else {
log::error("Failed to build font atlas! Check your Graphics driver!");
return false;
}
}
// Configure ImGui to use the font atlas
View::setFontAtlas(fonts);
View::setFontConfig(cfg);
return true;
}
bool loadFonts() {
// Check if unicode support is enabled in the settings and that the user doesn't use the No GPU version on Windows
// The Mesa3D software renderer on Windows identifies itself as "VMware, Inc."
bool shouldLoadUnicode =
ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.load_all_unicode_chars", false) &&
ImHexApi::System::getGPUVendor() != "VMware, Inc.";
return loadFontsImpl(shouldLoadUnicode);
}
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::post<EventImHexClosing>();
EventManager::clear();
while (ImHexApi::Provider::isValid())
ImHexApi::Provider::remove(ImHexApi::Provider::get());
ContentRegistry::Provider::impl::getEntries().clear();
ImHexApi::System::getInitArguments().clear();
ImHexApi::HexEditor::impl::getBackgroundHighlights().clear();
ImHexApi::HexEditor::impl::getForegroundHighlights().clear();
ImHexApi::HexEditor::impl::getBackgroundHighlightingFunctions().clear();
ImHexApi::HexEditor::impl::getForegroundHighlightingFunctions().clear();
ImHexApi::HexEditor::impl::getTooltips().clear();
ImHexApi::HexEditor::impl::getTooltipFunctions().clear();
ImHexApi::System::getAdditionalFolderPaths().clear();
ImHexApi::System::getCustomFontPath().clear();
ImHexApi::Messaging::impl::getHandlers().clear();
ContentRegistry::Settings::impl::getEntries().clear();
ContentRegistry::Settings::impl::getSettingsData().clear();
ContentRegistry::CommandPaletteCommands::impl::getEntries().clear();
ContentRegistry::CommandPaletteCommands::impl::getHandlers().clear();
ContentRegistry::PatternLanguage::impl::getFunctions().clear();
ContentRegistry::PatternLanguage::impl::getPragmas().clear();
ContentRegistry::PatternLanguage::impl::getVisualizers().clear();
ContentRegistry::PatternLanguage::impl::getInlineVisualizers().clear();
ContentRegistry::Views::impl::getEntries().clear();
impl::PopupBase::getOpenPopups().clear();
ContentRegistry::Tools::impl::getEntries().clear();
ContentRegistry::DataInspector::impl::getEntries().clear();
ContentRegistry::Language::impl::getLanguages().clear();
ContentRegistry::Language::impl::getLanguageDefinitions().clear();
LangEntry::resetLanguageStrings();
ContentRegistry::Interface::impl::getWelcomeScreenEntries().clear();
ContentRegistry::Interface::impl::getFooterItems().clear();
ContentRegistry::Interface::impl::getToolbarItems().clear();
ContentRegistry::Interface::impl::getMainMenuItems().clear();
ContentRegistry::Interface::impl::getMenuItems().clear();
ContentRegistry::Interface::impl::getSidebarItems().clear();
ContentRegistry::Interface::impl::getTitleBarButtons().clear();
ShortcutManager::clearShortcuts();
TaskManager::getRunningTasks().clear();
ContentRegistry::DataProcessorNode::impl::getEntries().clear();
ContentRegistry::DataFormatter::impl::getEntries().clear();
ContentRegistry::FileHandler::impl::getEntries().clear();
ContentRegistry::Hashes::impl::getHashes().clear();
ContentRegistry::HexEditor::impl::getVisualizers().clear();
ContentRegistry::BackgroundServices::impl::stopServices();
ContentRegistry::BackgroundServices::impl::getServices().clear();
ContentRegistry::CommunicationInterface::impl::getNetworkEndpoints().clear();
LayoutManager::reset();
ThemeManager::reset();
AchievementManager::getAchievements().clear();
ProjectFile::getHandlers().clear();
ProjectFile::getProviderHandlers().clear();
ProjectFile::setProjectFunctions(nullptr, nullptr);
fs::setFileBrowserErrorCallback(nullptr);
return true;
}
bool loadPlugins() {
// Load all 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!");
ImHexApi::System::impl::addInitArgument("no-plugins");
return false;
}
const auto shouldLoadPlugin = [executablePath = wolv::io::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
if (!executablePath.has_value())
return true;
// 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;
if (!shouldLoadPlugin(plugin)) {
log::debug("Skipping built-in plugin {}", plugin.getPath().string());
continue;
}
// Make sure there's only one built-in plugin
if (builtinPlugins > 1) continue;
// Initialize the plugin
if (!plugin.initializePlugin()) {
log::error("Failed to initialize plugin {}", wolv::util::toUTF8String(plugin.getPath().filename()));
loadErrors++;
} else {
builtinPlugins++;
}
}
// Load all other plugins
for (const auto &plugin : plugins) {
if (plugin.isBuiltinPlugin()) continue;
if (!shouldLoadPlugin(plugin)) {
log::debug("Skipping plugin {}", plugin.getPath().string());
continue;
}
// Initialize the plugin
if (!plugin.initializePlugin()) {
log::error("Failed to initialize plugin {}", wolv::util::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");
return false;
} else if (builtinPlugins > 1) {
log::error("Found more than one built-in plugin!");
ImHexApi::System::impl::addInitArgument("multiple-builtin-plugins");
return false;
}
return true;
}
bool clearOldLogs() {
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Logs)) {
std::vector<std::filesystem::directory_entry> files;
try {
for (const auto& file : std::filesystem::directory_iterator(path))
files.push_back(file);
if (files.size() <= 10)
return true;
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) {
return std::filesystem::last_write_time(a) > std::filesystem::last_write_time(b);
});
for (auto it = files.begin() + 10; it != files.end(); it++)
std::filesystem::remove(it->path());
} catch (std::filesystem::filesystem_error &e) {
log::error("Failed to clear old log! {}", e.what());
continue;
}
}
return true;
}
bool unloadPlugins() {
PluginManager::unload();
return true;
}
bool loadSettings() {
try {
// Try to load settings from file
ContentRegistry::Settings::impl::load();
} catch (std::exception &e) {
// If that fails, create a new settings file
log::error("Failed to load configuration! {}", e.what());
ContentRegistry::Settings::impl::clear();
ContentRegistry::Settings::impl::store();
return false;
}
return true;
}
bool configureUIScale() {
float interfaceScaling;
switch (ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.scaling", 0)) {
default:
case 0:
interfaceScaling = ImHexApi::System::getNativeScale();
break;
case 1:
interfaceScaling = 0.5F;
break;
case 2:
interfaceScaling = 1.0F;
break;
case 3:
interfaceScaling = 1.5F;
break;
case 4:
interfaceScaling = 2.0F;
break;
case 5:
interfaceScaling = 3.0F;
break;
case 6:
interfaceScaling = 4.0F;
break;
}
ImHexApi::System::impl::setGlobalScale(interfaceScaling);
return true;
}
bool storeSettings() {
try {
ContentRegistry::Settings::impl::store();
} catch (std::exception &e) {
log::error("Failed to store configuration! {}", e.what());
return false;
}
return true;
}
std::vector<Task> getInitTasks() {
return {
{ "Setting up environment", setupEnvironment, false },
{ "Creating directories", createDirectories, false },
#if defined(OS_LINUX)
{ "Migrate config to .config", migrateConfig, false },
#endif
{ "Loading settings", loadSettings, false },
{ "Configuring UI scale", configureUIScale, false },
{ "Loading plugins", loadPlugins, true },
{ "Checking for updates", checkForUpdates, true },
{ "Loading fonts", loadFonts, true },
};
}
std::vector<Task> getExitTasks() {
return {
{ "Saving settings...", storeSettings, false },
{ "Cleaning up shared data...", deleteSharedData, false },
{ "Unloading plugins...", unloadPlugins, false },
{ "Clearing old logs...", clearOldLogs, false },
};
}
}

153
main/gui/source/main.cpp Normal file
View File

@@ -0,0 +1,153 @@
#include <hex.hpp>
#include <hex/helpers/logger.hpp>
#include "window.hpp"
#include "crash_handlers.hpp"
#include "messaging.hpp"
#include "init/splash_window.hpp"
#include "init/tasks.hpp"
#include <hex/api/task.hpp>
#include <hex/api/project_file_manager.hpp>
#include <hex/api/plugin_manager.hpp>
#include <hex/helpers/fs.hpp>
#include "hex/subcommands/subcommands.hpp"
#include <wolv/io/fs.hpp>
#include <wolv/utils/guards.hpp>
using namespace hex;
namespace {
/**
* @brief Handles commands passed to ImHex via the command line
* @param argc Argument count
* @param argv Argument values
*/
void handleCommandLineInterface(int argc, char **argv) {
// Skip the first argument (the executable path) and convert the rest to a vector of strings
std::vector<std::string> args(argv + 1, argv + argc);
// Load all plugins but don't initialize them
for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Plugins)) {
PluginManager::load(dir);
}
// Setup messaging system to allow sending commands to the main ImHex instance
hex::messaging::setupMessaging();
// Process the arguments
hex::subcommands::processArguments(args);
// Unload plugins again
PluginManager::unload();
}
/**
* @brief Checks if the portable version of ImHex is installed
* @note The portable version is detected by the presence of a file named "PORTABLE" in the same directory as the executable
* @return True if ImHex is running in portable mode, false otherwise
*/
bool isPortableVersion() {
if (const auto executablePath = wolv::io::fs::getExecutablePath(); executablePath.has_value()) {
const auto flagFile = executablePath->parent_path() / "PORTABLE";
if (wolv::io::fs::exists(flagFile) && wolv::io::fs::isRegularFile(flagFile))
return true;
}
return false;
}
/**
* @brief Displays ImHex's splash screen and runs all initialization tasks. The splash screen will be displayed until all tasks have finished.
*/
void initializeImHex() {
init::WindowSplash splashWindow;
log::info("Using '{}' GPU", ImHexApi::System::getGPUVendor());
// Add initialization tasks to run
TaskManager::init();
for (const auto &[name, task, async] : init::getInitTasks())
splashWindow.addStartupTask(name, task, async);
splashWindow.startStartupTasks();
// Draw the splash window while tasks are running
if (!splashWindow.loop())
ImHexApi::System::getInitArguments().insert({ "tasks-failed", {} });
}
/**
* @brief Deinitializes ImHex by running all exit tasks and terminating all asynchronous tasks
*/
void deinitializeImHex() {
// Run exit tasks
for (const auto &[name, task, async] : init::getExitTasks())
task();
// Terminate all asynchronous tasks
TaskManager::exit();
}
/**
* @brief Handles a file open request by opening the file specified by OS-specific means
*/
void handleFileOpenRequest() {
if (auto path = hex::getInitialFilePath(); path.has_value()) {
EventManager::post<RequestOpenFile>(path.value());
}
}
}
/**
* @brief Main entry point of ImHex
* @param argc Argument count
* @param argv Argument values
* @return Exit code
*/
int main(int argc, char **argv) {
Window::initNative();
hex::crash::setupCrashHandlers();
if (argc > 1) {
handleCommandLineInterface(argc, argv);
}
log::info("Welcome to ImHex {}!", ImHexApi::System::getImHexVersion());
log::info("Compiled using commit {}@{}", ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash());
log::info("Running on {} {} ({})", ImHexApi::System::getOSName(), ImHexApi::System::getOSVersion(), ImHexApi::System::getArchitecture());
ImHexApi::System::impl::setPortableVersion(isPortableVersion());
bool shouldRestart = false;
do {
// Register an event handler that will make ImHex restart when requested
shouldRestart = false;
EventManager::subscribe<RequestRestartImHex>([&] {
shouldRestart = true;
});
initializeImHex();
handleFileOpenRequest();
// Clean up everything after the main window is closed
ON_SCOPE_EXIT {
deinitializeImHex();
};
// Main window
Window window;
window.loop();
} while (shouldRestart);
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,33 @@
#include <optional>
#include <hex/api/imhex_api.hpp>
#include <hex/api/event.hpp>
#include <hex/helpers/logger.hpp>
#include "messaging.hpp"
namespace hex::messaging {
void messageReceived(const std::string &eventName, const std::vector<u8> &eventData) {
log::debug("Received event '{}' with size {}", eventName, eventData.size());
ImHexApi::Messaging::impl::runHandler(eventName, eventData);
}
void setupEvents() {
EventManager::subscribe<SendMessageToMainInstance>([](const std::string eventName, const std::vector<u8> &eventData) {
log::debug("Forwarding message {} (maybe to us)", eventName);
if (ImHexApi::System::isMainInstance()) {
EventManager::subscribe<EventImHexStartupFinished>([eventName, eventData](){
ImHexApi::Messaging::impl::runHandler(eventName, eventData);
});
} else {
sendToOtherInstance(eventName, eventData);
}
});
}
void setupMessaging() {
ImHexApi::System::impl::setMainInstanceStatus(setupNative());
setupEvents();
}
}

View File

@@ -0,0 +1,24 @@
#if defined(OS_LINUX)
#include<stdexcept>
#include <hex/helpers/intrinsics.hpp>
#include <hex/helpers/logger.hpp>
#include "messaging.hpp"
namespace hex::messaging {
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &args) {
hex::unused(eventName);
hex::unused(args);
log::error("Unimplemented function 'sendToOtherInstance()' called");
}
// Not implemented, so lets say we are the main instance every time so events are forwarded to ourselves
bool setupNative() {
return true;
}
}
#endif

View File

@@ -0,0 +1,24 @@
#if defined(OS_MACOS)
#include<stdexcept>
#include <hex/helpers/intrinsics.hpp>
#include <hex/helpers/logger.hpp>
#include "messaging.hpp"
namespace hex::messaging {
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &args) {
hex::unused(eventName);
hex::unused(args);
log::error("Unimplemented function 'sendToOtherInstance()' called");
}
// Not implemented, so lets say we are the main instance every time so events are forwarded to ourselves
bool setupNative() {
return true;
}
}
#endif

View File

@@ -0,0 +1,84 @@
#if defined(OS_WINDOWS)
#include "messaging.hpp"
#include <hex/api/imhex_api.hpp>
#include <hex/helpers/logger.hpp>
#include <windows.h>
namespace hex::messaging {
std::optional<HWND> getImHexWindow() {
HWND imhexWindow = 0;
::EnumWindows([](HWND hWnd, LPARAM ret) -> BOOL {
// 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) == TRUE && length != 0) {
if (windowName.starts_with("ImHex")) {
// it's our window, return it and stop iteration
*reinterpret_cast<HWND*>(ret) = hWnd;
return FALSE;
}
}
// continue iteration
return TRUE;
}, reinterpret_cast<LPARAM>(&imhexWindow));
if (imhexWindow == 0) return { };
else return imhexWindow;
}
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &eventData) {
log::debug("Sending event {} to another instance (not us)", eventName);
// Get the window we want to send it to
HWND imHexWindow = *getImHexWindow();
// Create the message
// TODO actually send all arguments and not just the eventName
std::vector<u8> fulleventData(eventName.begin(), eventName.end());
fulleventData.push_back('\0');
fulleventData.insert(fulleventData.end(), eventData.begin(), eventData.end());
u8 *data = &fulleventData[0];
DWORD dataSize = static_cast<DWORD>(fulleventData.size());
COPYDATASTRUCT message = {
.dwData = 0,
.cbData = dataSize,
.lpData = data
};
// Send the message
SendMessage(imHexWindow, WM_COPYDATA, reinterpret_cast<WPARAM>(imHexWindow), reinterpret_cast<LPARAM>(&message));
}
bool setupNative() {
constexpr static auto UniqueMutexId = "ImHex/a477ea68-e334-4d07-a439-4f159c683763";
// check if an ImHex instance is already running by opening a global mutex
HANDLE globalMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, UniqueMutexId);
if (globalMutex == nullptr) {
// If no ImHex instance is running, create a new global mutex
globalMutex = CreateMutex(nullptr, FALSE, UniqueMutexId);
return true;
} else {
return false;
}
}
}
#endif

View File

@@ -0,0 +1,102 @@
#include "window.hpp"
#if defined(OS_LINUX)
#include <hex/api/imhex_api.hpp>
#include <hex/api/content_registry.hpp>
#include <hex/api/event.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/utils_linux.hpp>
#include <hex/helpers/logger.hpp>
#include <wolv/utils/core.hpp>
#include <nlohmann/json.hpp>
#include <cstdio>
#include <sys/wait.h>
#include <unistd.h>
#include <imgui_impl_glfw.h>
#include <string.h>
#include <ranges>
namespace hex {
bool isFileInPath(const std::fs::path &filename) {
auto optPathVar = hex::getEnvironmentVariable("PATH");
if (!optPathVar.has_value()) {
log::error("Could not find variable named PATH");
return false;
}
for (auto dir : std::views::split(optPathVar.value(), ':')) {
if (std::fs::exists(std::fs::path(std::string_view(dir)) / filename)) {
return true;
}
}
return false;
}
void nativeErrorMessage(const std::string &message) {
log::fatal(message);
if (isFileInPath("zenity")) {
executeCmd({"zenity", "--error", "--text", message});
} else if(isFileInPath("notify-send")) {
executeCmd({"notify-send", "-i", "script-error", "Error", message});
} // hopefully one of these commands is installed
}
void Window::initNative() {
// Add plugin library folders to dll search path
for (const auto &path : hex::fs::getDefaultPaths(fs::ImHexPath::Libraries)) {
if (std::fs::exists(path))
setenv("LD_LIBRARY_PATH", hex::format("{};{}", hex::getEnvironmentVariable("LD_LIBRARY_PATH").value_or(""), path.string().c_str()).c_str(), true);
}
// Various libraries sadly directly print to stderr with no way to disable it
// We redirect stderr to /dev/null to prevent this
wolv::util::unused(freopen("/dev/null", "w", stderr));
setvbuf(stderr, nullptr, _IONBF, 0);
// Redirect stdout to log file if we're not running in a terminal
if (!isatty(STDOUT_FILENO)) {
log::impl::redirectToFile();
}
}
void Window::setupNativeWindow() {
bool themeFollowSystem = ImHexApi::System::usesSystemThemeDetection();
EventManager::subscribe<EventOSThemeChanged>(this, [themeFollowSystem] {
if (!themeFollowSystem) return;
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;
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
result += buffer.data();
auto exitCode = WEXITSTATUS(pclose(pipe));
if (exitCode != 0) return;
EventManager::post<RequestChangeTheme>(hex::containsIgnoreCase(result, "light") ? "Light" : "Dark");
});
if (themeFollowSystem)
EventManager::post<EventOSThemeChanged>();
}
void Window::beginNativeWindowFrame() {
}
void Window::endNativeWindowFrame() {
}
}
#endif

View File

@@ -0,0 +1,66 @@
#include "window.hpp"
#if defined(OS_MACOS)
#include <hex/api/imhex_api.hpp>
#include <hex/api/content_registry.hpp>
#include <hex/api/event.hpp>
#include <hex/helpers/utils_macos.hpp>
#include <hex/helpers/logger.hpp>
#include <nlohmann/json.hpp>
#include <cstdio>
#include <unistd.h>
#include <imgui_impl_glfw.h>
namespace hex {
void nativeErrorMessage(const std::string &message) {
log::fatal(message);
errorMessageMacos(message.c_str());
}
void Window::initNative() {
// Add plugin library folders to dll search path
for (const auto &path : hex::fs::getDefaultPaths(fs::ImHexPath::Libraries)) {
if (std::fs::exists(path))
setenv("LD_LIBRARY_PATH", hex::format("{};{}", hex::getEnvironmentVariable("LD_LIBRARY_PATH").value_or(""), path.string().c_str()).c_str(), true);
}
// Various libraries sadly directly print to stderr with no way to disable it
// We redirect stderr to /dev/null to prevent this
freopen("/dev/null", "w", stderr);
setvbuf(stderr, nullptr, _IONBF, 0);
// Redirect stdout to log file if we're not running in a terminal
if (!isatty(STDOUT_FILENO)) {
log::impl::redirectToFile();
}
}
void Window::setupNativeWindow() {
bool themeFollowSystem = ImHexApi::System::usesSystemThemeDetection();
EventManager::subscribe<EventOSThemeChanged>(this, [themeFollowSystem] {
if (!themeFollowSystem) return;
if (!isMacosSystemDarkModeEnabled())
EventManager::post<RequestChangeTheme>("Light");
else
EventManager::post<RequestChangeTheme>("Dark");
});
if (themeFollowSystem)
EventManager::post<EventOSThemeChanged>();
}
void Window::beginNativeWindowFrame() {
}
void Window::endNativeWindowFrame() {
}
}
#endif

View File

@@ -0,0 +1,384 @@
#include "window.hpp"
#include "messaging.hpp"
#include <hex/api/content_registry.hpp>
#if defined(OS_WINDOWS)
#include <hex/helpers/utils.hpp>
#include <hex/helpers/logger.hpp>
#include <wolv/utils/string.hpp>
#include <imgui.h>
#include <imgui_internal.h>
#include <fonts/codicons_font.h>
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
#undef GLFW_EXPOSE_NATIVE_WIN32
#include <winbase.h>
#include <winuser.h>
#include <dwmapi.h>
#include <windowsx.h>
#include <shobjidl.h>
#include <wrl/client.h>
#include <csignal>
#include <cstdio>
#include <imgui_impl_glfw.h>
namespace hex {
template<typename T>
using WinUniquePtr = std::unique_ptr<std::remove_pointer_t<T>, BOOL(*)(T)>;
static LONG_PTR g_oldWndProc;
static float g_titleBarHeight;
static Microsoft::WRL::ComPtr<ITaskbarList4> g_taskbarList;
void nativeErrorMessage(const std::string &message) {
log::fatal(message);
MessageBox(NULL, message.c_str(), "Error", MB_ICONERROR | MB_OK);
}
// Custom Window procedure for receiving OS events
static LRESULT commonWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_COPYDATA: {
// Handle opening files in existing instance
auto message = reinterpret_cast<COPYDATASTRUCT *>(lParam);
if (message == nullptr) break;
ssize_t nullIndex = -1;
char* messageData = reinterpret_cast<char*>(message->lpData);
size_t messageSize = message->cbData;
for (size_t i = 0; i < messageSize; i++) {
if (messageData[i] == '\0') {
nullIndex = i;
break;
}
}
if (nullIndex == -1) {
log::warn("Received invalid forwarded event");
break;
}
std::string eventName(messageData, nullIndex);
std::vector<u8> eventData(messageData + nullIndex + 1, messageData + messageSize);
hex::messaging::messageReceived(eventName, eventData);
break;
}
case WM_SETTINGCHANGE: {
// Handle Windows theme changes
if (lParam == 0) break;
if (LPCTSTR(lParam) == std::string_view("ImmersiveColorSet")) {
EventManager::post<EventOSThemeChanged>();
}
break;
}
case WM_SETCURSOR: {
if (LOWORD(lParam) != HTCLIENT) {
return CallWindowProc((WNDPROC)g_oldWndProc, hwnd, uMsg, wParam, lParam);
} else {
switch (ImGui::GetMouseCursor()) {
case ImGuiMouseCursor_Arrow:
SetCursor(LoadCursor(nullptr, IDC_ARROW));
break;
case ImGuiMouseCursor_Hand:
SetCursor(LoadCursor(nullptr, IDC_HAND));
break;
case ImGuiMouseCursor_ResizeEW:
SetCursor(LoadCursor(nullptr, IDC_SIZEWE));
break;
case ImGuiMouseCursor_ResizeNS:
SetCursor(LoadCursor(nullptr, IDC_SIZENS));
break;
case ImGuiMouseCursor_ResizeNWSE:
SetCursor(LoadCursor(nullptr, IDC_SIZENWSE));
break;
case ImGuiMouseCursor_ResizeNESW:
SetCursor(LoadCursor(nullptr, IDC_SIZENESW));
break;
case ImGuiMouseCursor_ResizeAll:
SetCursor(LoadCursor(nullptr, IDC_SIZEALL));
break;
case ImGuiMouseCursor_NotAllowed:
SetCursor(LoadCursor(nullptr, IDC_NO));
break;
case ImGuiMouseCursor_TextInput:
SetCursor(LoadCursor(nullptr, IDC_IBEAM));
break;
}
return TRUE;
}
}
default:
break;
}
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_MOUSELAST:
break;
case WM_NCACTIVATE:
case WM_NCPAINT:
// Handle Windows Aero Snap
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
case WM_NCCALCSIZE: {
// Handle window resizing
RECT &rect = *reinterpret_cast<RECT *>(lParam);
RECT client = rect;
CallWindowProc((WNDPROC)g_oldWndProc, hwnd, uMsg, wParam, lParam);
if (IsMaximized(hwnd)) {
WINDOWINFO windowInfo = { };
windowInfo.cbSize = sizeof(WINDOWINFO);
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;
}
return 0;
}
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()),
static_cast<LONG>((::GetSystemMetrics(SM_CYFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER)) * ImHexApi::System::getGlobalScale())
};
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::IsAnyItemHovered() || 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;
}
return commonWindowProc(hwnd, uMsg, wParam, lParam);
}
void Window::initNative() {
HWND consoleWindow = ::GetConsoleWindow();
DWORD processId = 0;
::GetWindowThreadProcessId(consoleWindow, &processId);
if (GetCurrentProcessId() == processId) {
ShowWindow(consoleWindow, SW_HIDE);
log::impl::redirectToFile();
} else {
auto hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole != INVALID_HANDLE_VALUE) {
DWORD mode = 0;
if (::GetConsoleMode(hConsole, &mode) == TRUE) {
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT;
::SetConsoleMode(hConsole, mode);
}
}
}
ImHexApi::System::impl::setBorderlessWindowMode(true);
// Add plugin library folders to dll search path
for (const auto &path : hex::fs::getDefaultPaths(fs::ImHexPath::Libraries)) {
if (std::fs::exists(path))
AddDllDirectory(path.c_str());
}
// Various libraries sadly directly print to stderr with no way to disable it
// We redirect stderr to NUL to prevent this
freopen("NUL:", "w", stderr);
setvbuf(stderr, nullptr, _IONBF, 0);
}
void Window::setupNativeWindow() {
// Setup borderless window
auto hwnd = glfwGetWin32Window(this->m_window);
bool borderlessWindowMode = ImHexApi::System::isBorderlessWindowModeEnabled();
// Set up the correct window procedure based on the borderless window mode state
if (borderlessWindowMode) {
g_oldWndProc = ::SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)borderlessWindowProc);
MARGINS borderless = { 1, 1, 1, 1 };
::DwmExtendFrameIntoClientArea(hwnd, &borderless);
DWORD attribute = DWMNCRP_ENABLED;
::DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &attribute, sizeof(attribute));
::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE);
::SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_OVERLAPPEDWINDOW);
} else {
g_oldWndProc = ::SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)commonWindowProc);
}
// Add a custom exception handler to detect heap corruptions
{
::AddVectoredExceptionHandler(TRUE, [](PEXCEPTION_POINTERS exception) -> LONG {
if ((exception->ExceptionRecord->ExceptionCode & 0xF000'0000) == 0xC000'0000) {
log::fatal("Exception raised: 0x{:08X}", exception->ExceptionRecord->ExceptionCode);
if (exception->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION) {
log::fatal("Heap corruption detected!");
}
}
return EXCEPTION_CONTINUE_SEARCH;
});
}
// 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;
}
});
}
struct ACCENTPOLICY {
u32 accentState;
u32 accentFlags;
u32 gradientColor;
u32 animationId;
};
struct WINCOMPATTRDATA {
int attribute;
PVOID pData;
ULONG dataSize;
};
EventManager::subscribe<EventThemeChanged>([this]{
auto hwnd = glfwGetWin32Window(this->m_window);
static auto user32Dll = WinUniquePtr<HMODULE>(LoadLibraryA("user32.dll"), FreeLibrary);
if (user32Dll != nullptr) {
using SetWindowCompositionAttributeFunc = BOOL(WINAPI*)(HWND, WINCOMPATTRDATA*);
const auto SetWindowCompositionAttribute =
(SetWindowCompositionAttributeFunc)
(void*)
GetProcAddress(user32Dll.get(), "SetWindowCompositionAttribute");
if (SetWindowCompositionAttribute != nullptr) {
ACCENTPOLICY policy = { ImGui::GetCustomStyle().WindowBlur > 0.5F ? 4U : 0U, 0, ImGui::GetCustomColorU32(ImGuiCustomCol_BlurBackground), 0 };
WINCOMPATTRDATA data = { 19, &policy, sizeof(ACCENTPOLICY) };
SetWindowCompositionAttribute(hwnd, &data);
}
}
});
}
void Window::beginNativeWindowFrame() {
g_titleBarHeight = ImGui::GetCurrentWindow()->MenuBarHeight();
}
void Window::endNativeWindowFrame() {
if (!ImHexApi::System::isBorderlessWindowModeEnabled())
return;
}
}
#endif

File diff suppressed because it is too large Load Diff