Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion html/src/main/java/emu/joric/gwt/GwtKeyboardMatrix.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public class GwtKeyboardMatrix extends KeyboardMatrix {
*/
public GwtKeyboardMatrix() {
keyMatrix = createKeyMatrixArray();
// Install a DOM-level Meta-key watcher. See
// registerMetaKeyWatcher's Javadoc for the macOS keyup-
// suppression behaviour it works around. Only the UI-thread
// constructor calls this - the web-worker constructor below
// has no DOM to listen to.
registerMetaKeyWatcher();
}

/**
Expand All @@ -44,7 +50,85 @@ public native JavaScriptObject getSharedArrayBuffer()/*-{
var keyMatrix = this.@emu.joric.gwt.GwtKeyboardMatrix::keyMatrix;
return keyMatrix.buffer;
}-*/;


/**
* Installs DOM-level keydown/keyup listeners that synthesise
* keyup events for non-Meta keys whose natural keyup was
* swallowed by macOS while a Meta key was held.
*
* macOS suppresses keyUp events for non-modifier keys whose keyUp
* would occur while a Meta (CMD) key is held. THis is an OS-level
* behaviour inherited by Chrome, Safari and Firefox (open
* browser bugs dating back to 2011, still unresolved as of
* writing). The consequence in libGDX's GWT backend
* (DefaultGwtInput.java) is that its internal pressedKeys[]
* array gets stuck `true` for the un-released key. Typical
* symptom for Oric input behaviour: after CMD+X, X stays pressed,
* generating auto-repeat X inputs until the next plain X press.
*
* The workaround is to listen at the DOM level (capture phase, so
* we run before libGDX's own document-level bubble-phase listener).
* On every keydown/keyup, observe event.metaKey to track Meta
* pressed/released. On the released transition, dispatch a
* synthetic keyup for every non-Meta key whose keydown we saw
* without a subsequent keyup. The synthetic events flow through
* libGDX's normal keyup handler, which clears its internal
* pressedKeys[] state. (Note that in the case where a non-Meta
* key is still held down when Meta is released, MacOS appears
* to generate suitable key events so they effectively appear as
* new key presses without the Meta modifier.)
*/
private native void registerMetaKeyWatcher()/*-{
var metaPressed = false;
// DOM keyCodes of non-Meta keys we've seen keydown but not
// (yet) keyup for. Excludes the Meta key DOM codes (91, 92,
// 93 - values vary slightly across browsers and key
// locations - plus 224 for some Firefox variants).
var trackedKeys = {};

var update = function(e) {
var wasMetaPressed = metaPressed;
var isMetaPressed = !!e.metaKey;
metaPressed = isMetaPressed;
var code = e.keyCode;

// Track DOM press/release for non-Meta keys.
if (code !== 91 && code !== 92 && code !== 93 && code !== 224) {
if (e.type === 'keydown') {
trackedKeys[code] = true;
} else if (e.type === 'keyup') {
delete trackedKeys[code];
}
}

// Meta key just released. Synthesise keyup events for any
// still-tracked keys so libGDX's internal pressedKeys[]
// gate is cleared. Snapshot before iterating because the
// synthetic dispatch re-enters this handler (the keyup
// branch above) and would otherwise mutate trackedKeys
// mid-iteration.
if (wasMetaPressed && !isMetaPressed) {
var snapshot = [];
for (var k in trackedKeys) snapshot.push(k);
trackedKeys = {};
for (var i = 0; i < snapshot.length; i++) {
var kc = +snapshot[i];
// keyCode and which aren't part of KeyboardEvent's init dict,
// so passing them to the constructor has no effect. Override
// via Object.defineProperty after construction. libGDX's
// keyForCode reads keyCode via NativeEvent.getKeyCode().
var ke = new KeyboardEvent('keyup', { bubbles: true, cancelable: true });
Object.defineProperty(ke, 'keyCode', { value: kc });
Object.defineProperty(ke, 'which', { value: kc });
$doc.dispatchEvent(ke);
}
}
};

$doc.addEventListener('keydown', update, true);
$doc.addEventListener('keyup', update, true);
}-*/;

@Override
public int getKeyMatrixRow(int row) {
return keyMatrix.get(row);
Expand Down
Loading