Browse Source

EmscriptenApplication: get canvas target from Module['canvas'] and properly account for -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR

Module['canvas'] can be read even from code compiled with -s MODULARIZE so it's a better option than hardcoding it in Configuration. The target strings in Emscripten depend on whether we're compiled with DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR (see https://github.com/emscripten-core/emscripten/pull/7977). This is now detected and handled at runtime to prepend element IDs with # and use the correct window and document magic targets.
pull/480/head
Pablo Escobar 6 years ago
parent
commit
b1c31831a8
  1. 123
      src/Magnum/Platform/EmscriptenApplication.cpp
  2. 24
      src/Magnum/Platform/EmscriptenApplication.h

123
src/Magnum/Platform/EmscriptenApplication.cpp

@ -266,12 +266,19 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration) {
} }
#endif #endif
_canvasTarget = configuration.canvasTarget(); std::ostream* verbose = _verboseLog ? Debug::output() : nullptr;
_deprecatedTargetBehavior = checkForDeprecatedEmscriptenTargetBehavior();
if(_deprecatedTargetBehavior) {
Debug{verbose} << "Platform::EmscriptenApplication::tryCreate(): using old Emscripten target behavior";
}
_canvasTarget = (_deprecatedTargetBehavior ? "" : "#") + canvasId();
_dpiScaling = dpiScaling(configuration); _dpiScaling = dpiScaling(configuration);
if(!configuration.size().isZero()) { if(!configuration.size().isZero()) {
const Vector2i scaledCanvasSize = configuration.size()*_dpiScaling; const Vector2i scaledCanvasSize = configuration.size()*_dpiScaling;
emscripten_set_canvas_element_size(_canvasTarget.c_str(), scaledCanvasSize.x(), scaledCanvasSize.y()); emscripten_set_canvas_element_size(_canvasTarget.data(), scaledCanvasSize.x(), scaledCanvasSize.y());
} }
setupCallbacks(!!(configuration.windowFlags() & Configuration::WindowFlag::Resizable)); setupCallbacks(!!(configuration.windowFlags() & Configuration::WindowFlag::Resizable));
@ -329,7 +336,16 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const
_devicePixelRatio = Vector2{Float(emscripten_get_device_pixel_ratio())}; _devicePixelRatio = Vector2{Float(emscripten_get_device_pixel_ratio())};
Debug{verbose} << "Platform::EmscriptenApplication: device pixel ratio" << _devicePixelRatio.x(); Debug{verbose} << "Platform::EmscriptenApplication: device pixel ratio" << _devicePixelRatio.x();
_canvasTarget = configuration.canvasTarget(); /* Find out which elemen target strings Emscripten expects. This depends on
the DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR compiler option. */
_deprecatedTargetBehavior = checkForDeprecatedEmscriptenTargetBehavior();
if(_deprecatedTargetBehavior) {
Debug{verbose} << "Platform::EmscriptenApplication::tryCreate(): using old Emscripten target behavior";
}
/* Get the canvas ID from Module.canvas, either set by EmscriptenApplication.js
or overridden/manually set by the user. */
_canvasTarget = (_deprecatedTargetBehavior ? "" : "#") + canvasId();
/* Get CSS canvas size and cache it. This is used later to detect canvas /* Get CSS canvas size and cache it. This is used later to detect canvas
resizes in emscripten_set_resize_callback() and fire viewport events, resizes in emscripten_set_resize_callback() and fire viewport events,
@ -350,10 +366,10 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const
} }
_dpiScaling = dpiScaling(configuration); _dpiScaling = dpiScaling(configuration);
const Vector2i scaledCanvasSize = canvasSize*_dpiScaling*_devicePixelRatio; const Vector2i scaledCanvasSize = canvasSize*_dpiScaling*_devicePixelRatio;
emscripten_set_canvas_element_size(_canvasTarget.c_str(), scaledCanvasSize.x(), scaledCanvasSize.y()); emscripten_set_canvas_element_size(_canvasTarget.data(), scaledCanvasSize.x(), scaledCanvasSize.y());
/* Create WebGL context */ /* Create WebGL context */
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(_canvasTarget.c_str(), &attrs); EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(_canvasTarget.data(), &attrs);
if(!context) { if(!context) {
/* When context creation fails, `context` is a negative integer /* When context creation fails, `context` is a negative integer
matching EMSCRIPTEN_RESULT_* defines */ matching EMSCRIPTEN_RESULT_* defines */
@ -375,14 +391,14 @@ bool EmscriptenApplication::tryCreate(const Configuration& configuration, const
Vector2i EmscriptenApplication::windowSize() const { Vector2i EmscriptenApplication::windowSize() const {
Vector2d size; Vector2d size;
emscripten_get_element_css_size(_canvasTarget.c_str(), &size.x(), &size.y()); emscripten_get_element_css_size(_canvasTarget.data(), &size.x(), &size.y());
return Vector2i{Math::round(size)}; return Vector2i{Math::round(size)};
} }
#ifdef MAGNUM_TARGET_GL #ifdef MAGNUM_TARGET_GL
Vector2i EmscriptenApplication::framebufferSize() const { Vector2i EmscriptenApplication::framebufferSize() const {
Vector2i size; Vector2i size;
emscripten_get_canvas_element_size(_canvasTarget.c_str(), &size.x(), &size.y()); emscripten_get_canvas_element_size(_canvasTarget.data(), &size.x(), &size.y());
return size; return size;
} }
#endif #endif
@ -422,7 +438,7 @@ void EmscriptenApplication::handleCanvasResize(const EmscriptenUiEvent* event) {
if(canvasSize != _lastKnownCanvasSize) { if(canvasSize != _lastKnownCanvasSize) {
_lastKnownCanvasSize = canvasSize; _lastKnownCanvasSize = canvasSize;
const Vector2i size = canvasSize*_dpiScaling*_devicePixelRatio; const Vector2i size = canvasSize*_dpiScaling*_devicePixelRatio;
emscripten_set_canvas_element_size(_canvasTarget.c_str(), size.x(), size.y()); emscripten_set_canvas_element_size(_canvasTarget.data(), size.x(), size.y());
ViewportEvent e{event, canvasSize, ViewportEvent e{event, canvasSize,
#ifdef MAGNUM_TARGET_GL #ifdef MAGNUM_TARGET_GL
framebufferSize(), framebufferSize(),
@ -448,11 +464,11 @@ void EmscriptenApplication::setupCallbacks(bool resizable) {
changes. Better than polling for this change in every frame like changes. Better than polling for this change in every frame like
Sdl2Application does, but still not ideal. */ Sdl2Application does, but still not ideal. */
if(resizable) { if(resizable) {
const char* target =
#ifdef EMSCRIPTEN_EVENT_TARGET_WINDOW #ifdef EMSCRIPTEN_EVENT_TARGET_WINDOW
const char* target = EMSCRIPTEN_EVENT_TARGET_WINDOW; !_deprecatedTargetBehavior ? EMSCRIPTEN_EVENT_TARGET_WINDOW :
#else
const char* target = "#window";
#endif #endif
"#window";
auto cb = [](int, const EmscriptenUiEvent* event, void* userData) -> Int { auto cb = [](int, const EmscriptenUiEvent* event, void* userData) -> Int {
static_cast<EmscriptenApplication*>(userData)->handleCanvasResize(event); static_cast<EmscriptenApplication*>(userData)->handleCanvasResize(event);
return false; /** @todo what does ignoring a resize event mean? */ return false; /** @todo what does ignoring a resize event mean? */
@ -460,21 +476,21 @@ void EmscriptenApplication::setupCallbacks(bool resizable) {
emscripten_set_resize_callback(target, this, false, cb); emscripten_set_resize_callback(target, this, false, cb);
} }
emscripten_set_mousedown_callback(_canvasTarget.c_str(), this, false, emscripten_set_mousedown_callback(_canvasTarget.data(), this, false,
([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int {
MouseEvent e{*event}; MouseEvent e{*event};
static_cast<EmscriptenApplication*>(userData)->mousePressEvent(e); static_cast<EmscriptenApplication*>(userData)->mousePressEvent(e);
return e.isAccepted(); return e.isAccepted();
})); }));
emscripten_set_mouseup_callback(_canvasTarget.c_str(), this, false, emscripten_set_mouseup_callback(_canvasTarget.data(), this, false,
([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int {
MouseEvent e{*event}; MouseEvent e{*event};
static_cast<EmscriptenApplication*>(userData)->mouseReleaseEvent(e); static_cast<EmscriptenApplication*>(userData)->mouseReleaseEvent(e);
return e.isAccepted(); return e.isAccepted();
})); }));
emscripten_set_mousemove_callback(_canvasTarget.c_str(), this, false, emscripten_set_mousemove_callback(_canvasTarget.data(), this, false,
([](int, const EmscriptenMouseEvent* event, void* userData) -> Int { ([](int, const EmscriptenMouseEvent* event, void* userData) -> Int {
auto& app = *static_cast<EmscriptenApplication*>(userData); auto& app = *static_cast<EmscriptenApplication*>(userData);
/* With DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR, canvasX/Y is /* With DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR, canvasX/Y is
@ -491,7 +507,7 @@ void EmscriptenApplication::setupCallbacks(bool resizable) {
return e.isAccepted(); return e.isAccepted();
})); }));
emscripten_set_wheel_callback(_canvasTarget.c_str(), this, false, emscripten_set_wheel_callback(_canvasTarget.data(), this, false,
([](int, const EmscriptenWheelEvent* event, void* userData) -> Int { ([](int, const EmscriptenWheelEvent* event, void* userData) -> Int {
MouseScrollEvent e{*event}; MouseScrollEvent e{*event};
static_cast<EmscriptenApplication*>(userData)->mouseScrollEvent(e); static_cast<EmscriptenApplication*>(userData)->mouseScrollEvent(e);
@ -508,8 +524,7 @@ void EmscriptenApplication::setupCallbacks(bool resizable) {
1.38.27 depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 1.38.27 depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
but we don't want to force this flag on the users so the behavior but we don't want to force this flag on the users so the behavior
handles both. */ handles both. */
#ifdef EMSCRIPTEN_EVENT_TARGET_DOCUMENT char* keyboardListeningElement = reinterpret_cast<char*>(EM_ASM_INT({
char* const keyboardListeningElement = reinterpret_cast<char*>(EM_ASM_INT({
var element = Module['keyboardListeningElement'] || document; var element = Module['keyboardListeningElement'] || document;
if(element === document) return 1; if(element === document) return 1;
@ -519,29 +534,31 @@ void EmscriptenApplication::setupCallbacks(bool resizable) {
return 0; return 0;
})); }));
#else
char* const keyboardListeningElement = reinterpret_cast<char*>(EM_ASM_INT({
var element = Module['keyboardListeningElement'] || document;
if(element === document) element = {id: '#document'};
if(element === window) element = {id: '#window'};
if('id' in element)
return allocate(intArrayFromString(element.id), 'i8', ALLOC_NORMAL);
return 0;
}));
#endif
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
std::string keyboardListeningElementString;
const char* keyboardListeningTarget = keyboardListeningElement;
if(_deprecatedTargetBehavior && keyboardListeningElement == reinterpret_cast<char*>(1)) {
keyboardListeningTarget = "#document";
} else if(_deprecatedTargetBehavior && keyboardListeningElement == reinterpret_cast<char*>(2)) {
keyboardListeningTarget = "#window";
} else if(keyboardListeningElement) {
if(!_deprecatedTargetBehavior)
keyboardListeningElementString = "#";
keyboardListeningElementString += keyboardListeningElement;
std::free(keyboardListeningElement);
keyboardListeningTarget = keyboardListeningElementString.data();
}
/* Happens only if keyboardListeningElement was set, but did not have an /* Happens only if keyboardListeningElement was set, but did not have an
`id` attribute. Instead it should be either null or undefined, a DOM `id` attribute. Instead it should be either null or undefined, a DOM
element, `window` or `document`. */ element, `window` or `document`. */
CORRADE_ASSERT(keyboardListeningElement, CORRADE_ASSERT(keyboardListeningTarget,
"EmscriptenApplication::setupCallbacks(): invalid value for Module['keyboardListeningElement']", ); "EmscriptenApplication::setupCallbacks(): invalid value for Module['keyboardListeningElement']", );
/* keypress_callback does not fire for most of the keys and the modifiers /* keypress_callback does not fire for most of the keys and the modifiers
don't seem to work, keydown on the other hand works fine for all */ don't seem to work, keydown on the other hand works fine for all */
emscripten_set_keydown_callback(keyboardListeningElement, this, false, emscripten_set_keydown_callback(keyboardListeningTarget, this, false,
([](int, const EmscriptenKeyboardEvent* event, void* userData) -> Int { ([](int, const EmscriptenKeyboardEvent* event, void* userData) -> Int {
EmscriptenApplication& app = *static_cast<EmscriptenApplication*>(userData); EmscriptenApplication& app = *static_cast<EmscriptenApplication*>(userData);
const std::size_t keyLen = std::strlen(event->key); const std::size_t keyLen = std::strlen(event->key);
@ -557,20 +574,12 @@ void EmscriptenApplication::setupCallbacks(bool resizable) {
return e.isAccepted(); return e.isAccepted();
})); }));
emscripten_set_keyup_callback(keyboardListeningElement, this, false, emscripten_set_keyup_callback(keyboardListeningTarget, this, false,
([](int, const EmscriptenKeyboardEvent* event, void* userData) -> Int { ([](int, const EmscriptenKeyboardEvent* event, void* userData) -> Int {
KeyEvent e{*event}; KeyEvent e{*event};
static_cast<EmscriptenApplication*>(userData)->keyReleaseEvent(e); static_cast<EmscriptenApplication*>(userData)->keyReleaseEvent(e);
return e.isAccepted(); return e.isAccepted();
})); }));
#ifdef EMSCRIPTEN_EVENT_TARGET_DOCUMENT
if(keyboardListeningElement != EMSCRIPTEN_EVENT_TARGET_DOCUMENT &&
keyboardListeningElement != EMSCRIPTEN_EVENT_TARGET_WINDOW)
#endif
{
std::free(keyboardListeningElement);
}
} }
void EmscriptenApplication::setupAnimationFrame(bool forceAnimationFrame) { void EmscriptenApplication::setupAnimationFrame(bool forceAnimationFrame) {
@ -662,7 +671,7 @@ void EmscriptenApplication::setCursor(Cursor cursor) {
CORRADE_INTERNAL_ASSERT(UnsignedInt(cursor) < Containers::arraySize(CursorMap)); CORRADE_INTERNAL_ASSERT(UnsignedInt(cursor) < Containers::arraySize(CursorMap));
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension" #pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension"
EM_ASM_({document.querySelector(UTF8ToString($0)).style.cursor = AsciiToString($1);}, _canvasTarget.c_str(), CursorMap[UnsignedInt(cursor)]); EM_ASM_({document.querySelector(AsciiToString($0)).style.cursor = AsciiToString($1);}, _canvasTarget.data(), CursorMap[UnsignedInt(cursor)]);
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
} }
@ -739,6 +748,38 @@ void EmscriptenApplication::exit(int) {
_flags |= Flag::ExitRequested; _flags |= Flag::ExitRequested;
} }
bool EmscriptenApplication::checkForDeprecatedEmscriptenTargetBehavior() {
/* Emscripten 1.38.27 changed to generic CSS selectors from element IDs
depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 being
set.
https://github.com/emscripten-core/emscripten/pull/7977
There is no simple way to check for compiler options so check
whether the new CSS selectors are being used. If so, it should find
canvas#[id] which is any canvas with the ID of Module.canvas.
The old target behavior will look for an element with id="canvas#[id]"
which could theoretically exist but that's highly unlikely. */
bool deprecated = true;
#ifdef EMSCRIPTEN_EVENT_TARGET_WINDOW
Vector2d tempSize;
if(emscripten_get_element_css_size(("canvas#" + canvasId()).data(), &tempSize.x(), &tempSize.y()) >= 0) {
deprecated = false;
}
#endif
return deprecated;
}
std::string EmscriptenApplication::canvasId() {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
char* id = reinterpret_cast<char*>(EM_ASM_INT({
return allocate(intArrayFromString(Module['canvas'].id), 'i8', ALLOC_NORMAL);
}));
#pragma GCC diagnostic pop
std::string str = id;
std::free(id);
return str;
}
EmscriptenApplication::MouseEvent::Button EmscriptenApplication::MouseEvent::button() const { EmscriptenApplication::MouseEvent::Button EmscriptenApplication::MouseEvent::button() const {
return Button(_event.button); return Button(_event.button);
} }

24
src/Magnum/Platform/EmscriptenApplication.h

@ -874,6 +874,10 @@ class EmscriptenApplication {
* @} * @}
*/ */
private:
static bool checkForDeprecatedEmscriptenTargetBehavior();
static std::string canvasId();
private: private:
enum class Flag: UnsignedByte { enum class Flag: UnsignedByte {
Redraw = 1 << 0, Redraw = 1 << 0,
@ -896,6 +900,7 @@ class EmscriptenApplication {
Flags _flags; Flags _flags;
Cursor _cursor; Cursor _cursor;
bool _deprecatedTargetBehavior{};
std::string _canvasTarget; std::string _canvasTarget;
#ifdef MAGNUM_TARGET_GL #ifdef MAGNUM_TARGET_GL
@ -1175,7 +1180,7 @@ class EmscriptenApplication::Configuration {
*/ */
typedef Containers::EnumSet<WindowFlag> WindowFlags; typedef Containers::EnumSet<WindowFlag> WindowFlags;
/*implicit*/ Configuration() {} constexpr /*implicit*/ Configuration() {}
/** /**
* @brief Set window title * @brief Set window title
@ -1234,27 +1239,10 @@ class EmscriptenApplication::Configuration {
return *this; return *this;
} }
std::string canvasTarget() const {
/* Emscripten 1.38.27 changed to generic CSS selectors from element IDs
depending on -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 being
set (which we can't detect at compile time). Fortunately, using #canvas
works the same way both in the previous versions and the current one.
Unfortunately, this is also the only value that works the same way for
both. Further details at
https://github.com/emscripten-core/emscripten/pull/7977 */
return _canvasTarget.empty() ? "#canvas" : _canvasTarget;
}
Configuration& setCanvasTarget(const std::string& canvasTarget) {
_canvasTarget = canvasTarget;
return *this;
}
private: private:
Vector2i _size; Vector2i _size;
Vector2 _dpiScaling; Vector2 _dpiScaling;
WindowFlags _windowFlags; WindowFlags _windowFlags;
std::string _canvasTarget;
}; };
CORRADE_ENUMSET_OPERATORS(EmscriptenApplication::Configuration::WindowFlags) CORRADE_ENUMSET_OPERATORS(EmscriptenApplication::Configuration::WindowFlags)

Loading…
Cancel
Save