@@ -9,9 +9,11 @@ import {
99 DeviceSpec , EventMap , Keyboard , KeyboardMinimalInterface , KeyboardProcessor ,
1010 KeyEvent , KMXKeyboard , SyntheticTextStore , MutableSystemStore , TextStore , ProcessorAction ,
1111 StateKeyMap ,
12- Deadkey
12+ Deadkey ,
13+ Codes
1314} from "keyman/engine/keyboard" ;
1415import { KM_CORE_EVENT_FLAG } from '../core-adapter/KM_Core.js' ;
16+ import { ModifierKeyConstants } from '@keymanapp/common-types' ;
1517
1618export class CoreKeyboardInterface implements KeyboardMinimalInterface {
1719 public activeKeyboard : Keyboard ;
@@ -150,6 +152,12 @@ export class CoreKeyboardProcessor extends EventEmitter<EventMap> implements Key
150152 this . _layerStore . set ( value ) ;
151153 }
152154
155+ private getLayerId ( modifier : number ) : string {
156+ // TODO-web-core: implement
157+ // return Layouts.getLayerId(modifier);
158+ return 'default' ; // TODO-web-core: put into LayerNames enum
159+ }
160+
153161 /**
154162 * Retrieve context including deadkeys from TextStore and apply to Core's context
155163 *
@@ -302,6 +310,7 @@ export class CoreKeyboardProcessor extends EventEmitter<EventMap> implements Key
302310 return null ;
303311 }
304312
313+ // TODO-web-core: this could be shared with JsKeyboardProcessor
305314 /**
306315 * Determines if the given key event is a modifier key press.
307316 * Returns true if the event corresponds to a modifier key, otherwise false.
@@ -313,10 +322,113 @@ export class CoreKeyboardProcessor extends EventEmitter<EventMap> implements Key
313322 * @returns {boolean } True if the event is a modifier key press, false otherwise.
314323 */
315324 public doModifierPress ( keyEvent : KeyEvent , textStore : TextStore , isKeyDown : boolean ) : boolean {
316- // TODO-web-core: Implement this method (#15287)
325+ if ( ! this . activeKeyboard ) {
326+ return false ;
327+ }
328+
329+ if ( keyEvent . isModifier ) {
330+ this . activeKeyboard . notify ( keyEvent . Lcode , textStore , isKeyDown ) ;
331+ // For eventual integration - we bypass an OSK update for physical keystrokes when in touch mode.
332+ if ( ! keyEvent . device . touchable ) {
333+ return this . _UpdateVKShift ( keyEvent ) ; // I2187
334+ } else {
335+ return true ;
336+ }
337+ }
338+
339+ if ( keyEvent . LmodifierChange ) {
340+ this . activeKeyboard . notify ( 0 , textStore , true ) ;
341+ if ( ! keyEvent . device . touchable ) {
342+ this . _UpdateVKShift ( keyEvent ) ;
343+ }
344+ }
345+
346+ // No modifier keypresses detected.
317347 return false ;
318348 }
319349
350+
351+ // TODO-web-core: this could be shared with JsKeyboardProcessor
352+ /**
353+ * Updates the virtual keyboard shift state based on the provided key event.
354+ * Handles modifier key simulation, state key updates, and layer selection for the OSK.
355+ *
356+ * @param {KeyEvent } e - The key event used to update the shift state.
357+ *
358+ * @returns {boolean } True if the update was processed, otherwise true if no active keyboard.
359+ */
360+ private _UpdateVKShift ( e : KeyEvent ) : boolean {
361+ let keyShiftState = 0 ;
362+
363+ const lockNames = [ 'CAPS' , 'NUM_LOCK' , 'SCROLL_LOCK' ] as const ;
364+ const lockKeys = [ 'K_CAPS' , 'K_NUMLOCK' , 'K_SCROLL' ] as const ;
365+ const lockModifiers = [ ModifierKeyConstants . CAPITALFLAG , ModifierKeyConstants . NUMLOCKFLAG , ModifierKeyConstants . SCROLLFLAG ] as const ;
366+
367+ if ( ! this . activeKeyboard ) {
368+ return true ;
369+ }
370+
371+ if ( e ) {
372+ // read shift states from event
373+ keyShiftState = e . Lmodifiers ;
374+
375+ // Are we simulating AltGr? If it's a simulation and not real, time to un-simulate for the OSK.
376+ if ( this . activeKeyboard . isChiral && this . activeKeyboard . emulatesAltGr &&
377+ ( this . modStateFlags & Codes . modifierBitmasks [ 'ALT_GR_SIM' ] ) == Codes . modifierBitmasks [ 'ALT_GR_SIM' ] ) {
378+ keyShiftState |= Codes . modifierBitmasks [ 'ALT_GR_SIM' ] ;
379+ keyShiftState &= ~ ModifierKeyConstants . RALTFLAG ;
380+ }
381+
382+ // Set stateKeys where corresponding value is passed in e.Lstates
383+ let stateMutation = false ;
384+ for ( let i = 0 ; i < lockNames . length ; i ++ ) {
385+ if ( ( e . Lstates & Codes . stateBitmasks [ lockNames [ i ] ] ) != 0 ) {
386+ this . stateKeys [ lockKeys [ i ] ] = ( ( e . Lstates & lockModifiers [ i ] ) != 0 ) ;
387+ stateMutation = true ;
388+ }
389+ }
390+
391+ if ( stateMutation ) {
392+ this . emit ( 'statekeychange' , this . stateKeys ) ;
393+ }
394+ }
395+
396+ this . updateStates ( ) ;
397+
398+ if ( this . activeKeyboard . isMnemonic && this . stateKeys [ 'K_CAPS' ] && ( ! e || ! e . isModifier ) ) {
399+ // Modifier keypresses don't trigger mnemonic manipulation of modifier state.
400+ // Only an output key does; active use of Caps will also flip the SHIFT flag.
401+ // Mnemonic keystrokes manipulate the SHIFT property based on CAPS state.
402+ // We need to unflip them when tracking the OSK layer.
403+ keyShiftState ^= ModifierKeyConstants . K_SHIFTFLAG ;
404+ }
405+
406+ this . layerId = this . getLayerId ( keyShiftState ) ;
407+ return true ;
408+ }
409+
410+ // TODO-web-core: this could be shared with JsKeyboardProcessor
411+ private updateStates ( ) : void {
412+ const lockKeys = [ 'K_CAPS' , 'K_NUMLOCK' , 'K_SCROLL' ] as const ;
413+ const lockModifiers = [ ModifierKeyConstants . CAPITALFLAG , ModifierKeyConstants . NUMLOCKFLAG , ModifierKeyConstants . SCROLLFLAG ] as const ;
414+ const noLockModifers = [ ModifierKeyConstants . NOTCAPITALFLAG , ModifierKeyConstants . NOTNUMLOCKFLAG , ModifierKeyConstants . NOTSCROLLFLAG ] as const ;
415+
416+ for ( let i = 0 ; i < lockKeys . length ; i ++ ) {
417+ const key = lockKeys [ i ] ;
418+ const flag = this . stateKeys [ key ] ;
419+
420+ // Ensures that the current mod-state info properly matches the currently-simulated
421+ // state key states.
422+ if ( flag ) {
423+ this . modStateFlags |= lockModifiers [ i ] ;
424+ this . modStateFlags &= ~ noLockModifers [ i ] ;
425+ } else {
426+ this . modStateFlags &= ~ lockModifiers [ i ] ;
427+ this . modStateFlags |= noLockModifers [ i ] ;
428+ }
429+ }
430+ }
431+
320432 /**
321433 * Resets the keyboard context, optionally using the provided text store.
322434 * Clears or reinitializes the context for subsequent keyboard processing.
0 commit comments