/// /// //Since TS won't recognize the types b/c no "import"/"require" statements. // A small-scale manual definition. declare class EventEmitter { /** Add a listener for a given event */ on(event: string, func: (...args: any[]) => boolean, context?: any); /** Add a one-time listener for a given event */ once(event: string, func: (...args: any[]) => boolean, context?: any); removeListener(event: string, func: (...args: any[]) => boolean, context?: any, once?: boolean); // Defines their alternately-themed aliases. addListener: typeof EventEmitter.prototype.on; off: typeof EventEmitter.prototype.removeListener; // Defines the actual event-raising function. emit(eventName: string, ...args: any[]); } // Makes sure the code below knows that the namespaces exist. namespace com.keyman { export namespace dom { export declare var DOMEventHandlers: any; export declare class Utils { static getOutputTarget(elem: HTMLElement): any; // text.OutputTarget; } } export namespace osk { export declare var PreProcessor: any; export declare var VisualKeyboard: any; } } namespace KMWRecorder { /** * Contains browser-dependent code used to transcribe browser-based events * so that thay may be reconstructed for use in KMW testing. */ export class Scribe extends EventEmitter { //#region Static methods for recording input events static recordKeystroke(e: com.keyman.text.KeyEvent, eventSpec: InputEventSpec) { var recording: RecordedKeystroke; if(e.isSynthetic) { recording = new RecordedSyntheticKeystroke(e); } else { recording = new RecordedPhysicalKeystroke(e, eventSpec as PhysicalInputEventSpec); } return recording; } static recordKeyboardEvent(e: KeyboardEvent): PhysicalInputEventSpec { let recording = new PhysicalInputEventSpec(); recording.key = e.key; recording.code = e.code; recording.keyCode = e.keyCode; recording.location = e.location; var flagSet: number = 0; for(var key in PhysicalInputEventSpec.modifierCodes) { if(e.getModifierState(key)) { flagSet |= PhysicalInputEventSpec.modifierCodes[key]; } } recording.modifierSet = flagSet; return recording; } static recordOSKEvent(e: HTMLDivElement): OSKInputEventSpec { let recording = new OSKInputEventSpec(); recording.keyID = e.id; return recording; } static recordKeyboardStub(activeStub: any, basePath: string): KeyboardStub { let recording = new KeyboardStub(); recording.id = activeStub.KI; recording.id = recording.id.replace('Keyboard_', ''); recording.name = activeStub.KN; recording.filename = Scribe._setStubBasePath(activeStub.KF, basePath); recording.languages = [new LanguageStubForKeyboard(activeStub)]; return recording; } private static _setStubBasePath(filename: string, filePath: string, force?: boolean): string { var linkParser = document.createElement<"a">("a"); linkParser.href = filePath; if(force === undefined) { force = true; } if(force || (filename.indexOf(linkParser.protocol) < 0 && filename.indexOf('/') != 0)) { var file = filename.substr(filename.lastIndexOf('/')+1); return filePath + '/' + file; } else { return file; } } //#endregion _currentKeyEvent: com.keyman.text.KeyEvent; _currentSequence: RecordedKeystrokeSequence = new RecordedKeystrokeSequence(); _testDefinition: KeyboardTest = new KeyboardTest(); keyboardJustActivated: boolean = false; get currentSequence(): RecordedKeystrokeSequence { return this._currentSequence; } set currentSequence(value: RecordedKeystrokeSequence) { this._currentSequence = value; this.raiseRecordChanged(); } get testDefinition(): KeyboardTest { return this._testDefinition; } set testDefinition(value: KeyboardTest) { if(!value) { // It should never be null or undefined. throw new Error("Scribe's test definition must never be null or undefined!"); } this._testDefinition = value; this.raiseTestChanged(); } addInputRecord(json: RecordedKeystroke, currentOutput: string) { this.currentSequence.addInput(json, currentOutput); this.raiseRecordChanged(); } resetInputRecord() { window['keyman'].resetContext(); this.currentSequence = new KMWRecorder.RecordedKeystrokeSequence(); this.emit('record-reset', null); } errorUpdate(msg: string) { if(msg) { this.currentSequence.msg = msg; } else { delete this.currentSequence.msg; } this.raiseRecordChanged(); } private raiseRecordChanged() { this.emit('record-changed', this.currentSequence.toPrettyJSON()); } saveInputRecord(config: Constraint) { if(this.currentSequence.inputs.length > 0) { this.testDefinition.addTest(config, this.currentSequence); } this.resetInputRecord(); this.raiseTestChanged(); } private raiseTestChanged() { this.emit('test-changed', this.testDefinition ? this.testDefinition.toPrettyJSON() : ''); } // Time for the 'magic'. Yay, JavaScript method extension strategies... initHooks(recordingElement: HTMLElement) { let keyman = window['keyman']; let recorderScribe = this; let _originalKeyDown = keyman.touchAliasing._KeyDown.bind(keyman.touchAliasing); keyman.touchAliasing._KeyDown = function(e) { let in_output = com.keyman.dom.Utils.getOutputTarget(recordingElement); if(!in_output || com.keyman.dom.DOMEventHandlers.states.activeElement != in_output.getElement()) { return _originalKeyDown(e); } let event = KMWRecorder.Scribe.recordKeyboardEvent(e); var retVal = _originalKeyDown(e); let recording = Scribe.recordKeystroke(recorderScribe._currentKeyEvent, event); // Record the keystroke as part of a test sequence! // Miniature delay in case the keyboard relies upon default backspace/delete behavior! window.setTimeout(function() { recorderScribe.addInputRecord(recording, in_output.getText()); }, 1); return retVal; } var _originalModelKeyClick = com.keyman.osk.VisualKeyboard.prototype.modelKeyClick; //.bind(keyman.osk); com.keyman.osk.VisualKeyboard.prototype.modelKeyClick = function(e, touch) { let in_output = com.keyman.dom.Utils.getOutputTarget(recordingElement); if(!in_output || com.keyman.dom.DOMEventHandlers.states.activeElement != in_output.getElement()) { return _originalModelKeyClick.call(this, e, touch); } let event = KMWRecorder.Scribe.recordOSKEvent(e); var retVal = _originalModelKeyClick.call(this, e, touch); let recording = Scribe.recordKeystroke(recorderScribe._currentKeyEvent, event); // Record the click/touch as part of a test sequence! recorderScribe.addInputRecord(recording, in_output.getText()); return retVal; } var _originalSetActiveKeyboard = keyman.keyboardManager._SetActiveKeyboard.bind(keyman.keyboardManager); keyman.keyboardManager._SetActiveKeyboard = function(PInternalName, PLgCode, saveCookie) { let in_output = com.keyman.dom.Utils.getOutputTarget(recordingElement); // If it's not on our recording control, ignore the change and do nothing special. if(!in_output || document.activeElement != in_output.getElement()) { return _originalSetActiveKeyboard(PInternalName, PLgCode, saveCookie); } let testDefinition = recorderScribe.testDefinition; var sameKbd = (testDefinition.keyboard && ("Keyboard_" + testDefinition.keyboard.id) == PInternalName) && (!testDefinition.keyboard.getFirstLanguage() || (testDefinition.keyboard.getFirstLanguage() == PLgCode)); if(!testDefinition.isEmpty() && !sameKbd && !recorderScribe.keyboardJustActivated) { if(!confirm("Changing the keyboard will clear the current test set. Are you sure?")) { return _originalSetActiveKeyboard("Keyboard_" + testDefinition.keyboard.id, testDefinition.keyboard.languages[0].id); } } let promise = _originalSetActiveKeyboard(PInternalName, PLgCode, saveCookie); // What's the active stub immediately after our _SetActiveKeyboard call? var internalStub = keyman.keyboardManager.activeStub; if(internalStub && (com.keyman.dom.DOMEventHandlers.states._activeElement == in_output.getElement())) { var kbdRecord = KMWRecorder.Scribe.recordKeyboardStub(internalStub, 'resources/keyboards'); recorderScribe.emit('stub-changed', JSON.stringify(kbdRecord)); // var ta_activeStub = document.getElementById('activeStub'); // ta_activeStub.value = JSON.stringify(kbdRecord); if(!sameKbd && !recorderScribe.keyboardJustActivated) { recorderScribe.testDefinition = new KMWRecorder.KeyboardTest(kbdRecord); } } recorderScribe.keyboardJustActivated = false; return promise; } var _originalProcessKeyEvent = keyman.core.processKeyEvent.bind(keyman.core); keyman.core.processKeyEvent = function(keyEvent /* com.keyman.text.KeyEvent */, target /* com.keyman.text.OutputTarget */) { recorderScribe._currentKeyEvent = keyEvent; return _originalProcessKeyEvent(keyEvent, target); } } } }