From 060f0e6e56e3f589569041072c9cfb76c56a5712 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Thu, 12 Feb 2026 21:57:20 +0100 Subject: [PATCH] web: Move over to contrib.glfw3 --- dist/web/source/index.html | 4 +- dist/web/source/wasm-config.js | 82 ------------------- lib/libimhex/source/api/imhex_api.cpp | 20 ++--- lib/third_party/imgui/backend/CMakeLists.txt | 4 + main/gui/CMakeLists.txt | 3 +- main/gui/source/init/splash_window.cpp | 13 +++ main/gui/source/window/platform/linux.cpp | 2 +- main/gui/source/window/platform/web.cpp | 84 ++++++++++---------- main/gui/source/window/platform/windows.cpp | 2 +- main/gui/source/window/window.cpp | 8 +- 10 files changed, 77 insertions(+), 145 deletions(-) diff --git a/dist/web/source/index.html b/dist/web/source/index.html index 56096005d..e280efa93 100644 --- a/dist/web/source/index.html +++ b/dist/web/source/index.html @@ -96,7 +96,9 @@ - +
+ +
diff --git a/dist/web/source/wasm-config.js b/dist/web/source/wasm-config.js index 1fdffa5e5..24766d951 100644 --- a/dist/web/source/wasm-config.js +++ b/dist/web/source/wasm-config.js @@ -110,75 +110,7 @@ var Module = { ENV.IMHEX_SKIP_SPLASH_SCREEN = "1"; }, postRun: function() { - // Patch the emscripten GLFW module to send mouse and touch events in the right order - // For ImGui interactions to correctly work with touch input, MousePos events need - // to be processed first and then MouseButton events in the next frame. By default, - // GLFW does the exact opposite, which causes buttons to require two taps to register - // and windows get "stuck" to the cursor when dragged or resized - GLFW.onMousemove = event => { - if (event.type === "touchmove") { - event.preventDefault(); - let primaryChanged = false; - for (let i of event.changedTouches) { - if (GLFW.primaryTouchId === i.identifier) { - Browser.setMouseCoords(i.pageX, i.pageY); - primaryChanged = true; - break; - } - } - if (!primaryChanged) { - return; - } - } else { - Browser.calculateMouseEvent(event); - } - }; - GLFW.onMouseButtonChanged = (event, status) => { - if (!GLFW.active) return; - if (event.target != Module["canvas"]) return; - const isTouchType = event.type === "touchstart" || event.type === "touchend" || event.type === "touchcancel"; - let eventButton = 0; - if (isTouchType) { - event.preventDefault(); - let primaryChanged = false; - if (GLFW.primaryTouchId === null && event.type === "touchstart" && event.targetTouches.length > 0) { - const chosenTouch = event.targetTouches[0]; - GLFW.primaryTouchId = chosenTouch.identifier; - Browser.setMouseCoords(chosenTouch.pageX, chosenTouch.pageY); - primaryChanged = true; - } else if (event.type === "touchend" || event.type === "touchcancel") { - for (let i of event.changedTouches) { - if (GLFW.primaryTouchId === i.identifier) { - GLFW.primaryTouchId = null; - primaryChanged = true; - break; - } - } - } - if (!primaryChanged) { - return; - } - } else { - Browser.calculateMouseEvent(event); - eventButton = GLFW.DOMToGLFWMouseButton(event); - } - if (status == 1) { - GLFW.active.buttons |= (1 << eventButton); - try { - event.target.setPointerCapture(event.pointerId); - } catch (e) {} - } else { - GLFW.active.buttons &= ~(1 << eventButton); - } - - if (GLFW.active.cursorPosFunc) { - getWasmTableEntry(GLFW.active.cursorPosFunc)(GLFW.active.id, Browser.mouseX, Browser.mouseY); - } - if (GLFW.active.mouseButtonFunc) { - getWasmTableEntry(GLFW.active.mouseButtonFunc)(GLFW.active.id, eventButton, status, GLFW.getModBits(GLFW.active)); - } - }; }, onRuntimeInitialized: function() { // Triggered when the wasm module is loaded and ready to use. @@ -198,8 +130,6 @@ var Module = { e.preventDefault(); }, false); - js_resizeCanvas() - // Turn long touches into right-clicks let timer = null; canvas.addEventListener('touchstart', event => { @@ -269,18 +199,6 @@ if (urlParams.has("lang")) { Module["arguments"].push(urlParams.get("save-editor")); } -function js_resizeCanvas() { - let canvas = document.getElementById('canvas'); - - canvas.top = canvas.parentElement.clientTop; - canvas.left = canvas.parentElement.clientLeft; - - canvas.style.width = "100%"; - canvas.style.height = "100%"; -} -let resizeObserver = new ResizeObserver(js_resizeCanvas); -resizeObserver.observe(document.getElementById("canvas")) - // Prevent some default browser shortcuts from preventing ImHex ones to work document.addEventListener('keydown', e => { if (e.ctrlKey) { diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index 179b04cba..16f068bfd 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -680,23 +680,15 @@ namespace hex { if (!sessionType.has_value() || sessionType == "x11") return 1.0F; else { - static float scaleFactor = -1; - if (scaleFactor <= 0) { - int windowW, windowH; - int displayW, displayH; - glfwGetWindowSize(getMainWindowHandle(), &windowW, &windowH); - glfwGetFramebufferSize(getMainWindowHandle(), &displayW, &displayH); + int windowW, windowH; + int displayW, displayH; + glfwGetWindowSize(getMainWindowHandle(), &windowW, &windowH); + glfwGetFramebufferSize(getMainWindowHandle(), &displayW, &displayH); - float xScale = (windowW > 0) ? float(displayW) / windowW : 1.0f; - float yScale = (windowH > 0) ? float(displayH) / windowH : 1.0f; - - scaleFactor = std::midpoint(xScale, yScale); - } - - return scaleFactor; + return (windowW > 0) ? float(displayW) / windowW : 1.0f; } #elif defined(OS_WEB) - return MAIN_THREAD_EM_ASM_INT({ return window.devicePixelRatio; }); + return emscripten_get_device_pixel_ratio(); #else return 1.0F; #endif diff --git a/lib/third_party/imgui/backend/CMakeLists.txt b/lib/third_party/imgui/backend/CMakeLists.txt index c5f54167e..11c603c7d 100644 --- a/lib/third_party/imgui/backend/CMakeLists.txt +++ b/lib/third_party/imgui/backend/CMakeLists.txt @@ -25,6 +25,10 @@ if (NOT IMHEX_EXTERNAL_PLUGIN_BUILD) target_link_libraries(imgui_backend PUBLIC ${X11_LIBRARIES}) endif() + if (EMSCRIPTEN) + target_compile_options(imgui_backend PRIVATE --use-port=contrib.glfw3) + endif() + find_package(GLFW QUIET) if (NOT GLFW_FOUND OR "${GLFW_LIBRARIES}" STREQUAL "") find_package(glfw3 QUIET) diff --git a/main/gui/CMakeLists.txt b/main/gui/CMakeLists.txt index c53b77ef5..db15315d5 100644 --- a/main/gui/CMakeLists.txt +++ b/main/gui/CMakeLists.txt @@ -38,7 +38,8 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../lib/external/libromfs ${CMAKE add_dependencies(imhex_all main) if (EMSCRIPTEN) - target_link_options(main PRIVATE -sUSE_GLFW=3 -sUSE_PTHREADS=1 -sALLOW_MEMORY_GROWTH=1 -Wno-pthreads-mem-growth) + target_compile_options(main PRIVATE --use-port=contrib.glfw3) + target_link_options(main PRIVATE --use-port=contrib.glfw3 -sUSE_PTHREADS=1 -sALLOW_MEMORY_GROWTH=1 -Wno-pthreads-mem-growth) target_link_options(main PRIVATE -sTOTAL_MEMORY=134217728) target_link_options(main PRIVATE -sMAX_WEBGL_VERSION=2) target_link_options(main PRIVATE -sGL_UNSAFE_OPTS=0) diff --git a/main/gui/source/init/splash_window.cpp b/main/gui/source/init/splash_window.cpp index 73681e405..c81a2566f 100644 --- a/main/gui/source/init/splash_window.cpp +++ b/main/gui/source/init/splash_window.cpp @@ -67,6 +67,17 @@ namespace hex::init { log::debug("OpenGL Renderer: '{}'", glRendererString); log::debug("OpenGL Version String: '{}'", glVersionString); log::debug("OpenGL Shading Language Version: '{}'", glShadingLanguageVersion); + log::debug("GLFW Backend: '{}'", [] { + switch (glfwGetPlatform()) { + case GLFW_PLATFORM_WIN32: return "Win32"; + case GLFW_PLATFORM_COCOA: return "Cocoa"; + case GLFW_PLATFORM_X11: return "X11"; + case GLFW_PLATFORM_WAYLAND: return "Wayland"; + case GLFW_PLATFORM_NULL: return "null"; + case GLFW_PLATFORM_UNAVAILABLE: return "Unavailable"; + default: return "Unknown"; + } + }()); ImHexApi::System::impl::setGPUVendor(glVendorString); ImHexApi::System::impl::setGLRenderer(glRendererString); @@ -462,6 +473,8 @@ namespace hex::init { log::error("GLFW Error [{:05X}] : {}", errorCode, desc); }); + glfwDefaultWindowHints(); + #if defined(OS_LINUX) #if defined(GLFW_WAYLAND_APP_ID) glfwWindowHintString(GLFW_WAYLAND_APP_ID, "imhex"); diff --git a/main/gui/source/window/platform/linux.cpp b/main/gui/source/window/platform/linux.cpp index d18ddf3e9..c5001bf0c 100644 --- a/main/gui/source/window/platform/linux.cpp +++ b/main/gui/source/window/platform/linux.cpp @@ -112,7 +112,7 @@ namespace hex { glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); glfwWindowHint(GLFW_DECORATED, ImHexApi::System::isBorderlessWindowModeEnabled() ? GL_FALSE : GL_TRUE); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); diff --git a/main/gui/source/window/platform/web.cpp b/main/gui/source/window/platform/web.cpp index 03ae02f17..07c6ec7f4 100644 --- a/main/gui/source/window/platform/web.cpp +++ b/main/gui/source/window/platform/web.cpp @@ -1,9 +1,13 @@ +#include + #include "window.hpp" +#include "hex/api/imhex_api/system.hpp" #if defined(OS_WEB) #include #include +#include #include #include @@ -16,20 +20,6 @@ #include #include -// Function used by c++ to get the size of the html canvas -EM_JS(int, canvas_get_width, (), { - return Module.canvas.width; -}); - -// Function used by c++ to get the size of the html canvas -EM_JS(int, canvas_get_height, (), { - return Module.canvas.height; -}); - -// Function called by javascript -EM_JS(void, resizeCanvas, (), { - js_resizeCanvas(); -}); EM_JS(bool, isMacOS, (), { return navigator.userAgent.indexOf('Mac OS X') != -1 @@ -54,6 +44,11 @@ extern "C" void handleThemeChange() { hex::EventOSThemeChanged::post(); } +EMSCRIPTEN_KEEPALIVE +extern "C" void updateFramebufferSize(int width, int height) { + glfwSetWindowSize(hex::ImHexApi::System::getMainWindowHandle(), width, height); +} + EM_JS(void, setupInputModeListener, (), { Module.canvas.addEventListener('mousedown', function() { @@ -79,9 +74,12 @@ namespace hex { void Window::configureGLFW() { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); glfwWindowHint(GLFW_DECORATED, GL_FALSE); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); + glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); + + emscripten::glfw3::SetNextWindowCanvasSelector("#canvas"); } void Window::initNative() { @@ -101,8 +99,23 @@ namespace hex { }); } + static float calculateNativeScale(GLFWwindow *window) { + int windowW, windowH; + int displayW, displayH; + glfwGetWindowSize(window, &windowW, &windowH); + glfwGetFramebufferSize(window, &displayW, &displayH); + + const auto xScale = (windowW > 0) ? float(displayW) / windowW : 1.0f; + const auto yScale = (windowH > 0) ? float(displayH) / windowH : 1.0f; + + auto scaleFactor = std::midpoint(xScale, yScale); + if (scaleFactor <= 0.0F) + scaleFactor = 1.0F; + + return scaleFactor; + } + void Window::setupNativeWindow() { - resizeCanvas(); setupThemeListener(); setupInputModeListener(); fixCanvasInPlace(); @@ -123,15 +136,20 @@ namespace hex { glfwSetWindowRefreshCallback(m_window, [](GLFWwindow *window) { auto win = static_cast(glfwGetWindowUserPointer(window)); - resizeCanvas(); win->fullFrame(); }); if (themeFollowSystem) EventOSThemeChanged::post(); - if (isMacOS()) + if (emscripten::glfw3::IsRuntimePlatformApple()) ShortcutManager::enableMacOSMode(); + + glfwSetWindowAttrib(m_window, GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); + glfwShowWindow(m_window); + emscripten::glfw3::MakeCanvasResizable(m_window, "#canvas-wrapper"); + ImHexApi::System::impl::setNativeScale(calculateNativeScale(m_window)); + EventDPIChanged::post(1.0, ImHexApi::System::getBackingScaleFactor()); } void Window::beginNativeWindowFrame() { @@ -140,40 +158,18 @@ namespace hex { void Window::endNativeWindowFrame() { static float prevScaleFactor = 0; - - const float currScaleFactor = MAIN_THREAD_EM_ASM_DOUBLE({ - try { - return window.devicePixelRatio; - } catch (e) { - return 1.0; - } - }); + const float currScaleFactor = ImHexApi::System::getBackingScaleFactor(); if (prevScaleFactor != 0 && prevScaleFactor != currScaleFactor) { EventDPIChanged::post(prevScaleFactor, currScaleFactor); - resizeCanvas(); - ImHexApi::System::impl::setNativeScale(currScaleFactor); + ImHexApi::System::impl::setNativeScale(calculateNativeScale(m_window)); ThemeManager::reapplyCurrentTheme(); } - - static i32 prevWidth = 0; - static i32 prevHeight = 0; - - auto width = canvas_get_width(); - auto height = canvas_get_height(); - - if (prevWidth != width || prevHeight != height) { - // Size has changed - - prevWidth = width; - prevHeight = height; - this->resize(width, height); - resizeCanvas(); - } - prevScaleFactor = currScaleFactor; + + glfwSetWindowSize(m_window, EM_ASM_INT({ return document.getElementById("canvas-wrapper").clientWidth; }), EM_ASM_INT({ return document.getElementById("canvas-wrapper").clientHeight; })); } } diff --git a/main/gui/source/window/platform/windows.cpp b/main/gui/source/window/platform/windows.cpp index ab0dbf10a..0473b4a5d 100644 --- a/main/gui/source/window/platform/windows.cpp +++ b/main/gui/source/window/platform/windows.cpp @@ -401,7 +401,7 @@ namespace hex { glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); } else { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); } glfwWindowHint(GLFW_DECORATED, ImHexApi::System::isBorderlessWindowModeEnabled() ? GL_FALSE : GL_TRUE); diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index da74b51c8..0d3e9655e 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -1019,8 +1019,14 @@ namespace hex { glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + + // Don't hide the window on the web build, otherwise the mouse cursor offset will not + // be calculated correctly if the canvas is not filling the entire screen + #if !defined(OS_WEB) + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + #endif + configureGLFW(); if (initialWindowProperties.has_value()) {