mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-03-28 07:47:03 -05:00
impr: Refactored forwarder executable and add lots more information to it
This commit is contained in:
48
main/gui/CMakeLists.txt
Normal file
48
main/gui/CMakeLists.txt
Normal 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 ()
|
||||
7
main/gui/include/crash_handlers.hpp
Normal file
7
main/gui/include/crash_handlers.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace hex::crash {
|
||||
|
||||
void setupCrashHandlers();
|
||||
|
||||
}
|
||||
63
main/gui/include/init/splash_window.hpp
Normal file
63
main/gui/include/init/splash_window.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
17
main/gui/include/init/tasks.hpp
Normal file
17
main/gui/include/init/tasks.hpp
Normal 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();
|
||||
}
|
||||
36
main/gui/include/messaging.hpp
Normal file
36
main/gui/include/messaging.hpp
Normal 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);
|
||||
|
||||
}
|
||||
74
main/gui/include/window.hpp
Normal file
74
main/gui/include/window.hpp
Normal 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
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
BIN
main/gui/romfs/splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
main/gui/romfs/splash_background.png
Normal file
BIN
main/gui/romfs/splash_background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
main/gui/romfs/splash_text.png
Normal file
BIN
main/gui/romfs/splash_text.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
208
main/gui/source/crash_handlers.cpp
Normal file
208
main/gui/source/crash_handlers.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
477
main/gui/source/init/splash_window.cpp
Normal file
477
main/gui/source/init/splash_window.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
625
main/gui/source/init/tasks.cpp
Normal file
625
main/gui/source/init/tasks.cpp
Normal 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
153
main/gui/source/main.cpp
Normal 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;
|
||||
}
|
||||
33
main/gui/source/messaging/common.cpp
Normal file
33
main/gui/source/messaging/common.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
24
main/gui/source/messaging/linux.cpp
Normal file
24
main/gui/source/messaging/linux.cpp
Normal 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
|
||||
24
main/gui/source/messaging/macos.cpp
Normal file
24
main/gui/source/messaging/macos.cpp
Normal 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
|
||||
84
main/gui/source/messaging/win.cpp
Normal file
84
main/gui/source/messaging/win.cpp
Normal 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
|
||||
102
main/gui/source/window/linux_window.cpp
Normal file
102
main/gui/source/window/linux_window.cpp
Normal 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
|
||||
66
main/gui/source/window/macos_window.cpp
Normal file
66
main/gui/source/window/macos_window.cpp
Normal 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
|
||||
384
main/gui/source/window/win_window.cpp
Normal file
384
main/gui/source/window/win_window.cpp
Normal 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
|
||||
1140
main/gui/source/window/window.cpp
Normal file
1140
main/gui/source/window/window.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user