Skip to content
This repository was archived by the owner on Aug 31, 2021. It is now read-only.

Commit 33672ed

Browse files
committed
Merge pull request #2 from runrev/emscripten-keyboard_events
Emscripten keyboard events
2 parents 846ebed + ee83a8d commit 33672ed

File tree

4 files changed

+218
-15
lines changed

4 files changed

+218
-15
lines changed

engine/src/em-event.js

Lines changed: 181 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,15 @@ mergeInto(LibraryManager.library, {
4747

4848
// FIXME "keypress" events are deprecated
4949
['keypress', LiveCodeEvents._handleKeyboardEvent],
50+
['keyup', LiveCodeEvents._handleKeyboardEvent],
51+
['keydown', LiveCodeEvents._handleKeyboardEvent],
5052

51-
['input', LiveCodeEvents._handleKeyboardEvent],
53+
['input', LiveCodeEvents._handleInput],
54+
['beforeinput', LiveCodeEvents._handleInput],
55+
56+
['compositionstart', LiveCodeEvents._handleComposition],
57+
['compositionupdate', LiveCodeEvents._handleComposition],
58+
['compositionend', LiveCodeEvents._handleComposition],
5259
];
5360

5461
var mapLength = mapping.length;
@@ -76,6 +83,9 @@ mergeInto(LibraryManager.library, {
7683
// Make sure the canvas is treated as focusable...
7784
target.tabIndex = 0;
7885

86+
// Make it a target for text input events
87+
target.setAttribute('contentEditable', 'true');
88+
7989
LiveCodeEvents._initialised = false;
8090
},
8191

@@ -154,13 +164,61 @@ mergeInto(LibraryManager.library, {
154164
// Keyboard events
155165
// ----------------------------------------------------------------
156166

167+
// Converts a string in the form 'U+xxxx' into a codepoint number
168+
_parseCodepointString: function(string) {
169+
var codepointString = string.match(/^U\+([0-9a-fA-F]+)$/);
170+
if (codepointString) {
171+
var codepointNum = parseInt(codepointString[1], 16);
172+
if (codepointNum > 0xff) {
173+
return 0x01000000 | codepointNum;
174+
} else if (codepointNum === 0x08 || codepointNum === 0x09 || codepointNum < 0x80) {
175+
return codepointNum;
176+
} else {
177+
// Values outside the range HT,BS,0x20-0x7f tend to depend
178+
// on the browser and aren't reliable
179+
return 0;
180+
}
181+
}
182+
return 0;
183+
},
184+
185+
// Converts ASCII control characters to their X11 key equivalents
186+
// or returns zero if not a control
187+
_controlToX11Key: function(codepoint) {
188+
switch (codepoint) {
189+
case 0x0008: // Horizontal tab
190+
case 0x0009: // Backspace
191+
return 0xff00 + codepoint;
192+
193+
case 0x007f: // Delete
194+
return 0xffff;
195+
196+
default: // Unrecognised
197+
return 0;
198+
}
199+
},
200+
157201
// Generate the char_code argument needed by
158202
// MCEventQueuePostKeyPress() from JavaScript's
159203
// KeyboardEvent.key.
160204
_encodeKeyboardCharCode: function(keyboardEvent) {
161-
var key = keyboardEvent.key;
162-
var high = key.charCodeAt(0); // High surrogate
163-
var low = key.charCodeAt(1); // Low surrogate
205+
// Not all browsers implement the KeyboardEvent interface fully;
206+
// we may have to fall back to an older interface if not defined
207+
var key, high, low;
208+
if ('key' in keyboardEvent) {
209+
key = keyboardEvent.key;
210+
high = key.charCodeAt(0);
211+
low = key.charCodeAt(1);
212+
} else {
213+
// Browser uses the old-style key events. Just take whatever
214+
// charCode it has supplied (if any!)
215+
if (keyboardEvent.charCode !== 0) {
216+
return keyboardEvent.charCode;
217+
} else {
218+
// Try parsing the keyIdentifier
219+
return LiveCodeEvents._parseCodepointString(keyboardEvent.keyIdentifier);
220+
}
221+
}
164222

165223
// Check if there's actually a key code at all
166224
if (isNaN(high)) {
@@ -208,17 +266,35 @@ mergeInto(LibraryManager.library, {
208266
// FIXME Maybe this should be done in the engine?
209267
var char_code = LiveCodeEvents._encodeKeyboardCharCode(keyboardEvent);
210268

211-
// Unicode codepoints in the ISO/IEC 8859-1 range
269+
// Non-control Unicode codepoints in the ISO/IEC 8859-1 range
212270
// U+0020..U+00FF are passed directly as keycodes.
213271
// Otherwise, the codepoint is returned with bit 21 set.
214-
if (char_code > 0) {
215-
if (char_code >= 0x20 && char_code <= 0xff) {
272+
if (char_code >= 0x20) {
273+
if ((char_code >= 0x20 && char_code < 0x7f) || (char_code >= 0xa0 && char_code <= 0xff)) {
216274
return char_code;
275+
} else if (char_code > 0xff) {
276+
return char_code | 0x1000000;
217277
} else {
218-
return char_code & 0x1000000;
278+
// Control character -- handled below
219279
}
220280
}
221281

282+
// If the 'key' property isn't defined, the old-style keyboard events
283+
// are being used and we need to convert any ASCII control characters
284+
// into the corresponding XK_ code
285+
if (!('key' in keyboardEvent) && char_code !== 0) {
286+
return LiveCodeEvents._controlToX11Key(char_code);
287+
}
288+
289+
// String describing the key. This is stored in the 'key' property for
290+
// new-style key events and 'keyIdentifier' for old-style events.
291+
var keyName;
292+
if ('key' in keyboardEvent) {
293+
keyName = keyboardEvent.key;
294+
} else {
295+
keyName = keyboardEvent.keyIdentifier;
296+
}
297+
222298
// Otherwise, decode to an X11 keycode
223299
// See also DOM Level 3 KeyboardEvent key Values
224300
// <https://w3c.github.io/DOM-Level-3-Events-key/#key-value-tables>
@@ -227,20 +303,25 @@ mergeInto(LibraryManager.library, {
227303
//
228304
// Not all of these keycodes are actually understood by
229305
// the engine, but they're all included for completeness.
230-
switch (keyboardEvent.key) {
306+
switch (keyName) {
231307

232308
// Special Key Values
233-
case 'Unidentified': return 0;
309+
case 'Unidentified': return 0;
310+
case 'Dead': return 0; // Dead keys for IME composition
234311

235312
// Whitespace Keys
236313
case 'Enter': return 0xff0d; // XK_Return
237314
case 'Separator': return 0xffac; // XK_KP_Separator
238315
case 'Tab': return 0xff09; // XK_Tab
239316

240317
// Navigation Keys
318+
case 'Down':
241319
case 'ArrowDown': return 0xff54; // XK_Down
320+
case 'Left':
242321
case 'ArrowLeft': return 0xff51; // XK_Left
322+
case 'Right':
243323
case 'ArrowRight': return 0xff53; // XK_Right
324+
case 'Up':
244325
case 'ArrowUp': return 0xff52; // XK_Up
245326
case 'End': return 0xff57; // XK_End
246327
case 'Home': return 0xff50; // XK_Home
@@ -378,14 +459,25 @@ mergeInto(LibraryManager.library, {
378459
}
379460

380461
// General-Purpose Function keys
381-
var functionKey = keyboardEvent.key.match(/^F(\d+)$/);
462+
var functionKey = keyName.match(/^F(\d+)$/);
382463
if (functionKey) {
383464
var functionNum = parseInt(functionKey[1]);
384465
if (functionNum >= 1 && functionNum <= 35) {
385466
return 0xffbd + functionNum; // XK_F...
386467
}
387468
}
388469

470+
// Keys with Unicode codepoint names (U+xxxx)
471+
var codepoint = LiveCodeEvents._parseCodepointString(keyName);
472+
if (codepoint !== 0) {
473+
// Is this a control character?
474+
if (codepoint < 0x20 || codepoint === 0x7f) {
475+
return LiveCodeEvents._controlToX11Key(codepoint);
476+
} else {
477+
return codepoint;
478+
}
479+
}
480+
389481
console.debug('Don\'t know how to decode key: ' + keyboardEvent.key);
390482
return 0;
391483
},
@@ -409,10 +501,26 @@ mergeInto(LibraryManager.library, {
409501

410502
switch (e.type) {
411503
case 'keypress':
412-
case 'keyup':
413504
var char_code = LiveCodeEvents._encodeKeyboardCharCode(e);
414505
var key_code = LiveCodeEvents._encodeKeyboardKeyCode(e);
415506
LiveCodeEvents._postKeyPress(stack, mods, char_code, key_code);
507+
console.debug(e.type + ' ' + e.key + ': ' + char_code + '/' + key_code);
508+
break;
509+
case 'keyup':
510+
case 'keydown':
511+
char_code = LiveCodeEvents._encodeKeyboardCharCode(e);
512+
key_code = LiveCodeEvents._encodeKeyboardKeyCode(e);
513+
514+
// If this is a browser using old-style keyboard events, we won't get
515+
// a 'keypress' message for special keys
516+
if (!('key' in e) && e.type === 'keydown' && 0xFE00 <= key_code && key_code <= 0xFFFF) {
517+
// Dispatch the keypress to the engine
518+
LiveCodeEvents._postKeyPress(stack, mods, char_code, key_code);
519+
520+
// Suppress the default behaviour for this key
521+
e.preventDefault();
522+
}
523+
416524
console.debug(e.type + ' ' + e.key + ': ' + char_code + '/' + key_code);
417525
break;
418526
default:
@@ -422,8 +530,67 @@ mergeInto(LibraryManager.library, {
422530
});
423531
LiveCodeAsync.resume();
424532

425-
// Prevent event from propagating
426-
e.preventDefault();
533+
return false;
534+
},
535+
536+
// ----------------------------------------------------------------
537+
// Input events
538+
// ----------------------------------------------------------------
539+
540+
_postImeCompose: function(stack, enabled, offset, chars, length) {
541+
Module.ccall('MCEventQueuePostImeCompose',
542+
'number', /* bool */
543+
['number', /* MCStack* stack */
544+
'number', /* bool enabled */
545+
'number', /* uindex_t offset */
546+
'number', /* unichar_t* chars */
547+
'number'], /* uindex_t char_count */
548+
[stack, enabled, offset, chars, length]);
549+
},
550+
551+
_handleInput: function(inputEvent) {
552+
console.debug('Input event: ' + inputEvent.type + ' ' + inputEvent.data);
553+
},
554+
555+
_stringToUTF16: function(string) {
556+
var buffer = _malloc(2 * string.length + 2);
557+
Module.stringToUTF16(string, buffer, 2*string.length + 2);
558+
return [buffer, string.length];
559+
},
560+
561+
_handleComposition: function(compositionEvent) {
562+
LiveCodeAsync.delay(function() {
563+
// Stack that we're targeting
564+
var stack = LiveCodeEvents._getStack();
565+
566+
var encodedString;
567+
var chars, length;
568+
switch (compositionEvent.type) {
569+
case 'compositionstart':
570+
case 'compositionupdate':
571+
encodedString = LiveCodeEvents._stringToUTF16(compositionEvent.data);
572+
chars = encodedString[0];
573+
length = encodedString[1];
574+
console.debug('Composition event: ' + compositionEvent.type + ' ' + Module.UTF16ToString(chars));
575+
LiveCodeEvents._postImeCompose(stack, true, 0, chars, length);
576+
_free(chars);
577+
break;
578+
case 'compositionend':
579+
encodedString = LiveCodeEvents._stringToUTF16(compositionEvent.data);
580+
chars = encodedString[0];
581+
length = encodedString[1];
582+
console.debug('Composition event: ' + compositionEvent.type + ' ' + Module.UTF16ToString(chars));
583+
LiveCodeEvents._postImeCompose(stack, false, 0, chars, length);
584+
_free(chars);
585+
break;
586+
default:
587+
console.debug('Unexpected composition event type: ' + compositionEvent.type)
588+
return;
589+
}
590+
});
591+
LiveCodeAsync.resume();
592+
593+
// Preventing the IME event from propogating cancels the IME, so don't do that
427594
return false;
428595
},
429596

engine/src/em-exported.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
'_MCEmscriptenGetCurrentStack',
44
'_MCEmscriptenEventEncodeModifiers',
55

6+
'_MCEventQueuePostImeCompose',
67
'_MCEventQueuePostMouseFocus',
78
'_MCEventQueuePostMousePosition',
89
'_MCEventQueuePostMousePress',

engine/src/em-osspec-misc.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@ along with LiveCode. If not see <http://www.gnu.org/licenses/>. */
2020

2121
#include "em-util.h"
2222

23+
#include "globdefs.h"
24+
#include "filedefs.h"
25+
#include "objdefs.h"
26+
#include "parsedef.h"
27+
#include "exec.h"
28+
#include "stack.h"
29+
#include "object.h"
30+
#include "param.h"
2331
#include "sysdefs.h"
2432
#include "osspec.h"
33+
#include "globals.h"
2534

2635
/* ================================================================
2736
* Locales
@@ -37,3 +46,29 @@ MCS_getsystemlocale()
3746
/* UNCHECKED */ MCLocaleCreateWithName(MCSTR("C"), t_locale);
3847
return t_locale;
3948
}
49+
50+
51+
bool
52+
MCS_put(MCExecContext &ctxt, MCSPutKind p_kind, MCStringRef p_string)
53+
{
54+
bool t_success;
55+
switch (p_kind)
56+
{
57+
case kMCSPutOutput:
58+
case kMCSPutBeforeMessage:
59+
case kMCSPutIntoMessage:
60+
t_success = MCmb -> set(ctxt, p_string);
61+
break;
62+
case kMCSPutAfterMessage:
63+
// SN-2014-04-11 [[ FasterVariable ]] parameter updated to use the new 'set' operation on variables
64+
t_success = MCmb -> set(ctxt, p_string, kMCVariableSetAfter);
65+
break;
66+
default:
67+
t_success = false;
68+
break;
69+
}
70+
71+
// MW-2012-02-23: [[ PutUnicode ]] If we don't understand the kind
72+
// then return false (caller can then throw an error).
73+
return t_success;
74+
}

engine/src/font.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ void MCFontBreakText(MCFontRef p_font, MCStringRef p_text, MCRange p_range, MCFo
400400
else
401401
t_range = MCRangeMake(t_offset, t_break_point);
402402

403-
#if !defined(_WIN32) && !defined(_ANDROID_MOBILE)
403+
#if !defined(_WIN32) && !defined(_ANDROID_MOBILE) && !defined(__EMSCRIPTEN__)
404404
// This is a really ugly hack to get LTR/RTL overrides working correctly -
405405
// ATSUI and Pango think they know better than us and won't let us suppress
406406
// the BiDi algorithm they uses for text layout. So instead, we need to add

0 commit comments

Comments
 (0)