Emscripten: Simulate key press when a html button is clicked.

Created on 12 Jul 2015  路  4Comments  路  Source: emscripten-core/emscripten

My emscripten app uses keyboard input, however, I want to run the app on mobile. Hence, I just want to add a few html buttons that when clicked on, simulate a key press. This task seems pretty trivial but I can't make it work. I tried dispatching keypress events from document (document.dispatchEvent(keyboardEvent);), nothing seems to happen. Am I not emitting the right event? Or does emscripten not use event listeners? Should I call a function directly or something?

Help will be greatly appreciated.

Thanks

wontfix

Most helpful comment

I had to do something similar recently, and here is what I did for mouse events:

// eventType: "mousemove", "mousedown" or "mouseup".
// x and y: Normalized coordinate in the range [0,1] where to inject the event.
// button: which button was clicked. 0 = mouse left button. If eventType="mousemove", pass 0.
function simulateMouseEvent(eventType, x, y, button) {
  // Remap from [0,1] to canvas CSS pixel size.
  x *= Module['canvas'].clientWidth;
  y *= Module['canvas'].clientHeight;
  var rect = Module['canvas'].getBoundingClientRect();
  // Offset the injected coordinate from top-left of the client area to the top-left of the canvas.
  x = Math.round(rect.left + x);
  y = Math.round(rect.top + y);
  var e = document.createEvent("MouseEvents");
  e.initMouseEvent(eventType, true, true, window,
       eventType == 'mousemove' ? 0 : 1, x, y, x, y,
       0, 0, 0, 0,
       button, null);

    // To dispatch directly to Emscripten's html5.h API (use this if the Emscripten page uses emscripten/html5.h event handlers):
  if (typeof JSEvents !== 'undefined' && JSEvents.eventHandlers && JSEvents.eventHandlers.length > 0) {
    for(var i = 0; i < JSEvents.eventHandlers.length; ++i) {
      if ((JSEvents.eventHandlers[i].target == Module['canvas'] || JSEvents.eventHandlers[i].target == window)
       && JSEvents.eventHandlers[i].eventTypeString == eventType) {
         JSEvents.eventHandlers[i].handlerFunc(e);
      }
    }
  } else {
    // Alternatively, to dispatch directly to browser (use this if the Emscripten page uses SDL or something else to grab mouse input):
      Module['canvas'].dispatchEvent(e);
  }
}

and for keyboard events:

function simulateKeyEvent(eventType, keyCode, charCode) {
  var e = document.createEventObject ? document.createEventObject() : document.createEvent("Events");
  if (e.initEvent) e.initEvent(eventType, true, true);

  e.keyCode = keyCode;
  e.which = keyCode;
  e.charCode = charCode;

  // Dispatch directly to Emscripten's html5.h API (use this if page uses emscripten/html5.h event handling):
  if (typeof JSEvents !== 'undefined' && JSEvents.eventHandlers && JSEvents.eventHandlers.length > 0) {
    for(var i = 0; i < JSEvents.eventHandlers.length; ++i) {
      if ((JSEvents.eventHandlers[i].target == Module['canvas'] || JSEvents.eventHandlers[i].target == window)
       && JSEvents.eventHandlers[i].eventTypeString == eventType) {
         JSEvents.eventHandlers[i].handlerFunc(e);
      }
    }
  } else {
    // Dispatch to browser for real (use this if page uses SDL or something else for event handling):
    Module['canvas'].dispatchEvent ? Module['canvas'].dispatchEvent(e) : Module['canvas'].fireEvent("on" + eventType, e);
  }
}

I was bit by a Chrome issue with the KeyboardEvent object, see http://stackoverflow.com/questions/8942678/keyboardevent-in-chrome-keycode-is-0/12522752#12522752

Hopefully that is useful!

All 4 comments

Search for simulateKeyEvent in tests/*.py, we have similar code in the test suite that could be helpful.

I had to do something similar recently, and here is what I did for mouse events:

// eventType: "mousemove", "mousedown" or "mouseup".
// x and y: Normalized coordinate in the range [0,1] where to inject the event.
// button: which button was clicked. 0 = mouse left button. If eventType="mousemove", pass 0.
function simulateMouseEvent(eventType, x, y, button) {
  // Remap from [0,1] to canvas CSS pixel size.
  x *= Module['canvas'].clientWidth;
  y *= Module['canvas'].clientHeight;
  var rect = Module['canvas'].getBoundingClientRect();
  // Offset the injected coordinate from top-left of the client area to the top-left of the canvas.
  x = Math.round(rect.left + x);
  y = Math.round(rect.top + y);
  var e = document.createEvent("MouseEvents");
  e.initMouseEvent(eventType, true, true, window,
       eventType == 'mousemove' ? 0 : 1, x, y, x, y,
       0, 0, 0, 0,
       button, null);

    // To dispatch directly to Emscripten's html5.h API (use this if the Emscripten page uses emscripten/html5.h event handlers):
  if (typeof JSEvents !== 'undefined' && JSEvents.eventHandlers && JSEvents.eventHandlers.length > 0) {
    for(var i = 0; i < JSEvents.eventHandlers.length; ++i) {
      if ((JSEvents.eventHandlers[i].target == Module['canvas'] || JSEvents.eventHandlers[i].target == window)
       && JSEvents.eventHandlers[i].eventTypeString == eventType) {
         JSEvents.eventHandlers[i].handlerFunc(e);
      }
    }
  } else {
    // Alternatively, to dispatch directly to browser (use this if the Emscripten page uses SDL or something else to grab mouse input):
      Module['canvas'].dispatchEvent(e);
  }
}

and for keyboard events:

function simulateKeyEvent(eventType, keyCode, charCode) {
  var e = document.createEventObject ? document.createEventObject() : document.createEvent("Events");
  if (e.initEvent) e.initEvent(eventType, true, true);

  e.keyCode = keyCode;
  e.which = keyCode;
  e.charCode = charCode;

  // Dispatch directly to Emscripten's html5.h API (use this if page uses emscripten/html5.h event handling):
  if (typeof JSEvents !== 'undefined' && JSEvents.eventHandlers && JSEvents.eventHandlers.length > 0) {
    for(var i = 0; i < JSEvents.eventHandlers.length; ++i) {
      if ((JSEvents.eventHandlers[i].target == Module['canvas'] || JSEvents.eventHandlers[i].target == window)
       && JSEvents.eventHandlers[i].eventTypeString == eventType) {
         JSEvents.eventHandlers[i].handlerFunc(e);
      }
    }
  } else {
    // Dispatch to browser for real (use this if page uses SDL or something else for event handling):
    Module['canvas'].dispatchEvent ? Module['canvas'].dispatchEvent(e) : Module['canvas'].fireEvent("on" + eventType, e);
  }
}

I was bit by a Chrome issue with the KeyboardEvent object, see http://stackoverflow.com/questions/8942678/keyboardevent-in-chrome-keycode-is-0/12522752#12522752

Hopefully that is useful!

This issue has been automatically marked as stale because there has been no activity in the past 2 years. It will be closed automatically if no further activity occurs in the next 7 days. Feel free to re-open at any time if this issue is still relevant.

I documented this since it was kinda tricky here:
https://ryanpcmcquen.org/code/2020/11/26/porting-a-c-game-to-webassembly.html

Here's the JavaScript I used:

            // Thanks to @juj for some hints on this issue as to how to get this working:
            // https://github.com/emscripten-core/emscripten/issues/3614#issuecomment-142032269
            var create_and_fire_event = function (event, type) {
                var control_event = new Event(type, { bubbles: true });

                control_event.code = event.target.dataset.key;
                control_event.key = event.target.dataset.key;

                control_event.keyCode = event.target.dataset.which;
                control_event.which = event.target.dataset.which;

                Module.canvas.dispatchEvent(control_event);
            };

            Module.postRun.push(function () {
                var transform_multiplier = 0.75;
                var minimum_transform_multiplier = 0.6;
                while ((Module.canvas.width * transform_multiplier) > window.innerWidth && transform_multiplier > minimum_transform_multiplier) {
                    transform_multiplier = transform_multiplier - 0.05;
                }
                Module.canvas.parentNode.style.transform = `scale(${transform_multiplier})`;
                var margin_removal = (window.innerWidth * (transform_multiplier > minimum_transform_multiplier ? transform_multiplier - 0.1 : transform_multiplier)) / 2;
                Module.canvas.parentNode.style.margin = `-${margin_removal * 0.15}px -${margin_removal}px`;
                Array.from(document.querySelectorAll('.controls')).forEach(
                    function (control) {
                        if ('ontouchstart' in document.documentElement) {
                            // Mobile:
                            control.addEventListener('touchstart', function (
                                event
                            ) {
                                create_and_fire_event(event, 'keydown');
                            });
                            control.addEventListener('touchend', function (
                                event
                            ) {
                                create_and_fire_event(event, 'keyup');
                            });
                        } else {
                            // Desktop:
                            control.addEventListener('click', function (event) {
                                create_and_fire_event(event, 'keydown');
                                window.setTimeout(function () {
                                    create_and_fire_event(event, 'keyup');
                                }, 100);
                            });
                        }
                    }
                );
            });
Was this page helpful?
0 / 5 - 0 ratings