diff --git a/common/core/web/input-processor/src/text/inputProcessor.ts b/common/core/web/input-processor/src/text/inputProcessor.ts index 314d1e8c5ef..e2607cfd7dc 100644 --- a/common/core/web/input-processor/src/text/inputProcessor.ts +++ b/common/core/web/input-processor/src/text/inputProcessor.ts @@ -78,9 +78,13 @@ namespace com.keyman.text { } // Will handle keystroke-based non-layer change modifier & state keys, mapping them through the physical keyboard's version - // of state management. - if(!fromOSK && this.keyboardProcessor.doModifierPress(keyEvent, !fromOSK)) { - return new RuleBehavior(); + // of state management. `doModifierPress` must always run. + if(this.keyboardProcessor.doModifierPress(keyEvent, !fromOSK)) { + // If run on a desktop platform, we know that modifier & state key presses may not + // produce output, so we may make an immediate return safely. + if(!fromOSK) { + return new RuleBehavior(); + } } // If suggestions exist AND space is pressed, accept the suggestion and do not process the keystroke. diff --git a/common/core/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/core/web/keyboard-processor/src/keyboards/activeLayout.ts index ae8d65cafb1..3f5e804139a 100644 --- a/common/core/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/core/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -217,6 +217,20 @@ namespace com.keyman.keyboards { // This part depends on the keyboard processor's active state. if(keyboardProcessor) { keyboardProcessor.setSyntheticEventDefaults(Lkc); + + // If it's a state key modifier, trigger its effects as part of the + // keystroke. + const bitmap = { + 'K_CAPS': text.Codes.stateBitmasks.CAPS, + 'K_NUMLOCK': text.Codes.stateBitmasks.NUM_LOCK, + 'K_SCROLL': text.Codes.stateBitmasks.SCROLL_LOCK + }; + const bitmask = bitmap[Lkc.kName]; + + if(bitmask) { + Lkc.Lstates ^= bitmask; + Lkc.LmodifierChange = true; + } } return Lkc; @@ -670,4 +684,4 @@ namespace com.keyman.keyboards { return aLayout; } } -} \ No newline at end of file +} diff --git a/common/core/web/keyboard-processor/src/text/keyboardProcessor.ts b/common/core/web/keyboard-processor/src/text/keyboardProcessor.ts index 6ee5d2b9bfc..76e66193366 100644 --- a/common/core/web/keyboard-processor/src/text/keyboardProcessor.ts +++ b/common/core/web/keyboard-processor/src/text/keyboardProcessor.ts @@ -406,7 +406,7 @@ namespace com.keyman.text { for(i=0; i < lockNames.length; i++) { if(lockStates & Codes.stateBitmasks[lockNames[i]]) { - this.stateKeys[lockKeys[i]] = lockStates & Codes.modifierCodes[lockNames[i]]; + this.stateKeys[lockKeys[i]] = !!(lockStates & Codes.modifierCodes[lockNames[i]]); } } } else if(d) { @@ -427,10 +427,44 @@ namespace com.keyman.text { } } + this.updateStates(); + + if(this.activeKeyboard.isMnemonic && this.stateKeys['K_CAPS']) { + // Modifier keypresses doesn't trigger mnemonic manipulation of modifier state. + // Only an output key does; active use of Caps will also flip the SHIFT flag. + if(!e || !KeyboardProcessor.isModifier(e)) { + // Mnemonic keystrokes manipulate the SHIFT property based on CAPS state. + // We need to unflip them when tracking the OSK layer. + keyShiftState ^= Codes.modifierCodes['SHIFT']; + } + } + this.layerId = this.getLayerId(keyShiftState); return true; } + private updateStates(): void { + var lockNames = ['CAPS', 'NUM_LOCK', 'SCROLL_LOCK']; + var lockKeys = ['K_CAPS', 'K_NUMLOCK', 'K_SCROLL']; + + for(let i=0; i < lockKeys.length; i++) { + const key = lockKeys[i]; + const flag = this.stateKeys[key]; + const onBit = lockNames[i]; + const offBit = 'NO_' + lockNames[i]; + + // Ensures that the current mod-state info properly matches the currently-simulated + // state key states. + if(flag) { + this.modStateFlags |= Codes.modifierCodes[onBit]; + this.modStateFlags &= ~Codes.modifierCodes[offBit]; + } else { + this.modStateFlags &= ~Codes.modifierCodes[onBit]; + this.modStateFlags |= Codes.modifierCodes[offBit]; + } + } + } + getLayerId(modifier: number): string { return keyboards.Layouts.getLayerId(modifier); } diff --git a/web/source/osk/preProcessor.ts b/web/source/osk/preProcessor.ts index e8174a05cb9..edcbc0ac3a6 100644 --- a/web/source/osk/preProcessor.ts +++ b/web/source/osk/preProcessor.ts @@ -9,14 +9,6 @@ namespace com.keyman.osk { // First check the virtual key, and process shift, control, alt or function keys let Lkc = e.constructKeyEvent(core.keyboardProcessor, dom.Utils.getOutputTarget(Lelem), keyman.util.device.coreSpec); - // If it's actually a state key modifier, trigger its effects immediately, as KeyboardEvents would do the same. - switch(Lkc.kName) { - case 'K_CAPS': - case 'K_NUMLOCK': - case 'K_SCROLL': - core.keyboardProcessor.stateKeys[Lkc.kName] = ! core.keyboardProcessor.stateKeys[Lkc.kName]; - } - // End - mirrors _GetKeyEventProperties return Lkc; } diff --git a/web/source/osk/visualKeyboard.ts b/web/source/osk/visualKeyboard.ts index 03d4e525dd6..26fffd92e61 100644 --- a/web/source/osk/visualKeyboard.ts +++ b/web/source/osk/visualKeyboard.ts @@ -602,7 +602,7 @@ namespace com.keyman.osk { layers: keyboards.LayoutLayer[]; private layerId: string = "default"; readonly isRTL: boolean; - layerIndex: number; + layerIndex: number = 0; // the index of the default layer device: Device; isStatic: boolean = false; @@ -1022,6 +1022,12 @@ namespace com.keyman.osk { layerChangeHandler: text.SystemStoreMutationHandler = function(this: VisualKeyboard, source: text.MutableSystemStore, newValue: string) { + // This handler is also triggered on state-key state changes (K_CAPS) that + // may not actually change the layer. + if(this) { + this._UpdateVKShiftStyle(); + } + if(source.value != newValue) { this.layerId = newValue; let keyman = com.keyman.singleton;