@@ -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 - 9 a - f A - 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
0 commit comments