Files
imhex/lib/libimhex/source/api/imhex_api.cpp
paxcut 5c4cf7379f feat: Added Subpixel Font rendering (#2092)
Proof of concept for implementing subpixel processing in ImGui. This is
work in progress, and it is bound to have problems.

What it does:
1) Uses freetype own subpixel processing implementation to build a
32-bit color atlas for the default font only (no icons, no unifont) . 2)
Avoids pixel perfect font conversion when possible. 3) Self contained,
no ImGui source code changes.
4) Results in much improved legibility of fonts rendered on low dpi LCD
screens that use horizontal RGB pixel layouts (no BRG or OLED or CRT if
they even exist anymore)

What it doesn't:
1) Fancy class based interface. The code is barely the minimum needed to
show it can work. 2) Dual source color blending. That needs to be
implemented in shader code, so it needs to change ImGui source code
although minimally. This will result in some characters appearing dimmer
than others. Easily fixed with small fragment and vertex shaders. 3)
subpixel positioning. If characters are very thin they will look
colored, or they can be moved to improve legibility. 4) deal with
detection of fringe cases including rare pixel layouts, non LCD screens,
Mac-OS not handling subpixel rendering and any other deviation from the
standard LCD. 5) tries to be efficient in speed or memory use. Font
Atlases will be 4 times the size they were before, but there are no
noticeable delays in font loading in the examples I have tried.

Any comments and code improvements are welcome.

---------

Co-authored-by: Nik <werwolv98@gmail.com>
2025-05-11 15:36:32 +02:00

1138 lines
36 KiB
C++

#include <hex/api/imhex_api.hpp>
#include <hex/api/events/events_provider.hpp>
#include <hex/api/events/events_lifecycle.hpp>
#include <hex/api/events/events_gui.hpp>
#include <hex/api/events/requests_interaction.hpp>
#include <hex/api/events/requests_lifecycle.hpp>
#include <hex/api/events/requests_provider.hpp>
#include <hex/api/events/requests_gui.hpp>
#include <hex/api/events/events_interaction.hpp>
#include <hex/api/task_manager.hpp>
#include <hex/helpers/fmt.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/auto_reset.hpp>
#include <hex/providers/provider_data.hpp>
#include <hex/providers/provider.hpp>
#include <wolv/utils/string.hpp>
#include <utility>
#include <numeric>
#include <imgui.h>
#include <imgui_internal.h>
#include <set>
#include <algorithm>
#include <GLFW/glfw3.h>
#include <hex/helpers/utils_macos.hpp>
#if defined(OS_WINDOWS)
#include <windows.h>
#include <DSRole.h>
#else
#include <sys/utsname.h>
#include <unistd.h>
#endif
#if defined(OS_WEB)
#include <emscripten.h>
#endif
namespace hex {
namespace ImHexApi::HexEditor {
Highlighting::Highlighting(Region region, color_t color)
: m_region(region), m_color(color) {
}
Tooltip::Tooltip(Region region, std::string value, color_t color) : m_region(region), m_value(std::move(value)), m_color(color) {
}
namespace impl {
static AutoReset<std::map<u32, Highlighting>> s_backgroundHighlights;
const std::map<u32, Highlighting>& getBackgroundHighlights() {
return *s_backgroundHighlights;
}
static AutoReset<std::map<u32, HighlightingFunction>> s_backgroundHighlightingFunctions;
const std::map<u32, HighlightingFunction>& getBackgroundHighlightingFunctions() {
return *s_backgroundHighlightingFunctions;
}
static AutoReset<std::map<u32, Highlighting>> s_foregroundHighlights;
const std::map<u32, Highlighting>& getForegroundHighlights() {
return *s_foregroundHighlights;
}
static AutoReset<std::map<u32, HighlightingFunction>> s_foregroundHighlightingFunctions;
const std::map<u32, HighlightingFunction>& getForegroundHighlightingFunctions() {
return *s_foregroundHighlightingFunctions;
}
static AutoReset<std::map<u32, Tooltip>> s_tooltips;
const std::map<u32, Tooltip>& getTooltips() {
return *s_tooltips;
}
static AutoReset<std::map<u32, TooltipFunction>> s_tooltipFunctions;
const std::map<u32, TooltipFunction>& getTooltipFunctions() {
return *s_tooltipFunctions;
}
static AutoReset<std::map<u32, HoveringFunction>> s_hoveringFunctions;
const std::map<u32, HoveringFunction>& getHoveringFunctions() {
return *s_hoveringFunctions;
}
static AutoReset<std::optional<ProviderRegion>> s_currentSelection;
void setCurrentSelection(const std::optional<ProviderRegion> &region) {
if (region == Region::Invalid()) {
clearSelection();
} else {
*s_currentSelection = region;
}
}
static PerProvider<std::optional<Region>> s_hoveredRegion;
void setHoveredRegion(const prv::Provider *provider, const Region &region) {
if (region == Region::Invalid())
s_hoveredRegion.get(provider).reset();
else
s_hoveredRegion.get(provider) = region;
}
}
u32 addBackgroundHighlight(const Region &region, color_t color) {
static u32 id = 0;
id++;
impl::s_backgroundHighlights->insert({
id, Highlighting { region, color }
});
TaskManager::doLaterOnce([]{ EventHighlightingChanged::post(); });
return id;
}
void removeBackgroundHighlight(u32 id) {
impl::s_backgroundHighlights->erase(id);
TaskManager::doLaterOnce([]{ EventHighlightingChanged::post(); });
}
u32 addBackgroundHighlightingProvider(const impl::HighlightingFunction &function) {
static u32 id = 0;
id++;
impl::s_backgroundHighlightingFunctions->insert({ id, function });
TaskManager::doLaterOnce([]{ EventHighlightingChanged::post(); });
return id;
}
void removeBackgroundHighlightingProvider(u32 id) {
impl::s_backgroundHighlightingFunctions->erase(id);
TaskManager::doLaterOnce([]{ EventHighlightingChanged::post(); });
}
u32 addForegroundHighlight(const Region &region, color_t color) {
static u32 id = 0;
id++;
impl::s_foregroundHighlights->insert({
id, Highlighting { region, color }
});
TaskManager::doLaterOnce([]{ EventHighlightingChanged::post(); });
return id;
}
void removeForegroundHighlight(u32 id) {
impl::s_foregroundHighlights->erase(id);
TaskManager::doLaterOnce([]{ EventHighlightingChanged::post(); });
}
u32 addForegroundHighlightingProvider(const impl::HighlightingFunction &function) {
static u32 id = 0;
id++;
impl::s_foregroundHighlightingFunctions->insert({ id, function });
TaskManager::doLaterOnce([]{ EventHighlightingChanged::post(); });
return id;
}
void removeForegroundHighlightingProvider(u32 id) {
impl::s_foregroundHighlightingFunctions->erase(id);
TaskManager::doLaterOnce([]{ EventHighlightingChanged::post(); });
}
u32 addHoverHighlightProvider(const impl::HoveringFunction &function) {
static u32 id = 0;
id++;
impl::s_hoveringFunctions->insert({ id, function });
return id;
}
void removeHoverHighlightProvider(u32 id) {
impl::s_hoveringFunctions->erase(id);
}
static u32 s_tooltipId = 0;
u32 addTooltip(Region region, std::string value, color_t color) {
s_tooltipId++;
impl::s_tooltips->insert({ s_tooltipId, { region, std::move(value), color } });
return s_tooltipId;
}
void removeTooltip(u32 id) {
impl::s_tooltips->erase(id);
}
static u32 s_tooltipFunctionId;
u32 addTooltipProvider(TooltipFunction function) {
s_tooltipFunctionId++;
impl::s_tooltipFunctions->insert({ s_tooltipFunctionId, std::move(function) });
return s_tooltipFunctionId;
}
void removeTooltipProvider(u32 id) {
impl::s_tooltipFunctions->erase(id);
}
bool isSelectionValid() {
auto selection = getSelection();
return selection.has_value() && selection->provider != nullptr;
}
std::optional<ProviderRegion> getSelection() {
return impl::s_currentSelection;
}
void clearSelection() {
impl::s_currentSelection->reset();
}
void setSelection(const Region &region, prv::Provider *provider) {
setSelection(ProviderRegion { region, provider == nullptr ? Provider::get() : provider });
}
void setSelection(const ProviderRegion &region) {
RequestHexEditorSelectionChange::post(region);
}
void setSelection(u64 address, size_t size, prv::Provider *provider) {
setSelection({ { address, size }, provider == nullptr ? Provider::get() : provider });
}
void addVirtualFile(const std::fs::path &path, std::vector<u8> data, Region region) {
RequestAddVirtualFile::post(path, std::move(data), region);
}
const std::optional<Region>& getHoveredRegion(const prv::Provider *provider) {
return impl::s_hoveredRegion.get(provider);
}
}
namespace ImHexApi::Bookmarks {
u64 add(Region region, const std::string &name, const std::string &comment, u32 color) {
u64 id = 0;
RequestAddBookmark::post(region, name, comment, color, &id);
return id;
}
u64 add(u64 address, size_t size, const std::string &name, const std::string &comment, u32 color) {
return add(Region { address, size }, name, comment, color);
}
void remove(u64 id) {
RequestRemoveBookmark::post(id);
}
}
namespace ImHexApi::Provider {
static i64 s_currentProvider = -1;
static AutoReset<std::vector<std::unique_ptr<prv::Provider>>> s_providers;
static AutoReset<std::map<prv::Provider*, std::unique_ptr<prv::Provider>>> s_providersToRemove;
namespace impl {
static std::set<prv::Provider*> s_closingProviders;
void resetClosingProvider() {
s_closingProviders.clear();
}
std::set<prv::Provider*> getClosingProviders() {
return s_closingProviders;
}
static std::recursive_mutex s_providerMutex;
}
prv::Provider *get() {
if (!ImHexApi::Provider::isValid())
return nullptr;
return (*s_providers)[s_currentProvider].get();
}
std::vector<prv::Provider*> getProviders() {
std::vector<prv::Provider*> result;
result.reserve(s_providers->size());
for (const auto &provider : *s_providers)
result.push_back(provider.get());
return result;
}
void setCurrentProvider(i64 index) {
std::scoped_lock lock(impl::s_providerMutex);
if (TaskManager::getRunningTaskCount() > 0)
return;
if (std::cmp_less(index, s_providers->size()) && s_currentProvider != index) {
auto oldProvider = get();
s_currentProvider = index;
EventProviderChanged::post(oldProvider, get());
}
RequestUpdateWindowTitle::post();
}
void setCurrentProvider(NonNull<prv::Provider*> provider) {
std::scoped_lock lock(impl::s_providerMutex);
if (TaskManager::getRunningTaskCount() > 0)
return;
const auto providers = getProviders();
auto it = std::ranges::find(providers, provider.get());
auto index = std::distance(providers.begin(), it);
setCurrentProvider(index);
}
i64 getCurrentProviderIndex() {
return s_currentProvider;
}
bool isValid() {
return !s_providers->empty() && s_currentProvider >= 0 && s_currentProvider < i64(s_providers->size());
}
void markDirty() {
const auto provider = get();
if (!provider->isDirty()) {
provider->markDirty();
EventProviderDirtied::post(provider);
}
}
void resetDirty() {
for (const auto &provider : *s_providers)
provider->markDirty(false);
}
bool isDirty() {
return std::ranges::any_of(*s_providers, [](const auto &provider) {
return provider->isDirty();
});
}
void add(std::unique_ptr<prv::Provider> &&provider, bool skipLoadInterface, bool select) {
std::scoped_lock lock(impl::s_providerMutex);
if (TaskManager::getRunningTaskCount() > 0)
return;
if (skipLoadInterface)
provider->skipLoadInterface();
EventProviderCreated::post(provider.get());
s_providers->emplace_back(std::move(provider));
if (select || s_providers->size() == 1)
setCurrentProvider(s_providers->size() - 1);
}
void remove(prv::Provider *provider, bool noQuestions) {
std::scoped_lock lock(impl::s_providerMutex);
if (provider == nullptr)
return;
if (TaskManager::getRunningTaskCount() > 0)
return;
if (!noQuestions) {
impl::s_closingProviders.insert(provider);
bool shouldClose = true;
EventProviderClosing::post(provider, &shouldClose);
if (!shouldClose)
return;
}
const auto it = std::ranges::find_if(*s_providers, [provider](const auto &p) {
return p.get() == provider;
});
if (it == s_providers->end())
return;
if (!s_providers->empty()) {
if (it == s_providers->begin()) {
// If the first provider is being closed, select the one that's the first one now
setCurrentProvider(0);
if (s_providers->size() > 1)
EventProviderChanged::post(s_providers->at(0).get(), s_providers->at(1).get());
}
else if (std::distance(s_providers->begin(), it) == s_currentProvider) {
// If the current provider is being closed, select the one that's before it
setCurrentProvider(s_currentProvider - 1);
}
else {
// If any other provider is being closed, find the current provider in the list again and select it again
const auto currentProvider = get();
const auto currentIt = std::ranges::find_if(*s_providers, [currentProvider](const auto &p) {
return p.get() == currentProvider;
});
if (currentIt != s_providers->end()) {
auto newIndex = std::distance(s_providers->begin(), currentIt);
if (s_currentProvider == newIndex && newIndex != 0)
newIndex -= 1;
setCurrentProvider(newIndex);
} else {
// If the current provider is not in the list anymore, select the first one
setCurrentProvider(0);
}
}
}
static std::mutex eraseMutex;
// Move provider over to a list of providers to delete
eraseMutex.lock();
auto providerToRemove = it->get();
(*s_providersToRemove)[providerToRemove] = std::move(*it);
eraseMutex.unlock();
// Remove left over references from the main provider list
s_providers->erase(it);
impl::s_closingProviders.erase(provider);
if (s_currentProvider >= i64(s_providers->size()) && !s_providers->empty())
setCurrentProvider(s_providers->size() - 1);
if (s_providers->empty())
EventProviderChanged::post(provider, nullptr);
EventProviderClosed::post(providerToRemove);
RequestUpdateWindowTitle::post();
// Do the destruction of the provider in the background once all tasks have finished
TaskManager::runWhenTasksFinished([providerToRemove] {
EventProviderDeleted::post(providerToRemove);
TaskManager::createBackgroundTask("Closing Provider", [providerToRemove](Task &) {
eraseMutex.lock();
auto provider = std::move((*s_providersToRemove)[providerToRemove]);
s_providersToRemove->erase(providerToRemove);
eraseMutex.unlock();
provider->close();
});
});
}
prv::Provider* createProvider(const UnlocalizedString &unlocalizedName, bool skipLoadInterface, bool select) {
prv::Provider* result = nullptr;
RequestCreateProvider::post(unlocalizedName, skipLoadInterface, select, &result);
return result;
}
}
namespace ImHexApi::System {
namespace impl {
// Default to true means we forward to ourselves by default
static bool s_isMainInstance = true;
void setMainInstanceStatus(bool status) {
s_isMainInstance = status;
}
static ImVec2 s_mainWindowPos;
static ImVec2 s_mainWindowSize;
void setMainWindowPosition(i32 x, i32 y) {
s_mainWindowPos = ImVec2(float(x), float(y));
}
void setMainWindowSize(u32 width, u32 height) {
s_mainWindowSize = ImVec2(float(width), float(height));
}
static ImGuiID s_mainDockSpaceId;
void setMainDockSpaceId(ImGuiID id) {
s_mainDockSpaceId = id;
}
static GLFWwindow *s_mainWindowHandle;
void setMainWindowHandle(GLFWwindow *window) {
s_mainWindowHandle = window;
}
static float s_globalScale = 1.0;
void setGlobalScale(float scale) {
s_globalScale = scale;
}
static float s_nativeScale = 1.0;
void setNativeScale(float scale) {
s_nativeScale = scale;
}
static bool s_borderlessWindowMode;
void setBorderlessWindowMode(bool enabled) {
s_borderlessWindowMode = enabled;
}
static bool s_multiWindowMode = false;
void setMultiWindowMode(bool enabled) {
s_multiWindowMode = enabled;
}
static std::optional<InitialWindowProperties> s_initialWindowProperties;
void setInitialWindowProperties(InitialWindowProperties properties) {
s_initialWindowProperties = properties;
}
static AutoReset<std::string> s_gpuVendor;
void setGPUVendor(const std::string &vendor) {
s_gpuVendor = vendor;
}
static AutoReset<std::string> s_glRenderer;
void setGLRenderer(const std::string &renderer) {
s_glRenderer = renderer;
}
static AutoReset<std::map<std::string, std::string>> s_initArguments;
void addInitArgument(const std::string &key, const std::string &value) {
static std::mutex initArgumentsMutex;
std::scoped_lock lock(initArgumentsMutex);
(*s_initArguments)[key] = value;
}
static double s_lastFrameTime;
void setLastFrameTime(double time) {
s_lastFrameTime = time;
}
static bool s_windowResizable = true;
bool isWindowResizable() {
return s_windowResizable;
}
static auto& getAutoResetObjects() {
static std::set<hex::impl::AutoResetBase*> autoResetObjects;
return autoResetObjects;
}
void addAutoResetObject(hex::impl::AutoResetBase *object) {
getAutoResetObjects().insert(object);
}
void removeAutoResetObject(hex::impl::AutoResetBase *object) {
getAutoResetObjects().erase(object);
}
void cleanup() {
for (const auto &object : getAutoResetObjects())
object->reset();
}
}
bool isMainInstance() {
return impl::s_isMainInstance;
}
void closeImHex(bool noQuestions) {
RequestCloseImHex::post(noQuestions);
}
void restartImHex() {
RequestRestartImHex::post();
RequestCloseImHex::post(false);
}
void setTaskBarProgress(TaskProgressState state, TaskProgressType type, u32 progress) {
EventSetTaskBarIconState::post(u32(state), u32(type), progress);
}
static float s_targetFPS = 14.0F;
float getTargetFPS() {
return s_targetFPS;
}
void setTargetFPS(float fps) {
s_targetFPS = fps;
}
float getGlobalScale() {
return impl::s_globalScale;
}
float getNativeScale() {
return impl::s_nativeScale;
}
float getBackingScaleFactor() {
#if defined(OS_WINDOWS)
return 1.0F;
#elif defined(OS_MACOS)
return ::getBackingScaleFactor();
#elif defined(OS_LINUX)
const auto sessionType = hex::getEnvironmentVariable("XDG_SESSION_TYPE");
if (!sessionType.has_value() || sessionType == "x11")
return 1.0F;
else {
float xScale = 0, yScale = 0;
glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xScale, &yScale);
return std::midpoint(xScale, yScale);
}
#elif defined(OS_WEB)
return 1.0F;
#else
return 1.0F;
#endif
}
ImVec2 getMainWindowPosition() {
if ((ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != ImGuiConfigFlags_None)
return impl::s_mainWindowPos;
else
return { 0, 0 };
}
ImVec2 getMainWindowSize() {
return impl::s_mainWindowSize;
}
ImGuiID getMainDockSpaceId() {
return impl::s_mainDockSpaceId;
}
GLFWwindow* getMainWindowHandle() {
return impl::s_mainWindowHandle;
}
bool isBorderlessWindowModeEnabled() {
return impl::s_borderlessWindowMode;
}
bool isMutliWindowModeEnabled() {
return impl::s_multiWindowMode;
}
std::optional<InitialWindowProperties> getInitialWindowProperties() {
return impl::s_initialWindowProperties;
}
void* getLibImHexModuleHandle() {
return hex::getContainingModule(reinterpret_cast<void*>(&getLibImHexModuleHandle));
}
void addMigrationRoutine(SemanticVersion migrationVersion, std::function<void()> function) {
EventImHexUpdated::subscribe([migrationVersion, function](const SemanticVersion &oldVersion, const SemanticVersion &newVersion) {
if (oldVersion < migrationVersion && newVersion >= migrationVersion) {
function();
}
});
}
const std::map<std::string, std::string>& getInitArguments() {
return *impl::s_initArguments;
}
std::string getInitArgument(const std::string &key) {
if (impl::s_initArguments->contains(key))
return impl::s_initArguments->at(key);
else
return "";
}
static bool s_systemThemeDetection;
void enableSystemThemeDetection(bool enabled) {
s_systemThemeDetection = enabled;
EventOSThemeChanged::post();
}
bool usesSystemThemeDetection() {
return s_systemThemeDetection;
}
static AutoReset<std::vector<std::fs::path>> s_additionalFolderPaths;
const std::vector<std::fs::path>& getAdditionalFolderPaths() {
return *s_additionalFolderPaths;
}
void setAdditionalFolderPaths(const std::vector<std::fs::path> &paths) {
s_additionalFolderPaths = paths;
}
const std::string &getGPUVendor() {
return impl::s_gpuVendor;
}
const std::string &getGLRenderer() {
return impl::s_glRenderer;
}
bool isCorporateEnvironment() {
#if defined(OS_WINDOWS)
{
DSROLE_PRIMARY_DOMAIN_INFO_BASIC * info;
if ((DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (PBYTE *)&info) == ERROR_SUCCESS) && (info != nullptr))
{
bool result = std::wstring(info->DomainNameFlat) != L"WORKGROUP";
DsRoleFreeMemory(info);
return result;
} else {
DWORD size = 128;
char buffer[128];
::GetComputerNameExA(ComputerNameDnsDomain, buffer, &size);
return size > 0;
}
}
#else
return false;
#endif
}
bool isPortableVersion() {
static std::optional<bool> portable;
if (portable.has_value())
return portable.value();
if (const auto executablePath = wolv::io::fs::getExecutablePath(); executablePath.has_value()) {
const auto flagFile = executablePath->parent_path() / "PORTABLE";
portable = wolv::io::fs::exists(flagFile) && wolv::io::fs::isRegularFile(flagFile);
} else {
portable = false;
}
return portable.value();
}
std::string getOSName() {
#if defined(OS_WINDOWS)
return "Windows";
#elif defined(OS_LINUX)
#if defined(OS_FREEBSD)
return "FreeBSD";
#else
return "Linux";
#endif
#elif defined(OS_MACOS)
return "macOS";
#elif defined(OS_WEB)
return "Web";
#else
return "Unknown";
#endif
}
std::string getOSVersion() {
#if defined(OS_WINDOWS)
OSVERSIONINFOA info;
info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
::GetVersionExA(&info);
return hex::format("{}.{}.{}", info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber);
#elif defined(OS_LINUX) || defined(OS_MACOS) || defined(OS_WEB)
struct utsname details = { };
if (uname(&details) != 0) {
return "Unknown";
}
return std::string(details.release) + " " + std::string(details.version);
#else
return "Unknown";
#endif
}
std::string getArchitecture() {
#if defined(OS_WINDOWS)
SYSTEM_INFO info;
::GetNativeSystemInfo(&info);
switch (info.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_AMD64:
return "x86_64";
case PROCESSOR_ARCHITECTURE_ARM:
return "ARM";
case PROCESSOR_ARCHITECTURE_ARM64:
return "ARM64";
case PROCESSOR_ARCHITECTURE_IA64:
return "IA64";
case PROCESSOR_ARCHITECTURE_INTEL:
return "x86";
default:
return "Unknown";
}
#elif defined(OS_LINUX) || defined(OS_MACOS) || defined(OS_WEB)
struct utsname details = { };
if (uname(&details) != 0) {
return "Unknown";
}
return { details.machine };
#else
return "Unknown";
#endif
}
std::optional<LinuxDistro> getLinuxDistro() {
wolv::io::File file("/etc/os-release", wolv::io::File::Mode::Read);
std::string name;
std::string version;
auto fileContent = file.readString();
for (const auto &line : wolv::util::splitString(fileContent, "\n")) {
if (line.find("PRETTY_NAME=") != std::string::npos) {
name = line.substr(line.find("=") + 1);
std::erase(name, '\"');
} else if (line.find("VERSION_ID=") != std::string::npos) {
version = line.substr(line.find("=") + 1);
std::erase(version, '\"');
}
}
return { { name, version } };
}
SemanticVersion getImHexVersion() {
#if defined IMHEX_VERSION
static auto version = SemanticVersion(IMHEX_VERSION);
return version;
#else
return {};
#endif
}
std::string getCommitHash(bool longHash) {
#if defined GIT_COMMIT_HASH_LONG
if (longHash) {
return GIT_COMMIT_HASH_LONG;
} else {
return std::string(GIT_COMMIT_HASH_LONG).substr(0, 7);
}
#else
std::ignore = longHash;
return "Unknown";
#endif
}
std::string getCommitBranch() {
#if defined GIT_BRANCH
return GIT_BRANCH;
#else
return "Unknown";
#endif
}
bool isDebugBuild() {
#if defined DEBUG
return true;
#else
return false;
#endif
}
bool isNightlyBuild() {
return getImHexVersion().nightly();
}
bool updateImHex(UpdateType updateType) {
// Get the path of the updater executable
std::fs::path executablePath;
for (const auto &entry : std::fs::directory_iterator(wolv::io::fs::getExecutablePath()->parent_path())) {
if (entry.path().filename().string().starts_with("imhex-updater")) {
executablePath = entry.path();
break;
}
}
if (executablePath.empty() || !wolv::io::fs::exists(executablePath))
return false;
std::string updateTypeString;
switch (updateType) {
case UpdateType::Stable:
updateTypeString = "latest";
break;
case UpdateType::Nightly:
updateTypeString = "nightly";
break;
}
EventImHexClosing::subscribe([executablePath, updateTypeString] {
hex::startProgram(
hex::format("\"{}\" \"{}\"",
wolv::util::toUTF8String(executablePath),
updateTypeString
)
);
});
ImHexApi::System::closeImHex();
return true;
}
void addStartupTask(const std::string &name, bool async, const std::function<bool()> &function) {
RequestAddInitTask::post(name, async, function);
}
double getLastFrameTime() {
return impl::s_lastFrameTime;
}
void setWindowResizable(bool resizable) {
glfwSetWindowAttrib(impl::s_mainWindowHandle, GLFW_RESIZABLE, int(resizable));
impl::s_windowResizable = resizable;
}
}
namespace ImHexApi::Messaging {
namespace impl {
static AutoReset<std::map<std::string, MessagingHandler>> s_handlers;
const std::map<std::string, MessagingHandler>& getHandlers() {
return *s_handlers;
}
void runHandler(const std::string &eventName, const std::vector<u8> &args) {
const auto& handlers = getHandlers();
const auto matchHandler = handlers.find(eventName);
if (matchHandler == handlers.end()) {
log::error("Forward event handler {} not found", eventName);
} else {
matchHandler->second(args);
}
}
}
void registerHandler(const std::string &eventName, const impl::MessagingHandler &handler) {
log::debug("Registered new forward event handler: {}", eventName);
impl::s_handlers->insert({ eventName, handler });
}
}
namespace ImHexApi::Fonts {
namespace impl {
static AutoReset<std::vector<Font>> s_fonts;
const std::vector<Font>& getFonts() {
return *s_fonts;
}
static float s_fontSize = DefaultFontSize;
void setFontSize(float size) {
s_fontSize = size;
}
static AutoReset<ImFontAtlas*> s_fontAtlas;
void setFontAtlas(ImFontAtlas* fontAtlas) {
s_fontAtlas = fontAtlas;
}
static ImFont *s_boldFont = nullptr;
static ImFont *s_italicFont = nullptr;
void setFonts(ImFont *bold, ImFont *italic) {
s_boldFont = bold;
s_italicFont = italic;
}
static AutoReset<std::map<UnlocalizedString, ImFont*>> s_fontDefinitions;
std::map<UnlocalizedString, ImFont*>& getFontDefinitions() {
return *s_fontDefinitions;
}
}
GlyphRange glyph(const char *glyph) {
u32 codepoint;
ImTextCharFromUtf8(&codepoint, glyph, nullptr);
return {
.begin = u16(codepoint),
.end = u16(codepoint)
};
}
GlyphRange glyph(u32 codepoint) {
return {
.begin = u16(codepoint),
.end = u16(codepoint)
};
}
GlyphRange range(const char *glyphBegin, const char *glyphEnd) {
u32 codepointBegin, codepointEnd;
ImTextCharFromUtf8(&codepointBegin, glyphBegin, nullptr);
ImTextCharFromUtf8(&codepointEnd, glyphEnd, nullptr);
return {
.begin = u16(codepointBegin),
.end = u16(codepointEnd)
};
}
GlyphRange range(u32 codepointBegin, u32 codepointEnd) {
return {
.begin = u16(codepointBegin),
.end = u16(codepointEnd)
};
}
void loadFont(const std::fs::path &path, const std::vector<GlyphRange> &glyphRanges, Offset offset, u32 flags, std::optional<bool> scalable, std::optional<u32> defaultSize) {
wolv::io::File fontFile(path, wolv::io::File::Mode::Read);
if (!fontFile.isValid()) {
log::error("Failed to load font from file '{}'", wolv::util::toUTF8String(path));
return;
}
impl::s_fonts->emplace_back(Font {
wolv::util::toUTF8String(path.filename()),
fontFile.readVector(),
glyphRanges,
offset,
flags,
scalable,
defaultSize
});
}
void loadFont(const std::string &name, const std::span<const u8> &data, const std::vector<GlyphRange> &glyphRanges, Offset offset, u32 flags, std::optional<bool> scalable, std::optional<u32> defaultSize) {
impl::s_fonts->emplace_back(Font {
name,
{ data.begin(), data.end() },
glyphRanges,
offset,
flags,
scalable,
defaultSize
});
}
float getFontSize() {
return impl::s_fontSize;
}
ImFontAtlas* getFontAtlas() {
return impl::s_fontAtlas;
}
void registerFont(const UnlocalizedString &fontName) {
(*impl::s_fontDefinitions)[fontName] = nullptr;
}
ImFont* getFont(const UnlocalizedString &fontName) {
return (*impl::s_fontDefinitions)[fontName];
}
ImFont* Bold() {
return impl::s_boldFont;
}
ImFont* Italic() {
return impl::s_italicFont;
}
float getDpi() {
auto dpi = ImGui::GetCurrentContext()->CurrentDpiScale * 96.0F;
return dpi ? dpi : 96.0F;
}
float pixelsToPoints(float pixels) {
return pixels * (72.0 / getDpi());
}
float pointsToPixels(float points) {
return points / (72.0 / getDpi());
}
}
}