diff --git a/HISTORY.md b/HISTORY.md index b831dadee8a..899cc2a5dfa 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,65 @@ # Keyman Version History +## 18.0.162 alpha 2024-12-19 + +* fix(mac): standardize interaction between OSK and physical keyboard (#12836) + +## 18.0.161 alpha 2024-12-17 + +* fix(mac): OSK layers displayed consistently for hardware and OSK modifiers (#12829) + +## 18.0.160 alpha 2024-12-16 + +* feat(ios): configurable keyboard height (#12571) +* chore(common/web): remove .c8rc.json from types and merge exclusions into package.json (#12813) +* fix(android/engine): Initialize index when resuming KeyboardPicker (#12832) + +## 18.0.159 alpha 2024-12-13 + +* chore(core): remove meson warnings for wasm builds (#12827) +* chore(linux): Update debian changelog (#12023) + +## 18.0.158 alpha 2024-12-11 + +* fix(linux): pushing of updated changelog branch (#12818) +* fix(linux): work around Lintian errors (#12815) + +## 18.0.157 alpha 2024-12-10 + +* test(common/web): unit tests for kvk-file-writer (#12734) + +## 18.0.156 alpha 2024-12-09 + +* fix(developer): remove platforms from kmc-generate LM readme (#12803) +* test(developer): add test for ERROR_DescriptionIsMissing to kmc-keyboard-info (#12804) +* chore(developer): verify bundled node version when building installer (#12806) +* fix(developer): support hint property in displaymap (#12807) +* fix(common/web): delete replaceExtension in types/src/util/file-types.ts (#12762) +* chore(developer): add some docs for language examples in kmp.json (#12805) +* fix(core): implement ldml_processor::get_key_list() (#12644) + +## 18.0.155 alpha 2024-12-07 + +* chore(android,windows): Update crowdin for Czech (#12792) + +## 18.0.154 alpha 2024-12-06 + +* feat(developer,core): local imports (#12750) +* chore(core): remove `km_core_keyboard_load` API (#12769) +* chore: rename TestCompilerCallbacks.ts (#12775) +* chore(mac): update to SIL logo with glyph in About window (#12766) +* chore(android,windows): Update crowdin for Italian (#12793) +* change(developer): use full github url in kmc copy parameters (#12773) +* chore(developer): add baseline tests for bcp47 codes to kmc-package (#12506) +* chore(core): only install node on Windows if not available (#12772) +* chore(android): Disable auto-correct UI controls (#12791) + +## 18.0.153 alpha 2024-12-05 + +* feat(developer,common): verify normalization of strings (#12748) +* chore(core): Add link to Keyman Glossary (#12774) +* test(common/web/types): unit tests for file-types (#12716) + ## 18.0.152 alpha 2024-12-04 * refactor(mac): pass kmx data blob to keyman core instead of file path (#12760) diff --git a/VERSION.md b/VERSION.md index 6269a6496fb..99025bb930e 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.153 \ No newline at end of file +18.0.163 \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java index 977489f40a9..96abc83f8a9 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java @@ -108,11 +108,11 @@ public void onCreate(Bundle savedInstanceState) { RadioGroup radioGroup = (RadioGroup) findViewById(R.id.suggestion_radio_group); radioGroup.clearCheck(); + // Auto-correct disabled for Keyman 18.0 #12767 int[] RadioButtonArray = { R.id.suggestion_radio_0, R.id.suggestion_radio_1, - R.id.suggestion_radio_2, - R.id.suggestion_radio_3}; + R.id.suggestion_radio_2}; RadioButton radioButton = (RadioButton)radioGroup.findViewById(RadioButtonArray[maySuggest]); radioButton.setChecked(true); diff --git a/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml index 97da3115df7..ad6eab6344c 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-cs-rCZ/strings.xml @@ -91,11 +91,11 @@ Nezobrazovat titulek v mezerníku Při psaní vibrovat - + Vždy zobrazovat banner - + K provedení - + Pokud je vypnuto, zobrazí se pouze pokud je povolen prediktivní text Povolit odesílání hlášení o pádech přes síť diff --git a/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml index a475ae37255..de474402c06 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml @@ -68,7 +68,7 @@ Regola altezza tastiera - Adjust longpress delay + Regola ritardo pressione prolungata Didascalia barra spaziatrice @@ -89,11 +89,11 @@ Non mostrare la didascalia sulla barra spaziatrice Vibra durante la digitazione - + Mostra sempre il banner - + Da attuare - + Quando è spento, mostrato solo quando il testo predittivo è abilitato Consenti l\'invio di segnalazioni di crash attraverso la rete @@ -126,13 +126,13 @@ Ripristina impostazioni predefinite - Delay Time: %1$.1f seconds + Tempo ritardo: %1$.1f secondi - Delay time longer + Tempo ritardo maggiore - Delay time shorter + Tempo ritardo minore - Longpress delay time slider + Cursore tempo ritardo pressione prolungata Cerca o digita URL diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardPickerActivity.java index 48295f7297b..95902bd26e1 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardPickerActivity.java @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -252,8 +253,17 @@ protected void onResume() { adapter.notifyDataSetChanged(); } - int curKbPos = KeyboardController.getInstance().getKeyboardIndex(KMKeyboard.currentKeyboard()); - setSelection(curKbPos); + + // Determine the index to the current keyboard position to highlight as the selected keyboard + int currentKeyboardIndex; + String currentKeyboardKey = KMKeyboard.currentKeyboard(); + if (currentKeyboardKey == null) { + SharedPreferences prefs = this.getSharedPreferences(this.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + currentKeyboardIndex = prefs.getInt(KMManager.KMKey_UserKeyboardIndex, 0); + } else { + currentKeyboardIndex = KeyboardController.getInstance().getKeyboardIndex(currentKeyboardKey); + } + setSelection(currentKeyboardIndex); imeList = getIMEList(this); BaseAdapter imeAdapter = (BaseAdapter)imeListAdapter; diff --git a/android/KMEA/app/src/main/res/layout/language_settings_list_layout.xml b/android/KMEA/app/src/main/res/layout/language_settings_list_layout.xml index 6399987b041..42ae9b15116 100644 --- a/android/KMEA/app/src/main/res/layout/language_settings_list_layout.xml +++ b/android/KMEA/app/src/main/res/layout/language_settings_list_layout.xml @@ -90,12 +90,13 @@ android:layout_gravity="center_vertical" android:text="@string/suggestions_radio_2" /> - + diff --git a/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml b/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml index db45a6706e0..5fda42853c9 100644 --- a/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml +++ b/android/KMEA/app/src/main/res/values-cs-rCZ/strings.xml @@ -137,10 +137,15 @@ Klávesnice %1$s nainstalována Klávesnice smazána - + Povolit opravy - + Povolit predikce + + Vypnout návrhy + Pouze predikce + Predikce s opravami + Predikce s automatickými opravami Slovník diff --git a/android/KMEA/app/src/main/res/values-it-rIT/strings.xml b/android/KMEA/app/src/main/res/values-it-rIT/strings.xml index da1e3531a2b..7dce8252b27 100644 --- a/android/KMEA/app/src/main/res/values-it-rIT/strings.xml +++ b/android/KMEA/app/src/main/res/values-it-rIT/strings.xml @@ -133,10 +133,15 @@ %1$s tastiera installata Tastiera eliminata - + Abilita correzioni - + Abilita previsioni + + Disabilita suggerimenti + Solo previsioni + Previsioni con correzioni + Previsioni con correzioni automatiche Dizionario diff --git a/android/docs/help/android_images/disable-suggestions.png b/android/docs/help/android_images/disable-suggestions.png new file mode 100644 index 00000000000..a4a21b98cb5 Binary files /dev/null and b/android/docs/help/android_images/disable-suggestions.png differ diff --git a/android/docs/help/basic/using-the-banner.md b/android/docs/help/basic/using-the-banner.md index 75862f6df44..ff637645cf6 100644 --- a/android/docs/help/basic/using-the-banner.md +++ b/android/docs/help/basic/using-the-banner.md @@ -10,6 +10,18 @@ Keyman keyboards now always display a banner above the keyboard for one of the f * Display a Keyman-themed banner so popups and gestures for the top row of keys are visible * Reserved for future functionality +## Controlling the Keyboard Banner Mode + +The banner mode can be controlled by going to Keyman Settings --> (select an installed language) + +At the bottom of the language settings menu are three controls for the banner: + +![](../android_images/disable-suggestions.png) + +* Disable suggestions (Display image banner instead) +* Predictions only (Suggestion banner displays predictions) +* Predictions with corrections (Suggestion banner displays predictions and corrections) + ## Using the Suggestion Banner If a [dictionary is installed](installing-dictionaries) and enabled for the active Keyman keyboard, the banner will display suggestions that can be selected. diff --git a/common/web/types/.c8rc.json b/common/web/types/.c8rc.json deleted file mode 100644 index f7137ed999b..00000000000 --- a/common/web/types/.c8rc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "exclude": [ - "src/keyboard-object.ts", - "src/lexical-model-types.ts", - "src/outputTarget.interface.ts", - "src/*.d.ts", - "src/main.ts", - "src/schemas/*", - "src/schema-validators.ts", - "src/schemas.ts" - ] -} diff --git a/common/web/types/build.sh b/common/web/types/build.sh index 418840d1a85..597962aeee8 100755 --- a/common/web/types/build.sh +++ b/common/web/types/build.sh @@ -92,7 +92,7 @@ function do_test() { readonly C8_THRESHOLD=60 - # Excludes are defined in .c8rc.json + # Excludes are defined in package.json c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha ${MOCHA_FLAGS} "${builder_extra_params[@]}" builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal." builder_echo warning "Please increase threshold in build.sh as test coverage improves." diff --git a/common/web/types/package.json b/common/web/types/package.json index d6efc635039..429dfbcddbb 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -76,7 +76,13 @@ "src/keyman-touch-layout/keyman-touch-layout-file-writer.ts", "src/osk/osk.ts", "src/schemas/*", - "tests/" + "tests/", + "src/keyboard-object.ts", + "src/outputTarget.interface.ts", + "src/*.d.ts", + "src/main.ts", + "src/schema-validators.ts", + "src/schemas.ts" ] }, "sideEffects": false diff --git a/common/web/types/src/util/file-types.ts b/common/web/types/src/util/file-types.ts index eabcb21b523..d00f9daff12 100644 --- a/common/web/types/src/util/file-types.ts +++ b/common/web/types/src/util/file-types.ts @@ -166,19 +166,3 @@ export function filenameIs(filename: string, fileType: Source | Binary) { } return filename.toLowerCase().endsWith(fileType); } - -/** - * Replaces a filename extension with the new extension. Returns `null` if the - * filename does not end with oldExtension. - * @param filename - * @param oldExtension - * @param newExtension - * @returns - */ -export function replaceExtension(filename: string, oldExtension: string, newExtension: string): string { - const ext = filename.substring(filename.length - oldExtension.length); - if(ext !== oldExtension) { - return null; - } - return filename.substring(0, filename.length - oldExtension.length) + newExtension; -} \ No newline at end of file diff --git a/common/web/types/tests/kvk/kvk-file-writer.tests.ts b/common/web/types/tests/kvk/kvk-file-writer.tests.ts new file mode 100644 index 00000000000..b579446cf62 --- /dev/null +++ b/common/web/types/tests/kvk/kvk-file-writer.tests.ts @@ -0,0 +1,325 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Dr Mark C. Sinclair on 2024-12-05 + * + * Test code for kvk-file-writer.ts + */ + +import 'mocha'; +import { assert } from 'chai'; +import { KvkFileWriter } from '../../src/main.js'; +import { VisualKeyboard, + VisualKeyboardKey, + VisualKeyboardFont, + DEFAULT_KVK_FONT +} from '../../src/kvk/visual-keyboard.js'; +import { BUILDER_KVK_FILE, + BUILDER_KVK_HEADER_FLAGS, + BUILDER_KVK_STRING, + BUILDER_KVK_HEADER_IDENTIFIER, + BUILDER_KVK_HEADER_VERSION, + BUILDER_KVK_KEY, + BUILDER_KVK_KEY_FLAGS, + BUILDER_KVK_SHIFT_STATE, +} from '../../src/kvk/kvk-file.js'; + +const VISUAL_KEYBOARD_TEXT_COLOR = 0xFF000008; + +describe('Test of KVK-File-Writer', () => { + describe('Test of write()', () => { + it('can create a visual keyboard', () => { + const writer = new KvkFileWriter; + const vk = initVisualKeyboard([ + initVisualKeyboardKey(0), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ]); + const file = writer.write(vk); + assert.isNotNull(file); + }); + }); + describe('Test of build()', () => { + it('can build a BUILDER_KVK_FILE', () => { + const vk = initVisualKeyboard([ + initVisualKeyboardKey(0), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ]); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it('does not take account of the VisualKeyboard version', () => { + const vk_version = (BUILDER_KVK_HEADER_VERSION + 0x0100); + const vk = initVisualKeyboard([ + initVisualKeyboardKey(0), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ], + vk_version, + BUILDER_KVK_HEADER_FLAGS.kvkhNone, + "associatedKeyboard", + DEFAULT_KVK_FONT, + DEFAULT_KVK_FONT, + undefined, + ); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it.skip('can handle a null associatedKeyboard', () => { + const vk_associatedKeyboard: string = null; + const vk = initVisualKeyboard([ + initVisualKeyboardKey(0), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ], + undefined, + BUILDER_KVK_HEADER_FLAGS.kvkhNone, + vk_associatedKeyboard, + DEFAULT_KVK_FONT, + DEFAULT_KVK_FONT, + undefined, + ); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it.skip('can handle a null ansiFont name', () => { + const vk_ansiFont_name: string = null; + const vk = initVisualKeyboard([ + initVisualKeyboardKey(0), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ], + undefined, + BUILDER_KVK_HEADER_FLAGS.kvkhNone, + "associatedKeyboard", + { name: vk_ansiFont_name, size: -12 }, + DEFAULT_KVK_FONT, + undefined, + ); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it.skip('can handle a null unicodeFont name', () => { + const vk_unicodeFont_name: string = null; + const vk = initVisualKeyboard([ + initVisualKeyboardKey(0), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ], + undefined, + BUILDER_KVK_HEADER_FLAGS.kvkhNone, + "associatedKeyboard", + DEFAULT_KVK_FONT, + { name: vk_unicodeFont_name, size: -12 }, + undefined, + ); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it('can handle a null vkey', () => { + const vkk_vkey: number = null; + const vk = initVisualKeyboard([ + initVisualKeyboardKey( + vkk_vkey, + BUILDER_KVK_KEY_FLAGS.kvkkBitmap, + BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL, + "text", + null, + ), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ]); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it('can handle a null key flags', () => { + const vkk_flags: BUILDER_KVK_KEY_FLAGS = null; + const vk = initVisualKeyboard([ + initVisualKeyboardKey( + 0, + vkk_flags, + BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL, + "text", + null, + ), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ]); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it('can handle a null key shift', () => { + const vkk_shift: BUILDER_KVK_SHIFT_STATE = null; + const vk = initVisualKeyboard([ + initVisualKeyboardKey( + 0, + BUILDER_KVK_KEY_FLAGS.kvkkBitmap, + vkk_shift, + "text", + null, + ), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ]); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it('can handle a null key text', () => { + const vkk_text: string = null; + const vk = initVisualKeyboard([ + initVisualKeyboardKey( + 0, + BUILDER_KVK_KEY_FLAGS.kvkkBitmap, + BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL, + vkk_text, + null, + ), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ]); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + it('can handle a non-null key bitmap', () => { + const vkk_bitmap: number[] = [0]; + const vk = initVisualKeyboard([ + initVisualKeyboardKey( + 0, + BUILDER_KVK_KEY_FLAGS.kvkkBitmap, + BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL, + "text", + vkk_bitmap, + ), + initVisualKeyboardKey(1), + initVisualKeyboardKey(2), + ]); + const writer = new KvkFileWriter; + const binary: BUILDER_KVK_FILE = writer['build'](vk); + checkBuilderKvkFile(binary, vk); + }); + }); + describe('Test of setString()', () => { + it('can set a BUILDER_KVK_STRING', () => { + const bks: BUILDER_KVK_STRING = { len: 0, str: null }; + const writer = new KvkFileWriter; + const str = "hello"; + writer['setString'](bks, str); + assert.equal(bks.len, str.length + 1); + assert.deepEqual(bks.str, str); + }); + it('can handle an empty string', () => { + const bks: BUILDER_KVK_STRING = { len: 0, str: null }; + const writer = new KvkFileWriter; + const str = ""; + writer['setString'](bks, str); + assert.equal(bks.len, str.length + 1); + assert.deepEqual(bks.str, str); + }); + it('throws TypeError for a null BUILDER_KVK_STRING', () => { + assert.throws(() => { + const writer = new KvkFileWriter; + writer['setString'](null, ""); + }, TypeError); + }); + it('throws TypeError for a null value', () => { + assert.throws(() => { + const bks: BUILDER_KVK_STRING = { len: 0, str: null }; + const writer = new KvkFileWriter; + writer['setString'](bks, null); + }, TypeError); + }); + }); +}); + +function initVisualKeyboard( + vkks: VisualKeyboardKey[], + version: number = undefined, + flags: BUILDER_KVK_HEADER_FLAGS = BUILDER_KVK_HEADER_FLAGS.kvkhNone, + associatedKeyboard: string = "associatedKeyboard", + ansiFont: VisualKeyboardFont = DEFAULT_KVK_FONT, + unicodeFont: VisualKeyboardFont = DEFAULT_KVK_FONT, + underlyingLayout: string = undefined, +): VisualKeyboard { + const vkh = { + version: version, + flags: flags, + associatedKeyboard: associatedKeyboard, + ansiFont: ansiFont, + unicodeFont: unicodeFont, + underlyingLayout: underlyingLayout, + }; + return { header: vkh, keys: vkks }; +}; + +function initVisualKeyboardKey( + vkey: number, + flags: BUILDER_KVK_KEY_FLAGS = BUILDER_KVK_KEY_FLAGS.kvkkBitmap, + shift: BUILDER_KVK_SHIFT_STATE = BUILDER_KVK_SHIFT_STATE.KVKS_NORMAL, + text: string = "text", + bitmap: number[] = null, +): VisualKeyboardKey { + const vkk: VisualKeyboardKey = { + vkey, + flags, + shift, + text, + bitmap: bitmap ? new Uint8Array(bitmap) : null, + }; + return vkk; +} + +function checkBuilderKvkFile(binary: BUILDER_KVK_FILE, vk: VisualKeyboard) { + assert.equal(binary.header.identifier, BUILDER_KVK_HEADER_IDENTIFIER); + assert.equal(binary.header.version, BUILDER_KVK_HEADER_VERSION); + if (vk.header.associatedKeyboard != null) { + assert.deepEqual(binary.header.associatedKeyboard.str, vk.header.associatedKeyboard); + } else { + assert.deepEqual(binary.header.associatedKeyboard.str, ''); + } + assert.equal(binary.header.flags, vk.header.flags); + assert.equal(binary.header.ansiFont.color, VISUAL_KEYBOARD_TEXT_COLOR); + assert.equal(binary.header.ansiFont.size, vk.header.ansiFont.size); + if (vk.header.ansiFont.name != null) { + assert.deepEqual(binary.header.ansiFont.name.str, vk.header.ansiFont.name); + } else { + assert.deepEqual(binary.header.ansiFont.name.str, ''); + } + assert.equal(binary.header.unicodeFont.color, VISUAL_KEYBOARD_TEXT_COLOR); + assert.equal(binary.header.unicodeFont.size, vk.header.unicodeFont.size); + if (vk.header.unicodeFont.name != null) { + assert.deepEqual(binary.header.unicodeFont.name.str, vk.header.unicodeFont.name); + } else { + assert.deepEqual(binary.header.unicodeFont.name.str, ''); + } + for (let idx=0; idx { + describe('Test of fromFilename()', () => { + it('can extract Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('can extract Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('can extract unmatched file extension', () => { + const ext = ".cpp"; + assert.isFalse((Object.values(ALL_SOURCE) as string[]).includes(ext)); + const filename = `file${ext}`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ext); + }); + it('returns empty string for no file extension', () => { + const filename = `file`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ""); + }); + it('can extract upper case file extension', () => { + const ext = ALL_SOURCE[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + describe('Test of removeExtension()', () => { + it('can remove Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = removeExtension(filename); + assert.deepEqual(actual, "file"); + }); + }); + it('can remove Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = removeExtension(filename); + assert.deepEqual(actual, "file"); + }); + }); + it('can handle no file extension', () => { + const filename = removeExtension("file"); + assert.deepEqual(filename, "file"); + }); + }); + describe('Test of sourceOrBinaryTypeFromFilename()', () => { + it('can extract Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = sourceOrBinaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('can extract Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = sourceOrBinaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('returns null for unmatched file extension', () => { + const ext = ".cpp"; + assert.isFalse((Object.values(ALL) as string[]).includes(ext)); + const filename = `file${ext}`; + const actual = sourceOrBinaryTypeFromFilename(filename); + assert.isNull(actual); + }); + it('can extract upper case file extension', () => { + const ext = ALL[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = sourceOrBinaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + describe('Test of sourceTypeFromFilename()', () => { + it('can extract Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = sourceTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('returns null for a Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = sourceTypeFromFilename(filename); + assert.isNull(actual); + }); + }); + it('returns null for unmatched file extension', () => { + const ext = ".cpp"; + assert.isFalse((Object.values(ALL_SOURCE) as string[]).includes(ext)); + const filename = `file${ext}`; + const actual = sourceTypeFromFilename(filename); + assert.isNull(actual); + }); + it('can extract upper case file extension', () => { + const ext = ALL_SOURCE[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = sourceTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + describe('Test of binaryTypeFromFilename()', () => { + it('returns null for a Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = binaryTypeFromFilename(filename); + assert.isNull(actual); + }); + }); + it('can extract Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = binaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('returns null for unmatched file extension', () => { + const ext = ".cpp"; + assert.isFalse((Object.values(ALL_BINARY) as string[]).includes(ext)); + const filename = `file${ext}`; + const actual = binaryTypeFromFilename(filename); + assert.isNull(actual); + }); + it('can extract upper case file extension', () => { + const ext = ALL_BINARY[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = binaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + describe('Test of filenameIs()', () => { + it('can identify Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = filenameIs(filename, ext); + assert.isTrue(actual); + }); + }); + it('can identify Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + if (ext == Binary.Model) { // Special case for .model.js + const actual = filenameIs(filename, Binary.WebKeyboard); + assert.isFalse(actual); + } + const actual = filenameIs(filename, ext); + assert.isTrue(actual); + }); + }); + it('can identify upper case file extension', () => { + const ext = ALL[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = filenameIs(filename, ext); + assert.isTrue(actual); + }); + }); +}); diff --git a/core/docs/api/background.md b/core/docs/api/background.md index b0d09da10f7..d4c638f0ce7 100644 --- a/core/docs/api/background.md +++ b/core/docs/api/background.md @@ -396,7 +396,7 @@ typedef enum { KM_CORE_FALSE = 0, KM_CORE_TRUE = 1 } km_core_bool; [km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct" [km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct" [km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct" -[km_core_keyboard_load]: keyboards#km_core_keyboard_load "km_core_keyboard_load function" +[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function" [km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function" [km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function" [km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function" diff --git a/core/docs/api/changes.md b/core/docs/api/changes.md index ab8a2e287a1..e5736494abc 100644 --- a/core/docs/api/changes.md +++ b/core/docs/api/changes.md @@ -43,7 +43,7 @@ title: Changes - Keyman Core API [km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct" [km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct" [km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct" -[km_core_keyboard_load]: keyboards#km_core_keyboard_load "km_core_keyboard_load function" +[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function" [km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function" [km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function" [km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function" diff --git a/core/docs/api/index.md b/core/docs/api/index.md index c33246f7f44..94bb517654c 100644 --- a/core/docs/api/index.md +++ b/core/docs/api/index.md @@ -104,7 +104,7 @@ Caps Lock. [km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct" [km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct" [km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct" -[km_core_keyboard_load]: keyboards#km_core_keyboard_load "km_core_keyboard_load function" +[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function" [km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function" [km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function" [km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function" diff --git a/core/docs/api/keyboards.md b/core/docs/api/keyboards.md index b9312a78d94..3439e20722b 100644 --- a/core/docs/api/keyboards.md +++ b/core/docs/api/keyboards.md @@ -102,28 +102,35 @@ typedef struct { ------------------------------------------------------------------------------- -# km_core_keyboard_load() {#km_core_keyboard_load} +# km_core_keyboard_load_from_blob() {#km_core_keyboard_load_from_blob} ## Description -Parse and load keyboard from the supplied path and a pointer to the loaded keyboard -into the out paramter. +Parse and load a keyboard from the supplied blob and return a pointer to the +loaded keyboard in the out parameter. ## Specification ```c KMN_API km_core_status -km_core_keyboard_load(km_core_path_name kb_path, - km_core_keyboard **keyboard); +km_core_keyboard_load_from_blob(const km_core_path_name kb_name, + const void* blob, + const size_t blob_size, + km_core_keyboard** keyboard); ``` ## Parameters -`kb_path` -: On Windows, a UTF-16 string; on other platforms, a C string: - contains a valid path to the keyboard file. +`kb_name` +: a string with the name of the keyboard. + +`blob` +: a byte array containing the content of a KMX/KMX+ file. + +`blob_size` +: a size_t variable with the size of the blob in bytes. `keyboard` : A pointer to result variable: A pointer to the opaque keyboard @@ -154,7 +161,7 @@ km_core_keyboard_load(km_core_path_name kb_path, ## Description Free the allocated memory belonging to an opaque keyboard object previously -returned by [km_core_keyboard_load]. +returned by [km_core_keyboard_load_from_blob]. ## Specification @@ -637,7 +644,7 @@ km_core_state_to_json(km_core_state const *state, [km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct" [km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct" [km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct" -[km_core_keyboard_load]: keyboards#km_core_keyboard_load "km_core_keyboard_load function" +[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function" [km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function" [km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function" [km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function" diff --git a/core/docs/api/options.md b/core/docs/api/options.md index e5a171d6fea..41d5fe28c9a 100644 --- a/core/docs/api/options.md +++ b/core/docs/api/options.md @@ -263,7 +263,7 @@ km_core_state_options_to_json(km_core_state const *state, [km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct" [km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct" [km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct" -[km_core_keyboard_load]: keyboards#km_core_keyboard_load "km_core_keyboard_load function" +[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function" [km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function" [km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function" [km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function" diff --git a/core/docs/api/processor.md b/core/docs/api/processor.md index 0cd491ba81a..c72d743bb9c 100644 --- a/core/docs/api/processor.md +++ b/core/docs/api/processor.md @@ -177,7 +177,7 @@ enum km_core_event_code { [km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct" [km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct" [km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct" -[km_core_keyboard_load]: keyboards#km_core_keyboard_load "km_core_keyboard_load function" +[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function" [km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function" [km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function" [km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function" diff --git a/core/docs/api/state.md b/core/docs/api/state.md index 3c0c57a6aa5..0292d9f8a59 100644 --- a/core/docs/api/state.md +++ b/core/docs/api/state.md @@ -264,7 +264,7 @@ An opaque pointer to a state object. [km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct" [km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct" [km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct" -[km_core_keyboard_load]: keyboards#km_core_keyboard_load "km_core_keyboard_load function" +[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function" [km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function" [km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function" [km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function" diff --git a/core/include/keyman/keyman_core_api.h b/core/include/keyman/keyman_core_api.h index 3c41182bab2..7863689296d 100644 --- a/core/include/keyman/keyman_core_api.h +++ b/core/include/keyman/keyman_core_api.h @@ -1096,58 +1096,6 @@ typedef struct { ------------------------------------------------------------------------------- -# km_core_keyboard_load() - -## Description - -DEPRECATED: use [km_core_keyboard_load_from_blob] instead. - -Parse and load keyboard from the supplied path and a pointer to the loaded keyboard -into the out parameter. - -## Specification - -```c */ -// TODO-web-core: Deprecate this function (#12497) -// KMN_DEPRECATED_API -KMN_API -km_core_status -km_core_keyboard_load(km_core_path_name kb_path, - km_core_keyboard **keyboard); - -/* -``` - -## Parameters - -`kb_path` -: On Windows, a UTF-16 string; on other platforms, a C string: - contains a valid path to the keyboard file. - -`keyboard` -: A pointer to result variable: A pointer to the opaque keyboard - object returned by the Processor. This memory must be freed with a - call to [km_core_keyboard_dispose]. - -## Returns - -`KM_CORE_STATUS_OK` -: On success. - -`KM_CORE_STATUS_NO_MEM` -: In the event an internal memory allocation fails. - -`KM_CORE_STATUS_IO_ERROR` -: In the event the keyboard file is unparseable for any reason - -`KM_CORE_STATUS_INVALID_ARGUMENT` -: In the event the file doesn't exist or is inaccesible or `keyboard` is null. - -`KM_CORE_STATUS_OS_ERROR` -: Bit 31 (high bit) set, bits 0-30 are an OS-specific error code. - -------------------------------------------------------------------------------- - # km_core_keyboard_load_from_blob() ## Description @@ -1207,7 +1155,7 @@ km_core_status km_core_keyboard_load_from_blob(const km_core_path_name kb_name, ## Description Free the allocated memory belonging to an opaque keyboard object previously -returned by [km_core_keyboard_load]. +returned by [km_core_keyboard_load_from_blob]. ## Specification diff --git a/core/include/keyman/keyman_core_api_vkeys.h b/core/include/keyman/keyman_core_api_vkeys.h index 7e4a6d181aa..7e65d8ad91a 100644 --- a/core/include/keyman/keyman_core_api_vkeys.h +++ b/core/include/keyman/keyman_core_api_vkeys.h @@ -16,6 +16,7 @@ #pragma once enum km_core_modifier_state { + KM_CORE_MODIFIER_NONE = 0, KM_CORE_MODIFIER_LCTRL = 1 << 0, KM_CORE_MODIFIER_RCTRL = 1 << 1, KM_CORE_MODIFIER_LALT = 1 << 2, diff --git a/core/src/km_core_keyboard_api.cpp b/core/src/km_core_keyboard_api.cpp index abaf3bfaa08..70d37fa63b0 100644 --- a/core/src/km_core_keyboard_api.cpp +++ b/core/src/km_core_keyboard_api.cpp @@ -40,16 +40,18 @@ namespace } // namespace km_core_status -keyboard_load_from_blob_internal( +km_core_keyboard_load_from_blob( const km_core_path_name kb_name, - const std::vector & buf, + const void* blob, + const size_t blob_size, km_core_keyboard** keyboard ) { assert(keyboard); - if (!keyboard) { + if (!keyboard || !blob) { return KM_CORE_STATUS_INVALID_ARGUMENT; } + std::vector buf((uint8_t*)blob, (uint8_t*)blob + blob_size); *keyboard = nullptr; try { abstract_processor* kp = processor_factory(kb_name, buf); @@ -65,88 +67,6 @@ keyboard_load_from_blob_internal( return KM_CORE_STATUS_OK; } -km_core_status -km_core_keyboard_load_from_blob( - const km_core_path_name kb_name, - const void* blob, - const size_t blob_size, - km_core_keyboard** keyboard -) { - assert(keyboard); - if (!keyboard || !blob) { - return KM_CORE_STATUS_INVALID_ARGUMENT; - } - - std::vector buf((uint8_t*)blob, (uint8_t*)blob + blob_size); - return keyboard_load_from_blob_internal(kb_name, buf, keyboard); -} - -// TODO-web-core: Remove this code when we remove the deprecated km_core_keyboard_load method -// BEGIN DEPRECATED -#include -std::vector load_kmx_file(path const& kb_path) { - std::vector data; - std::ifstream file(static_cast(kb_path), std::ios::binary | std::ios::ate); - if (!file.good()) { - return std::vector(); - } - const std::streamsize size = file.tellg(); - if (size >= KMX_MAX_ALLOWED_FILE_SIZE) { - return std::vector(); - } - - file.seekg(0, std::ios::beg); - - data.resize((size_t)size); - if (!file.read((char*)data.data(), size)) { - return std::vector(); - } - - file.close(); - return data; -} - -KMN_DEPRECATED_API -km_core_status -km_core_keyboard_load(km_core_path_name kb, km_core_keyboard **keyboard) -{ - assert(keyboard); - if (!keyboard || !kb) { - return KM_CORE_STATUS_INVALID_ARGUMENT; - } - - path const kb_path(kb); - try - { - abstract_processor* kp = nullptr; - km_core_status status = KM_CORE_STATUS_OK; - // Some legacy packages may include upper-case file extensions - if (kb_path.suffix() == ".kmx" || kb_path.suffix() == ".KMX") { - std::vector buf = load_kmx_file(kb_path); - status = keyboard_load_from_blob_internal(kb_path.stem().c_str(), buf, (km_core_keyboard**)&kp); - if (status != KM_CORE_STATUS_OK) { - return status; - } - } else if (kb_path.suffix() == ".mock") { - kp = new mock_processor(kb_path); - } else { - kp = new null_processor(); - } - status = kp->validate(); - if (status != KM_CORE_STATUS_OK) { - delete kp; - return status; - } - *keyboard = static_cast(kp); - } - catch (std::bad_alloc &) - { - return KM_CORE_STATUS_NO_MEM; - } - return KM_CORE_STATUS_OK; -} -// END DEPRECATED - void km_core_keyboard_dispose(km_core_keyboard *keyboard) { diff --git a/core/src/kmx/kmx_plus.h b/core/src/kmx/kmx_plus.h index a8e5c1f0e46..e3a510b76df 100644 --- a/core/src/kmx/kmx_plus.h +++ b/core/src/kmx/kmx_plus.h @@ -455,7 +455,13 @@ class COMP_KMXPLUS_LAYR_Helper { bool setLayr(const COMP_KMXPLUS_LAYR *newLayr); bool valid() const; + /** + * @param list index from 0 to layr->listCount + */ const COMP_KMXPLUS_LAYR_LIST *getList(KMX_DWORD list) const; + /** + * @param entry index value: COMP_KMXPLUS_LAYR_LIST.layer but less than COMP_KMXPLUS_LAYR_LIST.layer+COMP_KMXPLUS_LAYR_LIST.count + */ const COMP_KMXPLUS_LAYR_ENTRY *getEntry(KMX_DWORD entry) const; const COMP_KMXPLUS_LAYR_ROW *getRow(KMX_DWORD row) const; const COMP_KMXPLUS_LAYR_KEY *getKey(KMX_DWORD key) const; diff --git a/core/src/ldml/ldml_processor.cpp b/core/src/ldml/ldml_processor.cpp index 548c0d76b00..6f90c1ad489 100644 --- a/core/src/ldml/ldml_processor.cpp +++ b/core/src/ldml/ldml_processor.cpp @@ -328,8 +328,7 @@ km_core_attr const & ldml_processor::attributes() const { } km_core_keyboard_key * ldml_processor::get_key_list() const { - km_core_keyboard_key* key_list = new km_core_keyboard_key(KM_CORE_KEYBOARD_KEY_LIST_END); - return key_list; + return keys.get_key_list(); } km_core_keyboard_imx * ldml_processor::get_imx_list() const { diff --git a/core/src/ldml/ldml_processor.hpp b/core/src/ldml/ldml_processor.hpp index 82f59292c32..6bf8a8fa318 100644 --- a/core/src/ldml/ldml_processor.hpp +++ b/core/src/ldml/ldml_processor.hpp @@ -34,10 +34,6 @@ class ldml_processor : public abstract_processor { const std::vector & data ); - static bool is_kmxplus_file( - const std::vector & data - ); - km_core_status process_event( km_core_state *state, diff --git a/core/src/ldml/ldml_vkeys.cpp b/core/src/ldml/ldml_vkeys.cpp index f79f82141fc..dc3f22f18b1 100644 --- a/core/src/ldml/ldml_vkeys.cpp +++ b/core/src/ldml/ldml_vkeys.cpp @@ -8,6 +8,8 @@ #include "ldml_vkeys.hpp" #include "kmx_file.h" #include +#include +#include namespace km { namespace core { @@ -22,6 +24,114 @@ vkeys::add(km_core_virtual_key vk, km_core_ldml_modifier_state modifier_state, s const vkey_id id(vk, modifier_state); // assign the string vkey_to_string[id] = output; + // includes all keys - including gaps. + all_vkeys.insert(id); +} + +km_core_keyboard_key * +vkeys::get_key_list() const { + // prescan to find out which modifier flags are used + + std::size_t other_key_count = 0; // number of 'OTHER' keys, which will need to expand to all of the other_state set ( so other_state.size()) + + std::set all_modifiers; + for (const auto &k : all_vkeys) { + const auto mod = k.second; + if (mod == LDML_KEYS_MOD_OTHER) { + other_key_count++; + } + all_modifiers.insert(mod); + } + std::set other_state; + + // Alt + if (all_modifiers.count(LDML_KEYS_MOD_ALT) == 0 && all_modifiers.count(LDML_KEYS_MOD_ALTL) == 0 && all_modifiers.count(LDML_KEYS_MOD_ALTR) == 0) { + // no ALT keys were seen, so OTHER includes ALT + other_state.insert(KM_CORE_MODIFIER_ALT); + } else if(all_modifiers.count(LDML_KEYS_MOD_ALTL) == 0) { + other_state.insert(KM_CORE_MODIFIER_RALT); + } else if(all_modifiers.count(LDML_KEYS_MOD_ALTR) == 0) { + other_state.insert(KM_CORE_MODIFIER_LALT); + } + + // ctrl + if (all_modifiers.count(LDML_KEYS_MOD_CTRL) == 0 && all_modifiers.count(LDML_KEYS_MOD_CTRLL) == 0 && all_modifiers.count(LDML_KEYS_MOD_CTRLR) == 0) { + // no CTRL keys were seen, so OTHER includes CTRL + other_state.insert(KM_CORE_MODIFIER_CTRL); + } else if(all_modifiers.count(LDML_KEYS_MOD_CTRLL) == 0) { + other_state.insert(KM_CORE_MODIFIER_RCTRL); + } else if(all_modifiers.count(LDML_KEYS_MOD_CTRLR) == 0) { + other_state.insert(KM_CORE_MODIFIER_LCTRL); + } + + // shift + if (all_modifiers.count(LDML_KEYS_MOD_SHIFT) == 0) { + other_state.insert(KM_CORE_MODIFIER_SHIFT); + } + + // caps + if (all_modifiers.count(LDML_KEYS_MOD_CAPS) == 0) { + other_state.insert(KM_CORE_MODIFIER_CAPS); + } + + // none- it's possible there is no 'none' layer + if (all_modifiers.count(LDML_KEYS_MOD_NONE) == 0) { + other_state.insert(KM_CORE_MODIFIER_NONE); + } + + // We need ALL combinations of the other_state, except for 'all off'. + // The number of additions will be (2**(other_state.size())-1 + // Also, since the LDML_KEYS_MOD_OTHER modifier is excluded, we + // will need to subtract 1 when calculating new_list_size + const std::size_t other_expanded_count = (1 << other_state.size()) - 1; + + std::vector other_expanded_mods(other_expanded_count); + + // populate the expanded list. + // we start at 1 because 0 is "all bits off" (00000b) + for (std::size_t expansion = 1; expansion <= other_expanded_count; expansion++) { + uint32_t &expanded_mod = other_expanded_mods.at(expansion-1) = KM_CORE_MODIFIER_NONE; + std::size_t bit_mask = 1; + for (const auto mod : other_state) { + // Check if this modifier is on in this iteration of the expansion + // bit_mask will be 2^0 … 2^(other_state().size()-1) + if (bit_mask & expansion) { + // do we include this entry? check if this bit is on + expanded_mod |= mod; + } + // shift the bitmask over + assert(expanded_mod <= KM_CORE_MODIFIER_MASK_CAPS); + bit_mask <<= 1; + } + } + + const std::size_t new_list_size = all_vkeys.size() // original size + + (other_key_count * (other_expanded_count - 1))// number of additional entries needed + + 1; // terminator + km_core_keyboard_key *list = new km_core_keyboard_key[new_list_size]; + std::size_t n = 0; + for (const auto &k : all_vkeys) { + const auto vkey = k.first; + const auto mod = k.second; + + if (mod == LDML_KEYS_MOD_OTHER) { + // expand to all of other_state + for (const auto expanded_mod : other_expanded_mods) { + list[n].key = vkey; + list[n++].modifier_flag = expanded_mod; + assert(n <= new_list_size); + } + } else { + assert(mod <= KM_CORE_MODIFIER_MASK_CAPS); // that no LDMLisms escape + list[n].key = vkey; + list[n++].modifier_flag = mod; + assert(n <= new_list_size); + } + } + // add the list terminator + list[n++] = KM_CORE_KEYBOARD_KEY_LIST_END; + assert(n == new_list_size); + return list; } static const uint16_t BOTH_ALT = LALTFLAG | RALTFLAG; diff --git a/core/src/ldml/ldml_vkeys.hpp b/core/src/ldml/ldml_vkeys.hpp index 6a54d770653..916d805e2c9 100644 --- a/core/src/ldml/ldml_vkeys.hpp +++ b/core/src/ldml/ldml_vkeys.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "keyman_core.h" @@ -37,6 +38,7 @@ typedef std::pair vkey_id; class vkeys { private: std::map vkey_to_string; + std::set all_vkeys; public: vkeys(); @@ -53,6 +55,11 @@ class vkeys { std::u16string lookup(km_core_virtual_key vk, uint16_t modifier_state, bool &found) const; + /** + * For implementing ldml_processor::get_key_list() + */ + km_core_keyboard_key* get_key_list() const; + private: /** * Non-recursive internal lookup of a specific ID diff --git a/core/tests/unit/km_core_keyboard_api.tests.cpp b/core/tests/unit/km_core_keyboard_api.tests.cpp index 184ec130c42..2898f9e7cd7 100644 --- a/core/tests/unit/km_core_keyboard_api.tests.cpp +++ b/core/tests/unit/km_core_keyboard_api.tests.cpp @@ -10,20 +10,6 @@ km::core::path test_dir; -// TODO-web-core: Remove this code when we remove the deprecated km_core_keyboard_load method -// BEGIN DEPRECATED -#if defined(__GNUC__) || defined(__clang__) -#define PRAGMA(X) _Pragma(#X) -#define DISABLE_WARNING_PUSH PRAGMA(GCC diagnostic push) -#define DISABLE_WARNING_POP PRAGMA(GCC diagnostic pop) -#define DISABLE_WARNING(W) PRAGMA(GCC diagnostic ignored #W) -#define DISABLE_WARNING_DEPRECATED_DECLARATIONS DISABLE_WARNING(-Wdeprecated-declarations) -#else -#define DISABLE_WARNING_PUSH -#define DISABLE_WARNING_POP -#define DISABLE_WARNING_DEPRECATED_DECLARATIONS -#endif - class KmCoreKeyboardApiTests : public testing::Test { protected: km_core_keyboard* keyboard = nullptr; @@ -35,22 +21,6 @@ class KmCoreKeyboardApiTests : public testing::Test { } }; -TEST_F(KmCoreKeyboardApiTests, LoadFromFile) { - // Setup - km::core::path kmxfile = km::core::path(test_dir / "kmx/k_020___deadkeys_and_backspace.kmx"); - - // Execute - DISABLE_WARNING_PUSH - DISABLE_WARNING_DEPRECATED_DECLARATIONS - auto status = km_core_keyboard_load(kmxfile.c_str(), &this->keyboard); - DISABLE_WARNING_POP - - // Verify - EXPECT_EQ(status, KM_CORE_STATUS_OK); - EXPECT_TRUE(this->keyboard != nullptr); -} -// END DEPRECATED - TEST_F(KmCoreKeyboardApiTests, LoadFromBlob) { // Setup km::core::path kmxfile = km::core::path(test_dir / "kmx/k_020___deadkeys_and_backspace.kmx"); diff --git a/core/tests/unit/kmx/fixtures/binary/meson.build b/core/tests/unit/kmx/fixtures/binary/meson.build index cf96bc75ce7..a6fc50150ad 100644 --- a/core/tests/unit/kmx/fixtures/binary/meson.build +++ b/core/tests/unit/kmx/fixtures/binary/meson.build @@ -16,11 +16,7 @@ binary_tests = [ ] foreach kbd : binary_tests - configure_file( - input: kbd + '.kmn', - output: kbd + '.kmn', - copy: true - ) + fs.copyfile(kbd + '.kmn', kbd + '.kmn') configure_file( command: hextobin_cmd + ['@INPUT@', '@OUTPUT@'], diff --git a/core/tests/unit/kmx/meson.build b/core/tests/unit/kmx/meson.build index a7a69079e4a..421a133eefb 100644 --- a/core/tests/unit/kmx/meson.build +++ b/core/tests/unit/kmx/meson.build @@ -139,7 +139,10 @@ foreach kbd : tests cfg.set('CONTENT', content) kbd_kmn = configure_file( - copy: true, + # not really a file that needs to be configured, but the `copy` parameter + # is deprecated and `fs.copyfile` doesn't work with files that are not in + # the source directory + configuration: cfg, input: kbd_src_path, output: kbd + '.kmn' ) diff --git a/core/tests/unit/ldml/core_ldml_min.tests.cpp b/core/tests/unit/ldml/core_ldml_min.tests.cpp index fadd62a2a45..9657b104b51 100644 --- a/core/tests/unit/ldml/core_ldml_min.tests.cpp +++ b/core/tests/unit/ldml/core_ldml_min.tests.cpp @@ -26,7 +26,7 @@ int main(int argc, const char *argv[]) { auto blob = km::tests::load_kmx_file(nowhere); status = km_core_keyboard_load_from_blob(nowhere, blob.data(), blob.size(), &test_kb); - std::cerr << "null km_core_keyboard_load = " << status << std::endl; + std::cerr << "null km_core_keyboard_load_from_blob = " << status << std::endl; test_assert(status == KM_CORE_STATUS_INVALID_ARGUMENT); test_assert(test_kb == nullptr); km_core_keyboard_dispose(test_kb); diff --git a/core/tests/unit/ldml/invalid-keyboards/meson.build b/core/tests/unit/ldml/invalid-keyboards/meson.build index e85646b68ea..f487cb83b91 100644 --- a/core/tests/unit/ldml/invalid-keyboards/meson.build +++ b/core/tests/unit/ldml/invalid-keyboards/meson.build @@ -11,11 +11,7 @@ invalid_tests = [ # Build all keyboards in output folder foreach kbd : invalid_tests - configure_file( - input: kbd + '.xml', - output: kbd + '.xml', - copy: true - ) + fs.copyfile(kbd + '.xml', kbd + '.xml') configure_file( command: hextobin_cmd + ['@INPUT@', '@OUTPUT@'], diff --git a/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml b/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml index 4346fa801b0..2d152867a75 100644 --- a/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml +++ b/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml @@ -2,8 +2,9 @@ @@ -23,5 +24,11 @@ + + + + + + diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index 15f458c587f..394b8a68113 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -65,11 +65,7 @@ kmc_cmd = [node, '--enable-source-maps', kmc_root] # Build all keyboards in output folder foreach kbd : tests_needing_copy - configure_file( - copy: true, - input: kbd + '.xml', - output: kbd + '.xml' - ) + fs.copyfile(kbd + '.xml', kbd + '.xml') configure_file( command: kmc_cmd + ['build', '@INPUT@', '--out-file', '@OUTPUT@'], @@ -86,13 +82,8 @@ foreach kbd : tests_with_testdata ) endforeach - foreach kbd : tests_from_cldr - configure_file( - copy: true, - input: ldml_data / kbd + '.xml', - output: kbd + '.xml' - ) + fs.copyfile(ldml_data / kbd + '.xml', kbd + '.xml') configure_file( command: kmc_cmd + ['build', '@INPUT@', '--out-file', '@OUTPUT@'], input: join_paths(ldml_data, kbd + '.xml'), diff --git a/core/tests/unit/ldml/ldml.cpp b/core/tests/unit/ldml/ldml.cpp index 78e0aacebac..f0b1eac4fed 100644 --- a/core/tests/unit/ldml/ldml.cpp +++ b/core/tests/unit/ldml/ldml.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "path.hpp" #include "state.hpp" @@ -268,6 +269,63 @@ verify_context(std::u16string &text_store, km_core_state *&test_state, std::vect delete[] buf; } + +/** + * @param actual the list from get_key_list() + * @param expected optional list with keys to check, can be empty - not exhaistive + * @returns true if passing + */ +bool +verify_key_list(std::set &actual, std::set &expected) { + bool equals = true; + // error if any bad modifier keys + for(const auto &akey : actual) { + if (akey.modifier_state > KM_CORE_MODIFIER_MASK_CAPS) { + equals = false; + std::u16string dump = convert(akey.dump()); // akey.dump() + std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - key_map had key with bad modifier " << akey.modifier_state << ": " << dump << console_color::reset() << std::endl; + } + } + // error if any expected keys missing (note expected may be empty) + for(const auto &ekey : expected) { + if (actual.count(ekey) == 0) { + equals = false; + std::u16string dump = convert(ekey.dump()); // akey.dump() + std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - key_map had missing key " << dump << console_color::reset() << std::endl; + } + } + if (equals) { + std::wcout << console_color::fg(console_color::GREEN) << " " << actual.size() << " vkeys OK, verified " << expected.size() << console_color::reset() << std::endl; + } + return equals; +} + +/** + * @param actual_list the list from get_key_list() + * @param keylist optional string with keys to check, can be empty + * @param test the LDML test source, for additional data + * @returns true if passing + */ +bool +verify_key_list(const km_core_keyboard_key *actual_list, const std::u16string &expected_list, const km::tests::LdmlTestSource &test) { + std::set actual, expected; + // convert actual list + while (actual_list != nullptr && !(actual_list->key == 0 && actual_list->modifier_flag == 0)) { + km::tests::key_event k(actual_list->key, (uint16_t)actual_list->modifier_flag); + actual.insert(k); + actual_list++; // advance pointer + } + // parse the expected list + std::string keylist = convert(expected_list); + while (!keylist.empty() && keylist[0] == '[') { + const km::tests::key_event k = km::tests::LdmlEmbeddedTestSource::parse_next_key(keylist); + if (!k.empty()) { + expected.emplace(k); + } + } + return verify_key_list(actual, expected); +} + int run_test(const km::core::path &source, const km::core::path &compiled, km::tests::LdmlTestSource& test_source) { km_core_keyboard * test_kb = nullptr; @@ -374,6 +432,17 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests errorLine = __LINE__; } } break; + case km::tests::LDML_ACTION_CHECK_KEYLIST: { + std::cout << "- checking keylist" << std::endl; + // get keylist from kbd + const km_core_keyboard_key* actual_list = test_kb->get_key_list(); + if (!verify_key_list(actual_list, action.string, test_source)) { + errorLine = __LINE__; + } else { + std::cout << " .. passes." << std::endl; + } + delete [] actual_list; + } break; case km::tests::LDML_ACTION_FAIL: { // test requested failure std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL: " << action.string << console_color::reset() @@ -424,7 +493,7 @@ int run_all_tests(const km::core::path &source, const km::core::path &compiled, std::vector failures; // track failures for summary - int embedded_result = embedded_test_source.load_source(source); + int embedded_result = embedded_test_source.load_source(source, compiled); if (!filter.empty()) { // Always skip the embedded test if there's a filter. diff --git a/core/tests/unit/ldml/ldml_test_source.cpp b/core/tests/unit/ldml/ldml_test_source.cpp index 94e10e7ac43..76b7a34a4a4 100644 --- a/core/tests/unit/ldml/ldml_test_source.cpp +++ b/core/tests/unit/ldml/ldml_test_source.cpp @@ -127,6 +127,50 @@ bool LdmlTestSource::get_expected_beep() const { return false; } +int LdmlTestSource::load_kmx_plus(const km::core::path &compiled) { + // check and load the KMX (yes, once again) + rawdata = km::tests::load_kmx_file(compiled); + if(!km::core::ldml_processor::is_handled(rawdata)) { + std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl; + return __LINE__; + } + + auto comp_keyboard = (const km::core::kmx::COMP_KEYBOARD*)rawdata.data(); + // initialize the kmxplus object with our copy + kmxplus.reset(new km::core::kmx::kmx_plus(comp_keyboard, rawdata.size())); + + if (!kmxplus->is_valid()) { + std::cerr << "kmx_plus invalid" << std::endl; + return __LINE__; + } + + if (!kmxplus->key2Helper.valid()) { + std::cerr << "kmx_plus invalid" << std::endl; + return __LINE__; + } + + return 0; // success +} + +bool LdmlTestSource::get_vkey_table(std::set &fillin) const { + if (!kmxplus || !kmxplus->is_valid()) { + return false; // fail + } + + // just dump the kmap table + for (KMX_DWORD kmapIdx = 0; kmapIdx < kmxplus->key2->kmapCount; kmapIdx++) { + const km::core::kmx::COMP_KMXPLUS_KEYS_KMAP *kmap = kmxplus->key2Helper.getKmap(kmapIdx); + if (kmap == nullptr) { + return false; + } + if (kmap->vkey > 0xFF) { + continue; // synthetic key- skip + } + fillin.insert(key_event(kmap->vkey, kmap->mod)); + } + return true; +} + // String trim functions from https://stackoverflow.com/a/217605/1836776 // trim from start (in place) static inline void @@ -264,12 +308,13 @@ LdmlEmbeddedTestSource::is_token(const std::string token, std::string &line) { } int -LdmlEmbeddedTestSource::load_source( const km::core::path &path ) { +LdmlEmbeddedTestSource::load_source( const km::core::path &path, const km::core::path &compiled ) { const std::string s_keys = "@@keys: "; const std::string s_expected = "@@expected: "; const std::string s_context = "@@context: "; const std::string s_capsLock = "@@capsLock: "; const std::string s_expecterror = "@@expect-error: "; + const std::string s_keylist = "@@keylist: "; // Parse out the header statements in file.kmn that tell us (a) environment, (b) key sequence, (c) start context, (d) expected // result @@ -301,6 +346,10 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) { context = parse_source_string(line); } else if (is_token(s_capsLock, line)) { set_caps_lock_on(parse_source_string(line).compare(u"1") == 0); + } else if (is_token(s_keylist, line)) { + set_keylist(line); + } else if (line[0] == '@') { + std::cerr << path << " warning, unknown @-command " << line << std::endl; } } @@ -316,7 +365,13 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) { return __LINE__; } - return 0; + // and load KMX+ for keylist + if (!expected_error) { + // don't attempt to load KMX+ on expected error + return load_kmx_plus(compiled); + } else { + return 0; + } } km_core_status @@ -357,7 +412,7 @@ LdmlTestSource::char_to_event(char ch) { } uint16_t -LdmlTestSource::get_modifier(std::string const m) { +LdmlTestSource::get_modifier(std::string const &m) { for (int i = 0; km::core::kmx::s_modifier_names[i].name; i++) { if (m == km::core::kmx::s_modifier_names[i].name) { return km::core::kmx::s_modifier_names[i].modifier; @@ -366,6 +421,12 @@ LdmlTestSource::get_modifier(std::string const m) { return 0; } +std::string key_event::dump() const { + std::stringstream f; + f << "Key: {" << km::core::kmx::Debug_VirtualKey(vk) << ", " << km::core::kmx::Debug_ModifierName(modifier_state) << "}"; + return f.str(); +} + key_event LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) { // vkey format is MODIFIER MODIFIER K_NAME @@ -401,34 +462,37 @@ LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) { void LdmlEmbeddedTestSource::next_action(ldml_action &fillin) { - if (is_done || keys.empty()) { - // We were already done. return done. - fillin.type = LDML_ACTION_DONE; - return; + if (keys.empty()) { + // #3 we are almost done, let's run the key check + if (check_keylist) { + fillin.type = LDML_ACTION_CHECK_KEYLIST; + fillin.string = expected_keylist; // could be empty + check_keylist = false; + } else { + fillin.type = LDML_ACTION_DONE; + } } else if(keys[0].empty()) { + // #2. Then, when we finish a key set, we check the 'expected' at the end of it. // Got to the end of a key set. time to check fillin.type = LDML_ACTION_CHECK_EXPECTED; fillin.string = expected[0]; // copy expected expected.pop_front(); keys.pop_front(); - if (keys.empty()) { - is_done = true; // so we get DONE next time - } } else { + // #1 First, we process each key fillin.type = LDML_ACTION_KEY_EVENT; fillin.k = next_key(); } } - key_event LdmlEmbeddedTestSource::next_key() { // mutate this->keys - return next_key(keys[0]); + return parse_next_key(keys[0]); } key_event -LdmlEmbeddedTestSource::next_key(std::string &keys) { +LdmlEmbeddedTestSource::parse_next_key(std::string &keys) { // Parse the next element of the string, chop it off, and return it // mutates keys if (keys.length() == 0) @@ -453,10 +517,10 @@ LdmlEmbeddedTestSource::next_key(std::string &keys) { class LdmlJsonTestSource : public LdmlTestSource { public: - LdmlJsonTestSource(const std::string &path, km::core::kmx::kmx_plus *kmxplus); + LdmlJsonTestSource(const std::string &path); virtual ~LdmlJsonTestSource(); virtual const std::u16string &get_context(); - int load(const nlohmann::json &test); + int load(const nlohmann::json &test, const km::core::path &compiled); virtual void next_action(ldml_action &fillin); private: std::string path; @@ -467,14 +531,14 @@ class LdmlJsonTestSource : public LdmlTestSource { * Which action are we on? */ std::size_t action_index = -1; - const km::core::kmx::kmx_plus *kmxplus; /** @return false if not found */ bool set_key_from_id(key_event& k, const std::u16string& id); bool loaded_context = false; + bool check_keys = true; }; -LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path, km::core::kmx::kmx_plus *k) -:path(path), kmxplus(k) { +LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path) +:path(path) { } @@ -519,6 +583,13 @@ bool LdmlJsonTestSource::set_key_from_id(key_event& k, const std::u16string& id) void LdmlJsonTestSource::next_action(ldml_action &fillin) { if ((action_index+1) >= data["/actions"_json_pointer].size()) { + // add check keylist + if (check_keys) { + fillin.type = LDML_ACTION_CHECK_KEYLIST; + fillin.string.clear(); + check_keys = false; + return; + } // at end, done fillin.type = LDML_ACTION_DONE; return; @@ -586,19 +657,21 @@ LdmlJsonTestSource::get_context() { return context; } -int LdmlJsonTestSource::load(const nlohmann::json &data) { +int LdmlJsonTestSource::load(const nlohmann::json &data, const km::core::path &compiled) { this->data = data; - // TODO-LDML: validate here? - return 0; + // TODO-LDML: validate JSON here? + + // load up the kmx_plus + return load_kmx_plus(compiled); } #if defined(HAVE_ICU4C) class LdmlJsonRepertoireTestSource : public LdmlTestSource { public: - LdmlJsonRepertoireTestSource(const std::string &path, km::core::kmx::kmx_plus *kmxplus); + LdmlJsonRepertoireTestSource(const std::string &path ); virtual ~LdmlJsonRepertoireTestSource(); virtual const std::u16string &get_context(); - int load(const nlohmann::json &test); + int load(const nlohmann::json &test, const km::core::path &compiled); virtual void next_action(ldml_action &fillin); private: std::string path; @@ -610,11 +683,10 @@ class LdmlJsonRepertoireTestSource : public LdmlTestSource { std::unique_ptr uset; std::unique_ptr iterator; bool need_check = false; // set this after each char - const km::core::kmx::kmx_plus *kmxplus; }; -LdmlJsonRepertoireTestSource::LdmlJsonRepertoireTestSource(const std::string &path, km::core::kmx::kmx_plus *k) -:path(path), kmxplus(k){ +LdmlJsonRepertoireTestSource::LdmlJsonRepertoireTestSource(const std::string &path) +:path(path) { } @@ -702,7 +774,7 @@ LdmlJsonRepertoireTestSource::get_context() { return context; // no context needed } -int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data) { +int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data, const km::core::path &compiled) { this->data = data; // TODO-LDML // Need an update to json.hpp to use contains() // if (data.contains("/type"_json_pointer)) { @@ -733,7 +805,7 @@ int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data) { // } // #endif iterator = std::unique_ptr(new icu::UnicodeSetIterator(*uset)); - return 0; + return load_kmx_plus(compiled); } #endif // HAVE_ICU4C @@ -757,26 +829,6 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co return __LINE__; // empty } - // check and load the KMX (yes, once again) - rawdata = km::tests::load_kmx_file(compiled); - if (!km::core::ldml_processor::is_handled(rawdata)) { - std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl; - return __LINE__; - } - - auto comp_keyboard = (const km::core::kmx::COMP_KEYBOARD*)rawdata.data(); - // initialize the kmxplus object with our copy - kmxplus.reset(new km::core::kmx::kmx_plus(comp_keyboard, rawdata.size())); - - if (!kmxplus->is_valid()) { - std::cerr << "kmx_plus invalid" << std::endl; - return __LINE__; - } - - if (!kmxplus->key2Helper.valid()) { - std::cerr << "kmx_plus invalid" << std::endl; - return __LINE__; - } auto conformsTo = data["/keyboardTest3/conformsTo"_json_pointer].get(); assert_or_return(std::string(LDML_CLDR_TEST_VERSION_LATEST) == conformsTo); @@ -799,8 +851,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co test_path.append(info_name).append("/tests/").append(tests_name).append("/").append(test_name); // std::cout << "JSON: reading " << info_name << "/" << test_path << std::endl; - std::unique_ptr subtest(new LdmlJsonTestSource(test_path, kmxplus.get())); - assert_or_return(subtest->load(test) == 0); + std::unique_ptr subtest(new LdmlJsonTestSource(test_path)); + assert_or_return(subtest->load(test, compiled) == 0); test_map[test_path] = std::unique_ptr(subtest.release()); } } @@ -815,8 +867,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co std::string test_path; test_path.append(info_name).append("/repertoire/").append(rep_name); - std::unique_ptr reptest(new LdmlJsonRepertoireTestSource(test_path, kmxplus.get())); - assert_or_return(reptest->load(rep) == 0); + std::unique_ptr reptest(new LdmlJsonRepertoireTestSource(test_path)); + assert_or_return(reptest->load(rep, compiled) == 0); test_map[test_path] = std::unique_ptr(reptest.release()); } diff --git a/core/tests/unit/ldml/ldml_test_source.hpp b/core/tests/unit/ldml/ldml_test_source.hpp index 8d2b05159a9..4e511d43bf2 100644 --- a/core/tests/unit/ldml/ldml_test_source.hpp +++ b/core/tests/unit/ldml/ldml_test_source.hpp @@ -4,6 +4,7 @@ #include "path.hpp" #include +#include #include #include "kmx/kmx_plus.h" @@ -17,6 +18,34 @@ namespace tests { struct key_event { km_core_virtual_key vk; uint16_t modifier_state; + public: + key_event() : vk(0), modifier_state(0) { + } + key_event(km_core_virtual_key k, uint16_t m) :vk(k), modifier_state(m) { + } + std::string dump() const; + int compare(const key_event &other) const { + if (vk < other.vk) return -1; + if (vk > other.vk) return 1; + if (modifier_state < other.modifier_state) return -1; + if (modifier_state > other.modifier_state) return 1; + return 0; + } + + bool operator<(const key_event &other) const { + return compare(other) < 0; + } + bool operator>(const key_event &other) const { + return compare(other) > 0; + } + bool operator==(const key_event &other) const { + return compare(other) == 0; + } + /** true if unset (null) key */ + bool empty() const { + return vk == 0 && modifier_state == 0; + } + }; enum ldml_action_type { @@ -40,6 +69,10 @@ enum ldml_action_type { * expected text */ LDML_ACTION_CHECK_EXPECTED, + /** + * string - keylist to check + */ + LDML_ACTION_CHECK_KEYLIST, // TODO-LDML: gestures, etc? Depends on touch. /** @@ -80,10 +113,17 @@ class LdmlTestSource { virtual km_core_status get_expected_load_status(); virtual const std::u16string &get_context() = 0; virtual bool get_expected_beep() const; + /** + * fillin a list + * @param fillin key list to be filled in with all vkeys in the hardware map. + * @param modifier modifier to load key list for + * @returns false on load fail, true on OK + */ + bool get_vkey_table(std::set &fillin) const; // helper functions static key_event char_to_event(char ch); - static uint16_t get_modifier(std::string const m); + static uint16_t get_modifier(std::string const &m); static std::u16string parse_source_string(std::string const &s); static std::u16string parse_u8_source_string(std::string const &s); @@ -105,6 +145,12 @@ class LdmlTestSource { private: bool _caps_lock_on = false; +protected: + /** populate rawdata and kmxplus */ + int load_kmx_plus(const km::core::path &compiled); + // copy of the kbd data, for lookups + std::vector rawdata; + std::unique_ptr kmxplus; }; typedef std::map> JsonTestMap; @@ -123,9 +169,6 @@ class LdmlJsonTestSourceFactory { const JsonTestMap& get_tests() const; private: JsonTestMap test_map; - // copy of the kbd data, for lookups - std::vector rawdata; - std::unique_ptr kmxplus; }; @@ -137,7 +180,7 @@ class LdmlEmbeddedTestSource : public LdmlTestSource { /** * Load the test_source from comments in the .xml source */ - int load_source(const km::core::path &path); + int load_source(const km::core::path &path, const km::core::path &compiled); virtual km_core_status get_expected_load_status(); virtual const std::u16string &get_context(); @@ -146,10 +189,6 @@ class LdmlEmbeddedTestSource : public LdmlTestSource { virtual void next_action(ldml_action &fillin); private: - - bool is_token(const std::string token, std::string &line); - key_event vkey_to_event(std::string const &vk_event); - key_event next_key(std::string &keys); key_event next_key(); std::deque keys; @@ -158,6 +197,24 @@ class LdmlEmbeddedTestSource : public LdmlTestSource { bool expected_beep = false; bool expected_error = false; bool is_done = false; + /** did we check the keylist yet? */ + bool check_keylist = true; + /** set the expected keys in the keylist */ + void set_keylist(std::string const& s) { + expected_keylist = parse_source_string(s); + } + + std::u16string expected_keylist; + + /** returns false on fail and updates the message */ + bool handle_check_keylist(std::string &message) const; + +// utility + static key_event vkey_to_event(std::string const &vk_event); + static bool is_token(const std::string token, std::string &line); + +public: + static key_event parse_next_key(std::string &keys); }; } // namespace tests diff --git a/core/tests/unit/ldml/meson.build b/core/tests/unit/ldml/meson.build index 08512bd5500..e7a271b91f8 100644 --- a/core/tests/unit/ldml/meson.build +++ b/core/tests/unit/ldml/meson.build @@ -43,16 +43,8 @@ else endif # copy package.json into build dir for test use -configure_file( - copy: true, - input: '../../../../package.json', - output: 'package.json', -) -configure_file( - copy: true, - input: '../../../../resources/standards-data/unicode-character-database/Blocks.txt', - output: 'Blocks.txt', -) +fs.copyfile('../../../../package.json', 'package.json') +fs.copyfile('../../../../resources/standards-data/unicode-character-database/Blocks.txt', 'Blocks.txt') configure_file( command: [node, meson.current_source_dir() / 'write_node_versions.js','@OUTPUT@'], diff --git a/core/wasm.build.linux.in b/core/wasm.build.linux.in index 27cb7ba68fe..90a31bf75b9 100644 --- a/core/wasm.build.linux.in +++ b/core/wasm.build.linux.in @@ -2,3 +2,4 @@ c = ['$EMSCRIPTEN_BASE/emcc'] cpp = ['$EMSCRIPTEN_BASE/em++'] ar = ['$EMSCRIPTEN_BASE/emar'] +strip = ['$EMSCRIPTEN_BASE/emstrip'] diff --git a/core/wasm.build.mac.in b/core/wasm.build.mac.in index 27cb7ba68fe..90a31bf75b9 100644 --- a/core/wasm.build.mac.in +++ b/core/wasm.build.mac.in @@ -2,3 +2,4 @@ c = ['$EMSCRIPTEN_BASE/emcc'] cpp = ['$EMSCRIPTEN_BASE/em++'] ar = ['$EMSCRIPTEN_BASE/emar'] +strip = ['$EMSCRIPTEN_BASE/emstrip'] diff --git a/core/wasm.build.win.in b/core/wasm.build.win.in index c846fbc7407..948287d8b84 100644 --- a/core/wasm.build.win.in +++ b/core/wasm.build.win.in @@ -2,5 +2,6 @@ c = ['python.exe', '$EMSCRIPTEN_BASE/emcc.py'] cpp = ['python.exe', '$EMSCRIPTEN_BASE/em++.py'] ar = ['python.exe', '$EMSCRIPTEN_BASE/emar.py'] +strip = ['python.exe', '$EMSCRIPTEN_BASE/emstrip.py'] [properties] diff --git a/core/wasm.defs.build b/core/wasm.defs.build index f41af337a38..9e0859b49ba 100644 --- a/core/wasm.defs.build +++ b/core/wasm.defs.build @@ -2,6 +2,7 @@ c = ['emcc'] cpp = ['em++'] ar = ['emar'] +strip = ['emstrip'] exe_wrapper = 'node' [properties] diff --git a/developer/docs/help/reference/file-types/metadata.md b/developer/docs/help/reference/file-types/metadata.md index 8a3e50deaec..1a0d6318209 100644 --- a/developer/docs/help/reference/file-types/metadata.md +++ b/developer/docs/help/reference/file-types/metadata.md @@ -243,6 +243,12 @@ The `Keyboard` object describes an individual keyboard in the Keyman package. A The filename of the font for the OSK +`examples` + +: `Array` + + An array of [`Example`](#obj-example) objects linked to the keyboard. + ### The Language object The `Language` object describes the language that can be typed with the keyboard @@ -259,6 +265,51 @@ The `Language` object describes the language that can be typed with the keyboard [BCP 47 language code](../bcp-47) +### The Example object + +The `Example` object describes a keying sequence example text for the keyboard. +This can be the easiest way for a new user to start using a keyboard, and is +particularly helpful when the keyboard makes use of keying sequences that may +not be immediately obvious. + +`id` + +: `string` + + The BCP 47 language code for the example. + +`keys` + +: `string` + + The key sequence to type the example. The key sequence must list each key + combination, separated by space. The actual text for each key is reasonably + arbitrary, to allow you to provide examples for touch keyboards as well as + desktop keyboards. There are three special kinds of key strings: + * Modifier keys may be specified with the `+` character, e.g. `shift+e` or + `right-alt+k`. Use lower case keys. The suggested standard modifiers are: + `shift`, `ctrl`, `alt`, `left-alt`, `right-alt`, `left-ctrl`, `right-ctrl`, + and `option` (mac). + * The space key itself may be specified with `space`. + * To avoid confusion with modifier keys, the + key can be specified + with `plus`. + + For example, the key sequence x, j, m, + Shift+e, r may be specified as + `x j m shift+e r` or `x j m E r`. + +`text` + +: `string` + + The expected output when the `keys` are typed + +`note` + +: `string` + + A brief explanation of the example, e.g. "Name of language" + ### The LexicalModel object The `LexicalModel` object describes an individual model in the Keyman package. A package cannot contain both lexical models and keyboards. diff --git a/developer/docs/help/reference/kmc/cli/reference.md b/developer/docs/help/reference/kmc/cli/reference.md index 1de0b64c76d..763d5ad71f8 100644 --- a/developer/docs/help/reference/kmc/cli/reference.md +++ b/developer/docs/help/reference/kmc/cli/reference.md @@ -493,9 +493,8 @@ following sources: `author.bcp47.uniq` id pattern, or a keyboard id pattern (where period `.` is not permitted) * A GitHub repository or subfolder within a repository that matches the Keyman - keyboard/model repository layout. The branch name is optional, and will use - the default branch from the repository if omitted. For example, - `github:keyman-keyboards/khmer_angkor:main:/khmer_angkor.kpj` + keyboard/model repository layout. For example, + `https://github.com/keyman-keyboards/khmer_angkor/tree/main/khmer_angkor.kpj` `-o, --out-path ` diff --git a/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts b/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts new file mode 100644 index 00000000000..a0b0464e218 --- /dev/null +++ b/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts @@ -0,0 +1,168 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks, + CompilerError, CompilerNetAsyncCallbacks, DefaultCompilerFileSystemAsyncCallbacks, + CompilerFileSystemAsyncCallbacks } from '@keymanapp/developer-utils'; +import { fileURLToPath } from 'url'; + +const { TEST_SAVE_FIXTURES } = process.env; + +/** + * A CompilerCallbacks implementation for testing + */ +export class TestCompilerCallbacks implements CompilerCallbacks { + /* TestCompilerCallbacks */ + + messages: CompilerEvent[] = []; + readonly _net: TestCompilerNetAsyncCallbacks; + readonly _fsAsync: DefaultCompilerFileSystemAsyncCallbacks = new DefaultCompilerFileSystemAsyncCallbacks(this); + + constructor(basePath?: string) { + if(basePath) { + this._net = new TestCompilerNetAsyncCallbacks(basePath); + } + } + + clear() { + this.messages = []; + } + + printMessages() { + if(this.messages.length) { + process.stdout.write(CompilerError.formatEvent(this.messages)); + } + } + + hasMessage(code: number): boolean { + return this.messages.find((item) => item.code == code) === undefined ? false : true; + } + + fileURLToPath(url: string | URL): string { + return fileURLToPath(url); + } + + /** true of at least one error */ + hasError(): boolean { + return CompilerError.hasError(this.messages); + } + + /* CompilerCallbacks */ + + loadFile(filename: string): Uint8Array { + try { + return fs.readFileSync(filename); + } catch(e) { + if (e.code === 'ENOENT') { + return null; + } else { + throw e; + } + } + } + + fileSize(filename: string): number { + return fs.statSync(filename)?.size; + } + + isDirectory(filename: string): boolean { + return fs.statSync(filename)?.isDirectory(); + } + + get path(): CompilerPathCallbacks { + return { + ...path, + isAbsolute: path.win32.isAbsolute + }; + } + + get fs(): CompilerFileSystemCallbacks { + return fs; + } + + get net(): CompilerNetAsyncCallbacks { + return this._net; + } + + get fsAsync(): CompilerFileSystemAsyncCallbacks { + return this._fsAsync; + } + + resolveFilename(baseFilename: string, filename: string): string { + const basePath = + baseFilename.endsWith('/') || baseFilename.endsWith('\\') ? + baseFilename : + path.dirname(baseFilename); + // Transform separators to platform separators -- we are agnostic + // in our use here but path prefers files may use + // either / or \, although older kps files were always \. + if(path.sep == '/') { + filename = filename.replace(/\\/g, '/'); + } else { + filename = filename.replace(/\//g, '\\'); + } + if(!path.isAbsolute(filename)) { + filename = path.resolve(basePath, filename); + } + return filename; + } + + reportMessage(event: CompilerEvent): void { + // console.log(event.message); + this.messages.push(event); + } + + debug(msg: string) { + console.debug(msg); + } +}; + +class TestCompilerNetAsyncCallbacks implements CompilerNetAsyncCallbacks { + constructor(private basePath: string) { + } + + urlToPath(url: string): string { + const p = new URL(url); + return path.join(this.basePath, p.hostname, p.pathname.replaceAll(/[^a-z0-9_.!@#$%() -]/ig, '#')); + } + + async fetchBlob(url: string): Promise { + const p = this.urlToPath(url); + + if(TEST_SAVE_FIXTURES) { + // When TEST_SAVE_FIXTURES env variable is set, we will do the actual + // fetch from the origin so that we can build the fixtures easily + console.log(`Downloading file ${url} --> ${p}`); + let response: Response; + try { + response = await fetch(url); + } catch(e) { + console.error(`failed to download ${url}`); + console.error(e); + throw e; // yes, we want to abort the download + } + + fs.mkdirSync(path.dirname(p), {recursive: true}); + if(!response.ok) { + // We won't save a file, just delete any existing file + if(fs.existsSync(p)) { + fs.rmSync(p); + } + } else { + const data = new Uint8Array(await response.arrayBuffer()); + fs.writeFileSync(p, data); + } + } + + if(!fs.existsSync(p)) { + // missing file, this is okay + return null; + } + const data: Uint8Array = fs.readFileSync(p); + return data; + } + + async fetchJSON(url: string): Promise { + const data = await this.fetchBlob(url); + return data ? JSON.parse(new TextDecoder().decode(data)) : null; + } +} diff --git a/developer/src/common/web/test-helpers/index.ts b/developer/src/common/web/test-helpers/index.ts index ec1fcaddfd5..6970ed3cc80 100644 --- a/developer/src/common/web/test-helpers/index.ts +++ b/developer/src/common/web/test-helpers/index.ts @@ -1,164 +1,2 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerError, CompilerNetAsyncCallbacks, DefaultCompilerFileSystemAsyncCallbacks, CompilerFileSystemAsyncCallbacks } from '@keymanapp/developer-utils'; -import { fileURLToPath } from 'url'; export { verifyCompilerMessagesObject } from './verifyCompilerMessagesObject.js'; - -const { TEST_SAVE_FIXTURES } = process.env; - -class TestCompilerNetAsyncCallbacks implements CompilerNetAsyncCallbacks { - constructor(private basePath: string) { - } - - urlToPath(url: string): string { - const p = new URL(url); - return path.join(this.basePath, p.hostname, p.pathname.replaceAll(/[^a-z0-9_.!@#$%() -]/ig, '#')); - } - - async fetchBlob(url: string): Promise { - const p = this.urlToPath(url); - - if(TEST_SAVE_FIXTURES) { - // When TEST_SAVE_FIXTURES env variable is set, we will do the actual - // fetch from the origin so that we can build the fixtures easily - console.log(`Downloading file ${url} --> ${p}`); - let response: Response; - try { - response = await fetch(url); - } catch(e) { - console.error(`failed to download ${url}`); - console.error(e); - throw e; // yes, we want to abort the download - } - - fs.mkdirSync(path.dirname(p), {recursive: true}); - if(!response.ok) { - // We won't save a file, just delete any existing file - if(fs.existsSync(p)) { - fs.rmSync(p); - } - } else { - const data = new Uint8Array(await response.arrayBuffer()); - fs.writeFileSync(p, data); - } - } - - if(!fs.existsSync(p)) { - // missing file, this is okay - return null; - } - const data: Uint8Array = fs.readFileSync(p); - return data; - } - - async fetchJSON(url: string): Promise { - const data = await this.fetchBlob(url); - return data ? JSON.parse(new TextDecoder().decode(data)) : null; - } -} - -/** - * A CompilerCallbacks implementation for testing - */ -export class TestCompilerCallbacks implements CompilerCallbacks { - /* TestCompilerCallbacks */ - - messages: CompilerEvent[] = []; - readonly _net: TestCompilerNetAsyncCallbacks; - readonly _fsAsync: DefaultCompilerFileSystemAsyncCallbacks = new DefaultCompilerFileSystemAsyncCallbacks(this); - - constructor(basePath?: string) { - if(basePath) { - this._net = new TestCompilerNetAsyncCallbacks(basePath); - } - } - - clear() { - this.messages = []; - } - - printMessages() { - if(this.messages.length) { - process.stdout.write(CompilerError.formatEvent(this.messages)); - } - } - - hasMessage(code: number): boolean { - return this.messages.find((item) => item.code == code) === undefined ? false : true; - } - - fileURLToPath(url: string | URL): string { - return fileURLToPath(url); - } - - /** true of at least one error */ - hasError(): boolean { - return CompilerError.hasError(this.messages); - } - - /* CompilerCallbacks */ - - loadFile(filename: string): Uint8Array { - try { - return fs.readFileSync(filename); - } catch(e) { - if (e.code === 'ENOENT') { - return null; - } else { - throw e; - } - } - } - - fileSize(filename: string): number { - return fs.statSync(filename)?.size; - } - - isDirectory(filename: string): boolean { - return fs.statSync(filename)?.isDirectory(); - } - - get path(): CompilerPathCallbacks { - return path; - } - - get fs(): CompilerFileSystemCallbacks { - return fs; - } - - get net(): CompilerNetAsyncCallbacks { - return this._net; - } - - get fsAsync(): CompilerFileSystemAsyncCallbacks { - return this._fsAsync; - } - - resolveFilename(baseFilename: string, filename: string): string { - const basePath = - baseFilename.endsWith('/') || baseFilename.endsWith('\\') ? - baseFilename : - path.dirname(baseFilename); - // Transform separators to platform separators -- we are agnostic - // in our use here but path prefers files may use - // either / or \, although older kps files were always \. - if(path.sep == '/') { - filename = filename.replace(/\\/g, '/'); - } else { - filename = filename.replace(/\//g, '\\'); - } - if(!path.isAbsolute(filename)) { - filename = path.resolve(basePath, filename); - } - return filename; - } - - reportMessage(event: CompilerEvent): void { - // console.log(event.message); - this.messages.push(event); - } - - debug(msg: string) { - console.debug(msg); - } -}; +export { TestCompilerCallbacks } from './TestCompilerCallbacks.js'; diff --git a/developer/src/common/web/utils/src/cloud-urls.ts b/developer/src/common/web/utils/src/cloud-urls.ts new file mode 100644 index 00000000000..33f9c669b9b --- /dev/null +++ b/developer/src/common/web/utils/src/cloud-urls.ts @@ -0,0 +1,18 @@ +/** + * Matches a Keyman keyboard resource, based on the permanent home page for the + * keyboard on keyman.com, `https://keyman.com/keyboards/` + */ +export const KEYMANCOM_CLOUD_URI = /^(?:http(?:s)?:\/\/)?keyman\.com\/keyboards\/(?[a-z0-9_.-]+)/i; + +/** + * Matches a `cloud:` URI for a Keyman resource (e.g. keyboard or lexical + * model) + */ +export const CLOUD_URI = /^cloud:(?.+)$/i; + + +export interface CloudUriRegexMatchArray extends RegExpMatchArray { + groups?: { + id?: string; + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/src/common-messages.ts b/developer/src/common/web/utils/src/common-messages.ts index 724dadf9cd8..6eca08d37c4 100644 --- a/developer/src/common/web/utils/src/common-messages.ts +++ b/developer/src/common/web/utils/src/common-messages.ts @@ -20,17 +20,19 @@ export class CommonTypesMessages { static ERROR_ImportInvalidBase = SevError | 0x0002; static Error_ImportInvalidBase = (o: { base: string, path: string, subtag: string }) => m(this.ERROR_ImportInvalidBase, - `Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} is supported.`); + `Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} or empty (for local) are supported.`); static ERROR_ImportInvalidPath = SevError | 0x0003; static Error_ImportInvalidPath = (o: { base: string, path: string, subtag: string }) => m(this.ERROR_ImportInvalidPath, - `Import element with invalid path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml`); + `Import element with invalid path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml'`); static ERROR_ImportReadFail = SevError | 0x0004; static Error_ImportReadFail = (o: { base: string, path: string, subtag: string }) => m(this.ERROR_ImportReadFail, - `Import could not read data with path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml'`); + `Import could not read data with path ${def(o.path)}`, + // for CLDR, give guidance on the suggested path + (o.base === constants.cldr_import_base) ? `expected the form '${constants.cldr_version_latest}/*.xml' for ${o.base}` : undefined); static ERROR_ImportWrongRoot = SevError | 0x0005; static Error_ImportWrongRoot = (o: { base: string, path: string, subtag: string }) => diff --git a/developer/src/common/web/utils/src/github-urls.ts b/developer/src/common/web/utils/src/github-urls.ts new file mode 100644 index 00000000000..1a65106bf1f --- /dev/null +++ b/developer/src/common/web/utils/src/github-urls.ts @@ -0,0 +1,35 @@ +/** + * Matches only a GitHub permanent raw URI with a commit hash, without any other + * components; note hash is called branch to match other URI formats + */ +export const GITHUB_STABLE_SOURCE = /^https:\/\/github\.com\/(?[a-zA-Z0-9-]+)\/(?[\w\.-]+)\/raw\/(?[a-f0-9]{40})\/(?.+)$/; + +/** + * Matches any GitHub git resource raw 'user content' URI which can be + * translated to a permanent URI with a commit hash + */ +export const GITHUB_RAW_URI = /^https:\/\/raw\.githubusercontent\.com\/(?[a-zA-Z0-9-]+)\/(?[\w\.-]+)\/(?:refs\/(?:heads|tags)\/)?(?[^/]+)\/(?.+)$/; + +/** + * Matches any GitHub git resource raw URI which can be translated to a + * permanent URI with a commit hash + */ +export const GITHUB_URI = /^https:\/\/github\.com\/(?[a-zA-Z0-9-]+)\/(?[\w\.-]+)\/(?:raw|blob|tree)\/(?:refs\/(?:heads|tags)\/)?(?[^/]+)\/(?.+)$/; + +/** + * Matches any GitHub git resource raw URI which can be translated to a + * permanent URI with a commit hash, with the http[s] protocol optional, for + * matching user-supplied URLs. groups are: `owner`, `repo`, `branch`, and + * `path`. + */ +export const GITHUB_URI_OPTIONAL_PROTOCOL = /^(?:http(?:s)?:\/\/)?github\.com\/(?[a-zA-Z0-9-]+)\/(?[\w\.-]+)(?:\/(?:(?:raw|blob|tree)\/(?:refs\/(?:heads|tags)\/)?(?[^/]+)\/(?.*))?)?$/; + + +export interface GitHubRegexMatchArray extends RegExpMatchArray { + groups?: { + owner?: string; + repo?: string; + branch?: string; + path?: string; + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index 5b4c9c0e91a..290efd5fc70 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -66,3 +66,6 @@ export { UrlSubpathCompilerCallback } from './utils/UrlSubpathCompilerCallback.j export { CommonTypesMessages } from './common-messages.js'; export * as SourceFilenamePatterns from './source-filename-patterns.js'; export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader } from './xml-utils.js'; + +export * as GitHubUrls from './github-urls.js'; +export * as CloudUrls from './cloud-urls.js'; \ No newline at end of file diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts index 41d4c699f40..928bb94bef7 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -19,7 +19,10 @@ interface NameAndProps { }; export class LDMLKeyboardXMLSourceFileReaderOptions { - importsPath: string; + /** path to the CLDR imports */ + cldrImportsPath: string; + /** ordered list of paths for local imports */ + localImportsPaths: string[]; }; export class LDMLKeyboardXMLSourceFileReader { @@ -31,10 +34,21 @@ export class LDMLKeyboardXMLSourceFileReader { } readImportFile(version: string, subpath: string): Uint8Array { - const importPath = this.callbacks.resolveFilename(this.options.importsPath, `${version}/${subpath}`); + const importPath = this.callbacks.resolveFilename(this.options.cldrImportsPath, `${version}/${subpath}`); return this.callbacks.loadFile(importPath); } + readLocalImportFile(path: string): Uint8Array { + // try each of the local imports paths + for (const localPath of this.options.localImportsPaths) { + const importPath = this.callbacks.path.join(localPath, path); + if(this.callbacks.fs.existsSync(importPath)) { + return this.callbacks.loadFile(importPath); + } + } + return null; // was not able to load from any of the paths + } + /** * xml2js will not place single-entry objects into arrays. * Easiest way to fix this is to box them ourselves as needed @@ -203,16 +217,25 @@ export class LDMLKeyboardXMLSourceFileReader { */ private resolveOneImport(obj: any, subtag: string, asImport: LKImport, implied? : boolean) : boolean { const { base, path } = asImport; - if (base !== constants.cldr_import_base) { + // If base is not an empty string (or null/undefined), then it must be 'cldr' + if (base && base !== constants.cldr_import_base) { this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidBase({base, path, subtag})); return false; } - const paths = path.split('/'); - if (paths[0] == '' || paths[1] == '' || paths.length !== 2) { - this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidPath({base, path, subtag})); - return false; + let importData: Uint8Array; + + if (base === constants.cldr_import_base) { + // CLDR import + const paths = path.split('/'); + if (paths[0] == '' || paths[1] == '' || paths.length !== 2) { + this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidPath({base, path, subtag})); + return false; + } + importData = this.readImportFile(paths[0], paths[1]); + } else { + // local import + importData = this.readLocalImportFile(path); } - const importData: Uint8Array = this.readImportFile(paths[0], paths[1]); if (!importData || !importData.length) { this.callbacks.reportMessage(CommonTypesMessages.Error_ImportReadFail({base, path, subtag})); return false; @@ -241,6 +264,9 @@ export class LDMLKeyboardXMLSourceFileReader { // mark all children as an implied import subsubval.forEach(o => o[ImportStatus.impliedImport] = basePath); } + if (base !== constants.cldr_import_base) { + subsubval.forEach(o => o[ImportStatus.localImport] = path); + } if (!obj[subsubtag]) { obj[subsubtag] = []; // start with empty array diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts index 696194573f8..587b02dd719 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts @@ -38,7 +38,7 @@ export interface LKImport { /** * import base, currently `cldr` is supported */ - base: string; + base?: 'cldr' | ''; /** * path to imported resource, of the form `45/*.xml` */ @@ -199,6 +199,8 @@ export class ImportStatus { static impliedImport = Symbol('LDML implied import'); /** item came in via import */ static import = Symbol('LDML import'); + /** item came in via local (not CLDR) import */ + static localImport = Symbol('LDML local import'); /** @returns true if the object was loaded through an implied import */ static isImpliedImport(o : any) : boolean { @@ -208,5 +210,9 @@ export class ImportStatus { static isImport(o : any) : boolean { return o && !!o[ImportStatus.import]; } + /** @returns true if the object was loaded through an explicit import */ + static isLocalImport(o : any) : boolean { + return o && !!o[ImportStatus.localImport]; + } }; diff --git a/developer/src/common/web/utils/test/TestCompilerCallbacks.ts b/developer/src/common/web/utils/test/TestCompilerCallbacks.ts deleted file mode 100644 index 5b36c63fe3e..00000000000 --- a/developer/src/common/web/utils/test/TestCompilerCallbacks.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { loadFile, resolveFilename } from './helpers/index.js'; -import { CompilerCallbacks, CompilerFileSystemAsyncCallbacks, CompilerFileSystemCallbacks, CompilerNetAsyncCallbacks, CompilerPathCallbacks } from '../src/compiler-callbacks.js'; -import { CompilerError, CompilerEvent } from '../src/compiler-interfaces.js'; -import { fileURLToPath } from 'url'; - -// This is related to developer/src/common/web/test-helpers/index.ts but has a slightly different API surface -// as this runs at a lower level than the compiler. -/** - * A CompilerCallbacks implementation for testing - */ -export class TestCompilerCallbacks implements CompilerCallbacks { - clear() { - this.messages = []; - } - debug(msg: string): void { - console.debug(msg); - } - - printMessages() { - process.stdout.write(CompilerError.formatEvent(this.messages)); - } - - messages: CompilerEvent[] = []; - - get path(): CompilerPathCallbacks { - return path; - } - - get fs(): CompilerFileSystemCallbacks { - return fs; - } - - get fsAsync(): CompilerFileSystemAsyncCallbacks { - return null; // Note: not currently used - } - - get net(): CompilerNetAsyncCallbacks { - return null; // Note: not currently used - } - - resolveFilename(baseFilename: string, filename: string): string { - return resolveFilename(baseFilename, filename); - } - - fileURLToPath(url: string | URL): string { - return fileURLToPath(url); - } - - loadFile(filename: string): Uint8Array { - // TODO: error management, does it belong here? - try { - return loadFile(filename); - } catch (e) { - if (e.code === 'ENOENT') { - return null; - } else { - throw e; - } - } - } - - fileSize(filename: string): number { - return fs.statSync(filename).size; - } - - isDirectory(filename: string): boolean { - return fs.statSync(filename)?.isDirectory(); - } - - reportMessage(event: CompilerEvent): void { - // console.log(event.message); - this.messages.push(event); - } -} diff --git a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-local.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-local.xml new file mode 100644 index 00000000000..b3cc7cabdff --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-local.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-local.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-local.xml new file mode 100644 index 00000000000..e8ac9dc9640 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-local.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/keys-Zyyy-morepunctuation.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/keys-Zyyy-morepunctuation.xml new file mode 100644 index 00000000000..768e81e7cd2 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/keys-Zyyy-morepunctuation.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/developer/src/common/web/utils/test/helpers/reader-callback-test.ts b/developer/src/common/web/utils/test/helpers/reader-callback-test.ts index c67d49dfdb4..d865ab2f46a 100644 --- a/developer/src/common/web/utils/test/helpers/reader-callback-test.ts +++ b/developer/src/common/web/utils/test/helpers/reader-callback-test.ts @@ -7,9 +7,11 @@ import { LDMLKeyboardXMLSourceFile } from '../../src/types/ldml-keyboard/ldml-ke import { LDMLKeyboardTestDataXMLSourceFile } from '../../src/types/ldml-keyboard/ldml-keyboard-testdata-xml.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { fileURLToPath } from 'url'; +import { dirname } from 'node:path'; const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) + cldrImportsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [], }; export interface CompilationCase { @@ -76,18 +78,29 @@ export interface TestDataCase { export function testReaderCases(cases : CompilationCase[]) { // we need our own callbacks rather than using the global so messages don't get mixed const callbacks = new TestCompilerCallbacks(); - const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); for (const testcase of cases) { const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`: `should load: ${testcase.subpath}`; it(testHeading, function () { callbacks.clear(); - - const data = loadFile(makePathToFixture('ldml-keyboard', testcase.subpath)); + const path = makePathToFixture('ldml-keyboard', testcase.subpath); + // update readerOptions to point to the source dir. + readerOptions.localImportsPaths = [ dirname(path) ]; + const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); + const data = loadFile(path); assert.ok(data, `reading ${testcase.subpath}`); const source = reader.load(data); if (!testcase.loadfail) { + if (!source) { + // print any loading errs here + if (testcase.warnings) { + assert.includeDeepMembers(callbacks.messages, testcase.warnings, 'expected warnings to be included'); + } else if (!expectFailure) { + // no warnings, so expect zero messages + assert.deepEqual(callbacks.messages, [], 'expected zero messages'); + } + } assert.ok(source, `loading ${testcase.subpath}`); } else { assert.notOk(source, `loading ${testcase.subpath} (expected failure)`); diff --git a/developer/src/common/web/utils/test/kmx/ldml-keyboard-xml-reader.tests.ts b/developer/src/common/web/utils/test/kmx/ldml-keyboard-xml-reader.tests.ts index 17c35660245..e3e6fc1e9fb 100644 --- a/developer/src/common/web/utils/test/kmx/ldml-keyboard-xml-reader.tests.ts +++ b/developer/src/common/web/utils/test/kmx/ldml-keyboard-xml-reader.tests.ts @@ -119,11 +119,33 @@ describe('ldml keyboard xml reader tests', function () { // 'hash' is an import but not implied assert.isFalse(ImportStatus.isImpliedImport(k.find(({id}) => id === 'hash'))); assert.isTrue(ImportStatus.isImport(k.find(({id}) => id === 'hash'))); + assert.isFalse(ImportStatus.isLocalImport(k.find(({id}) => id === 'hash'))); // 'zz' is not imported assert.isFalse(ImportStatus.isImpliedImport(k.find(({id}) => id === 'zz'))); assert.isFalse(ImportStatus.isImport(k.find(({id}) => id === 'zz'))); }, }, + { + subpath: 'import-local.xml', + callback: (data, source, subpath, callbacks) => { + assert.ok(source?.keyboard3?.keys); + const k = pluckKeysFromKeybag(source?.keyboard3?.keys.key, ['interrobang','snail']); + assert.sameDeepOrderedMembers(k.map((entry) => { + // Drop the Symbol members from the returned keys; assertions may expect their presence. + return { + id: entry.id, + output: entry.output + }; + }), [ + { id: 'interrobang', output: '‽' }, + { id: 'snail', output: '@' }, + ]); + // all of the keys are implied imports here + assert.isFalse(ImportStatus.isImpliedImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail'))); + assert.isTrue(ImportStatus.isImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail'))); + assert.isTrue(ImportStatus.isLocalImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail'))); + }, + }, { subpath: 'invalid-import-base.xml', loadfail: true, @@ -135,12 +157,23 @@ describe('ldml keyboard xml reader tests', function () { }), ], }, + { + subpath: 'invalid-import-local.xml', + loadfail: true, + errors: [ + CommonTypesMessages.Error_ImportReadFail({ + base: undefined, + path: 'keys-Zyyy-DOESNOTEXIST.xml', + subtag: 'keys' + }), + ], + }, { subpath: 'invalid-import-path.xml', loadfail: true, errors: [ CommonTypesMessages.Error_ImportInvalidPath({ - base: null, + base: 'cldr', path: '45/too/many/slashes/leading/to/nothing-Zxxx-does-not-exist.xml', subtag: null, }), @@ -151,7 +184,7 @@ describe('ldml keyboard xml reader tests', function () { loadfail: true, errors: [ CommonTypesMessages.Error_ImportReadFail({ - base: null, + base: 'cldr', path: '45/none-Zxxx-does-not-exist.xml', subtag: null, }), diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index 7502e02bb91..95551a45a76 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -6,6 +6,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE source "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +source "$KEYMAN_ROOT/resources/build/jq.inc.sh" builder_describe "Installation files for Keyman Developer" \ @/common/windows/data \ @@ -51,6 +52,7 @@ function do_clean() { function do_publish() { verify-program-signatures + verify-node-installer-version "$KEYMAN_ROOT/common/windows/cef-checkout.sh" @@ -81,6 +83,10 @@ function do_publish() { verify-installer-signatures } +function do_test() { + verify-node-installer-version +} + function copy-kmdev() { builder_heading copy-kmdev @@ -105,6 +111,20 @@ function verify-installer-signatures() { verify-all-executable-signatures-in-folder "$DEVELOPER_ROOT/release/${VERSION}" } +function verify-node-installer-version() { + builder_heading verify-node-installer-version + + local REQUIRED_NODE_VERSION=$("$JQ" -r .engines.node < "$KEYMAN_ROOT/package.json") + local INSTALLER_NODE_VERSION="$("$THIS_SCRIPT_PATH/node/dist/node.exe" --version)" + INSTALLER_NODE_VERSION="${INSTALLER_NODE_VERSION##v}" + + if [[ "$REQUIRED_NODE_VERSION" != "$INSTALLER_NODE_VERSION" ]]; then + builder_echo error "Installer node version in /developer/src/inst/node/dist/node.exe is '$INSTALLER_NODE_VERSION'," + builder_echo error "but the expected version per /package.json is '$REQUIRED_NODE_VERSION'." + builder_die "Version mismatch for bundled node.exe version" + fi +} + function test-releaseexists() { if [[ -d "$DEVELOPER_ROOT/release/${VERSION}" ]]; then builder_die "Release ${VERSION} already exists. Delete it or update VERSION.md and try again" @@ -264,5 +284,5 @@ function copy-schemas() { builder_run_action clean do_clean # builder_run_action configure do_configure # builder_run_action build do_build -# builder_run_action test do_test +builder_run_action test do_test builder_run_action publish do_publish diff --git a/developer/src/inst/node/dist/CHANGELOG.md b/developer/src/inst/node/dist/CHANGELOG.md index f9593cb605a..e50fe779a66 100644 --- a/developer/src/inst/node/dist/CHANGELOG.md +++ b/developer/src/inst/node/dist/CHANGELOG.md @@ -2,11 +2,13 @@ Select a Node.js version below to view the changelog history: -* [Node.js 18](doc/changelogs/CHANGELOG_V18.md) **Long Term Support** +* [Node.js 20](doc/changelogs/CHANGELOG_V20.md) **Long Term Support** +* [Node.js 19](doc/changelogs/CHANGELOG_V19.md) End-of-Life +* [Node.js 18](doc/changelogs/CHANGELOG_V18.md) Long Term Support * [Node.js 17](doc/changelogs/CHANGELOG_V17.md) End-of-Life -* [Node.js 16](doc/changelogs/CHANGELOG_V16.md) Long Term Support +* [Node.js 16](doc/changelogs/CHANGELOG_V16.md) End-of-Life * [Node.js 15](doc/changelogs/CHANGELOG_V15.md) End-of-Life -* [Node.js 14](doc/changelogs/CHANGELOG_V14.md) Long Term Support +* [Node.js 14](doc/changelogs/CHANGELOG_V14.md) End-of-Life * [Node.js 13](doc/changelogs/CHANGELOG_V13.md) End-of-Life * [Node.js 12](doc/changelogs/CHANGELOG_V12.md) End-of-Life * [Node.js 11](doc/changelogs/CHANGELOG_V11.md) End-of-Life @@ -27,13 +29,43 @@ release. + - + -
20 (LTS) 18 (LTS) 16 (LTS)14 (LTS)
-18.14.1
+20.16.0
+20.15.1
+20.15.0
+20.14.0
+20.13.1
+20.13.0
+20.12.2
+20.12.1
+20.12.0
+20.11.1
+20.11.0
+20.10.0
+20.9.0
+20.8.1
+20.8.0
+20.7.0
+20.6.1
+20.6.0
+20.5.1
+20.5.0
+20.4.0
+20.3.1
+20.3.0
+20.2.0
+20.1.0
+20.0.0
+
+18.15.0
+18.14.2
+18.14.1
18.14.0
18.13.0
18.12.1
@@ -53,7 +85,17 @@ release. 18.0.0
-16.14.2
+16.20.0
+16.19.1
+16.19.0
+16.18.1
+16.18.0
+16.17.1
+16.17.0
+16.16.0
+16.15.1
+16.15.0
+16.14.2
16.14.1
16.14.0
16.13.2
@@ -79,46 +121,6 @@ release. 16.1.0
16.0.0
-14.19.1
-14.19.0
-14.18.3
-14.18.2
-14.18.1
-14.18.0
-14.17.6
-14.17.5
-14.17.4
-14.17.3
-14.17.2
-14.17.1
-14.17.0
-14.16.1
-14.16.0
-14.15.5
-14.15.4
-14.15.3
-14.15.2
-14.15.1
-14.15.0
-14.14.0
-14.13.1
-14.13.0
-14.12.0
-14.11.0
-14.10.1
-14.10.0
-14.9.0
-14.8.0
-14.7.0
-14.6.0
-14.5.0
-14.4.0
-14.3.0
-14.2.0
-14.1.0
-14.0.0
-
diff --git a/developer/src/inst/node/dist/LICENSE b/developer/src/inst/node/dist/LICENSE index f8fa687202d..6b3d1a613b9 100644 --- a/developer/src/inst/node/dist/LICENSE +++ b/developer/src/inst/node/dist/LICENSE @@ -78,19 +78,30 @@ The externally maintained libraries used by Node.js are: - c-ares, located at deps/cares, is licensed as follows: """ - Copyright (c) 2007 - 2018, Daniel Stenberg with many contributors, see AUTHORS + MIT License + + Copyright (c) 1998 Massachusetts Institute of Technology + Copyright (c) 2007 - 2023 Daniel Stenberg with many contributors, see AUTHORS file. - Copyright 1998 by the Massachusetts Institute of Technology. + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. - Permission to use, copy, modify, and distribute this software and its - documentation for any purpose and without fee is hereby granted, provided that - the above copyright notice appear in all copies and that both that copyright - notice and this permission notice appear in supporting documentation, and that - the name of M.I.T. not be used in advertising or publicity pertaining to - distribution of the software without specific, written prior permission. - M.I.T. makes no representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied warranty. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. """ - cjs-module-lexer, located at deps/cjs-module-lexer, is licensed as follows: @@ -121,52 +132,47 @@ The externally maintained libraries used by Node.js are: - ICU, located at deps/icu-small, is licensed as follows: """ - UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE - - See Terms of Use - for definitions of Unicode Inc.’s Data Files and Software. - - NOTICE TO USER: Carefully read the following legal agreement. - BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S - DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), - YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE - TERMS AND CONDITIONS OF THIS AGREEMENT. - IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE - THE DATA FILES OR SOFTWARE. + UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE - Copyright © 1991-2022 Unicode, Inc. All rights reserved. - Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + Copyright © 2016-2024 Unicode, Inc. - Permission is hereby granted, free of charge, to any person obtaining - a copy of the Unicode data files and any associated documentation - (the "Data Files") or Unicode software and any associated documentation - (the "Software") to deal in the Data Files or Software - without restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, and/or sell copies of - the Data Files or Software, and to permit persons to whom the Data Files - or Software are furnished to do so, provided that either - (a) this copyright and permission notice appear with all copies - of the Data Files or Software, or - (b) this copyright and permission notice appear in associated - Documentation. - - THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF - ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT OF THIRD PARTY RIGHTS. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS - NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL - DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THE DATA FILES OR SOFTWARE. + NOTICE TO USER: Carefully read the following legal agreement. BY + DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR + SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE + TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT + DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. - Except as contained in this notice, the name of a copyright holder - shall not be used in advertising or otherwise to promote the sale, - use or other dealings in these Data Files or Software without prior - written authorization of the copyright holder. + Permission is hereby granted, free of charge, to any person obtaining a + copy of data files and any associated documentation (the "Data Files") or + software and any associated documentation (the "Software") to deal in the + Data Files or Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, and/or sell + copies of the Data Files or Software, and to permit persons to whom the + Data Files or Software are furnished to do so, provided that either (a) + this copyright and permission notice appear with all copies of the Data + Files or Software, or (b) this copyright and permission notice appear in + associated Documentation. + + THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + THIRD PARTY RIGHTS. + + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE + BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, + OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA + FILES OR SOFTWARE. + + Except as contained in this notice, the name of a copyright holder shall + not be used in advertising or otherwise to promote the sale, use or other + dealings in these Data Files or Software without prior written + authorization of the copyright holder. + + SPDX-License-Identifier: Unicode-3.0 ---------------------------------------------------------------------- @@ -639,9 +645,6 @@ The externally maintained libraries used by Node.js are: - libuv, located at deps/uv, is licensed as follows: """ - libuv is licensed for use as follows: - - ==== Copyright (c) 2015-present libuv project contributors. Permission is hereby granted, free of charge, to any person obtaining a copy @@ -661,8 +664,6 @@ The externally maintained libraries used by Node.js are: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ==== - This license applies to parts of libuv originating from the https://github.com/joyent/libuv repository: @@ -699,12 +700,6 @@ The externally maintained libraries used by Node.js are: - inet_pton and inet_ntop implementations, contained in src/inet.c, are copyright the Internet Systems Consortium, Inc., and licensed under the ISC license. - - - stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three - clause BSD license. - - - pthread-fixes.c, copyright Google Inc. and Sony Mobile Communications AB. - Three clause BSD license. """ - llhttp, located at deps/llhttp, is licensed as follows: @@ -1292,9 +1287,9 @@ The externally maintained libraries used by Node.js are: - zlib, located at deps/zlib, is licensed as follows: """ zlib.h -- interface of the 'zlib' general purpose compression library - version 1.2.13, October 13th, 2022 + version 1.3.0.1, August xxth, 2023 - Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler + Copyright (C) 1995-2023 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -1338,6 +1333,47 @@ The externally maintained libraries used by Node.js are: CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +- ada, located at deps/ada, is licensed as follows: + """ + Copyright 2023 Yagiz Nizipli and Daniel Lemire + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + """ + +- minimatch, located at deps/minimatch, is licensed as follows: + """ + The ISC License + + Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + """ + - npm, located at deps/npm, is licensed as follows: """ The npm application @@ -1733,6 +1769,35 @@ The externally maintained libraries used by Node.js are: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ +- gypi_to_gn.py, located at tools/gypi_to_gn.py, is licensed as follows: + """ + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + """ + - ESLint, located at tools/node_modules/eslint, is licensed as follows: """ Copyright OpenJS Foundation and other contributors, @@ -2112,9 +2177,9 @@ The externally maintained libraries used by Node.js are: - base64, located at deps/base64/base64/, is licensed as follows: """ Copyright (c) 2005-2007, Nick Galbreath - Copyright (c) 2013-2019, Alfred Klomp - Copyright (c) 2015-2017, Wojciech Mula + Copyright (c) 2015-2018, Wojciech Muła Copyright (c) 2016-2017, Matthieu Darbois + Copyright (c) 2013-2022, Alfred Klomp All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/developer/src/inst/node/dist/README.md b/developer/src/inst/node/dist/README.md index fb3f183934d..601a4e06bb8 100644 --- a/developer/src/inst/node/dist/README.md +++ b/developer/src/inst/node/dist/README.md @@ -43,7 +43,7 @@ Looking for help? Check out the * **Current**: Under active development. Code for the Current release is in the branch for its major version number (for example, - [v15.x](https://github.com/nodejs/node/tree/v15.x)). Node.js releases a new + [v19.x](https://github.com/nodejs/node/tree/v19.x)). Node.js releases a new major version every 6 months, allowing for breaking changes. This happens in April and October every year. Releases appearing each October have a support life of 8 months. Releases appearing each April convert to LTS (see below) @@ -74,8 +74,8 @@ Binaries, installers, and source tarballs are available at The [latest](https://nodejs.org/download/release/latest/) directory is an alias for the latest Current release. The latest-_codename_ directory is an alias for the latest release from an LTS line. For example, the -[latest-fermium](https://nodejs.org/download/release/latest-fermium/) directory -contains the latest Fermium (Node.js 14) release. +[latest-hydrogen](https://nodejs.org/download/release/latest-hydrogen/) +directory contains the latest Hydrogen (Node.js 18) release. #### Nightly releases @@ -98,15 +98,15 @@ files. To download `SHASUMS256.txt` using `curl`: -```console -$ curl -O https://nodejs.org/dist/vx.y.z/SHASUMS256.txt +```bash +curl -O https://nodejs.org/dist/vx.y.z/SHASUMS256.txt ``` To check that a downloaded file matches the checksum, run it through `sha256sum` with a command such as: -```console -$ grep node-vx.y.z.tar.gz SHASUMS256.txt | sha256sum -c - +```bash +grep node-vx.y.z.tar.gz SHASUMS256.txt | sha256sum -c - ``` For Current and LTS, the GPG detached signature of `SHASUMS256.txt` is in @@ -115,16 +115,16 @@ For Current and LTS, the GPG detached signature of `SHASUMS256.txt` is in [the GPG keys of individuals authorized to create releases](#release-keys). To import the keys: -```console -$ gpg --keyserver hkps://keys.openpgp.org --recv-keys 4ED778F539E3634C779C87C6D7062848A1AB005C +```bash +gpg --keyserver hkps://keys.openpgp.org --recv-keys 4ED778F539E3634C779C87C6D7062848A1AB005C ``` See [Release keys](#release-keys) for a script to import active release keys. Next, download the `SHASUMS256.txt.sig` for the release: -```console -$ curl -O https://nodejs.org/dist/vx.y.z/SHASUMS256.txt.sig +```bash +curl -O https://nodejs.org/dist/vx.y.z/SHASUMS256.txt.sig ``` Then use `gpg --verify SHASUMS256.txt.sig SHASUMS256.txt` to verify @@ -158,24 +158,18 @@ For information about the governance of the Node.js project, see ### TSC (Technical Steering Committee) +#### TSC voting members + * [aduh95](https://github.com/aduh95) - **Antoine du Hamel** <> (he/him) * [apapirovski](https://github.com/apapirovski) - **Anatoli Papirovski** <> (he/him) -* [BethGriggs](https://github.com/BethGriggs) - - **Beth Griggs** <> (she/her) +* [benjamingr](https://github.com/benjamingr) - + **Benjamin Gruenbaum** <> * [BridgeAR](https://github.com/BridgeAR) - **Ruben Bridgewater** <> (he/him) -* [ChALkeR](https://github.com/ChALkeR) - - **Сковорода Никита Андреевич** <> (he/him) -* [cjihrig](https://github.com/cjihrig) - - **Colin Ihrig** <> (he/him) -* [danielleadams](https://github.com/danielleadams) - - **Danielle Adams** <> (she/her) -* [fhinkel](https://github.com/fhinkel) - - **Franziska Hinkelmann** <> (she/her) * [GeoffreyBooth](https://github.com/geoffreybooth) - **Geoffrey Booth** <> (he/him) * [gireeshpunathil](https://github.com/gireeshpunathil) - @@ -186,43 +180,66 @@ For information about the governance of the Node.js project, see **Joyee Cheung** <> (she/her) * [legendecas](https://github.com/legendecas) - **Chengzhong Wu** <> (he/him) +* [marco-ippolito](https://github.com/marco-ippolito) - + **Marco Ippolito** <> (he/him) * [mcollina](https://github.com/mcollina) - **Matteo Collina** <> (he/him) * [mhdawson](https://github.com/mhdawson) - **Michael Dawson** <> (he/him) +* [MoLow](https://github.com/MoLow) - + **Moshe Atlow** <> (he/him) * [RafaelGSS](https://github.com/RafaelGSS) - **Rafael Gonzaga** <> (he/him) -* [RaisinTen](https://github.com/RaisinTen) - - **Darshan Sen** <> (he/him) * [richardlau](https://github.com/richardlau) - **Richard Lau** <> * [ronag](https://github.com/ronag) - **Robert Nagy** <> +* [ruyadorno](https://github.com/ruyadorno) - + **Ruy Adorno** <> (he/him) +* [ShogunPanda](https://github.com/ShogunPanda) - + **Paolo Insogna** <> (he/him) * [targos](https://github.com/targos) - **Michaël Zasso** <> (he/him) * [tniessen](https://github.com/tniessen) - **Tobias Nießen** <> (he/him) + +#### TSC regular members + +* [anonrig](https://github.com/anonrig) - + **Yagiz Nizipli** <> (he/him) +* [BethGriggs](https://github.com/BethGriggs) - + **Beth Griggs** <> (she/her) +* [bnoordhuis](https://github.com/bnoordhuis) - + **Ben Noordhuis** <> +* [cjihrig](https://github.com/cjihrig) - + **Colin Ihrig** <> (he/him) +* [codebytere](https://github.com/codebytere) - + **Shelley Vohr** <> (she/her) +* [danielleadams](https://github.com/danielleadams) - + **Danielle Adams** <> (she/her) +* [MylesBorins](https://github.com/MylesBorins) - + **Myles Borins** <> (he/him) * [Trott](https://github.com/Trott) - **Rich Trott** <> (he/him)
-Emeriti +TSC emeriti members -### TSC emeriti +#### TSC emeriti members * [addaleax](https://github.com/addaleax) - **Anna Henningsen** <> (she/her) -* [bnoordhuis](https://github.com/bnoordhuis) - - **Ben Noordhuis** <> +* [ChALkeR](https://github.com/ChALkeR) - + **Сковорода Никита Андреевич** <> (he/him) * [chrisdickinson](https://github.com/chrisdickinson) - **Chris Dickinson** <> -* [codebytere](https://github.com/codebytere) - - **Shelley Vohr** <> (she/her) * [danbev](https://github.com/danbev) - **Daniel Bevenius** <> (he/him) * [evanlucas](https://github.com/evanlucas) - **Evan Lucas** <> (he/him) +* [fhinkel](https://github.com/fhinkel) - + **Franziska Hinkelmann** <> (she/her) * [Fishrock123](https://github.com/Fishrock123) - **Jeremiah Senkpiel** <> (he/they) * [gabrielschulhof](https://github.com/gabrielschulhof) - @@ -239,8 +256,6 @@ For information about the governance of the Node.js project, see **Mary Marchini** <> (she/her) * [mscdex](https://github.com/mscdex) - **Brian White** <> -* [MylesBorins](https://github.com/MylesBorins) - - **Myles Borins** <> (he/him) * [nebrius](https://github.com/nebrius) - **Bryan Hughes** <> * [ofrobots](https://github.com/ofrobots) - @@ -249,6 +264,8 @@ For information about the governance of the Node.js project, see **Alexis Campailla** <> * [piscisaureus](https://github.com/piscisaureus) - **Bert Belder** <> +* [RaisinTen](https://github.com/RaisinTen) - + **Darshan Sen** <> (he/him) * [rvagg](https://github.com/rvagg) - **Rod Vagg** <> * [sam-github](https://github.com/sam-github) - @@ -275,35 +292,27 @@ For information about the governance of the Node.js project, see * [aduh95](https://github.com/aduh95) - **Antoine du Hamel** <> (he/him) * [anonrig](https://github.com/anonrig) - - **Yagiz Nizipli** <> (he/him) -* [antsmartian](https://github.com/antsmartian) - - **Anto Aravinth** <> (he/him) + **Yagiz Nizipli** <> (he/him) * [apapirovski](https://github.com/apapirovski) - **Anatoli Papirovski** <> (he/him) -* [AshCripps](https://github.com/AshCripps) - - **Ash Cripps** <> +* [atlowChemi](https://github.com/atlowChemi) - + **Chemi Atlow** <> (he/him) * [Ayase-252](https://github.com/Ayase-252) - **Qingyu Deng** <> -* [bcoe](https://github.com/bcoe) - - **Ben Coe** <> (he/him) * [bengl](https://github.com/bengl) - **Bryan English** <> (he/him) * [benjamingr](https://github.com/benjamingr) - **Benjamin Gruenbaum** <> * [BethGriggs](https://github.com/BethGriggs) - **Beth Griggs** <> (she/her) -* [bmeck](https://github.com/bmeck) - - **Bradley Farias** <> * [bnb](https://github.com/bnb) - - **Tierney Cyren** <> (they/he) + **Tierney Cyren** <> (they/them) * [bnoordhuis](https://github.com/bnoordhuis) - **Ben Noordhuis** <> * [BridgeAR](https://github.com/BridgeAR) - **Ruben Bridgewater** <> (he/him) * [cclauss](https://github.com/cclauss) - **Christian Clauss** <> (he/him) -* [ChALkeR](https://github.com/ChALkeR) - - **Сковорода Никита Андреевич** <> (he/him) * [cjihrig](https://github.com/cjihrig) - **Colin Ihrig** <> (he/him) * [codebytere](https://github.com/codebytere) - @@ -312,26 +321,24 @@ For information about the governance of the Node.js project, see **Kohei Ueno** <> (he/him) * [daeyeon](https://github.com/daeyeon) - **Daeyeon Jeong** <> (he/him) -* [danbev](https://github.com/danbev) - - **Daniel Bevenius** <> (he/him) * [danielleadams](https://github.com/danielleadams) - **Danielle Adams** <> (she/her) -* [devnexen](https://github.com/devnexen) - - **David Carlier** <> -* [devsnek](https://github.com/devsnek) - - **Gus Caplan** <> (they/them) +* [debadree25](https://github.com/debadree25) - + **Debadree Chatterjee** <> (he/him) +* [deokjinkim](https://github.com/deokjinkim) - + **Deokjin Kim** <> (he/him) * [edsadr](https://github.com/edsadr) - **Adrian Estrada** <> (he/him) * [erickwendel](https://github.com/erickwendel) - **Erick Wendel** <> (he/him) -* [evanlucas](https://github.com/evanlucas) - - **Evan Lucas** <> (he/him) -* [fhinkel](https://github.com/fhinkel) - - **Franziska Hinkelmann** <> (she/her) +* [Ethan-Arrowood](https://github.com/Ethan-Arrowood) - + **Ethan Arrowood** <> (he/him) * [F3n67u](https://github.com/F3n67u) - **Feng Yu** <> (he/him) +* [fhinkel](https://github.com/fhinkel) - + **Franziska Hinkelmann** <> (she/her) * [Flarna](https://github.com/Flarna) - - **Gerhard Stöbich** <> (he/they) + **Gerhard Stöbich** <> (he/they) * [gabrielschulhof](https://github.com/gabrielschulhof) - **Gabriel Schulhof** <> * [gengjiawen](https://github.com/gengjiawen) - @@ -342,16 +349,12 @@ For information about the governance of the Node.js project, see **Gireesh Punathil** <> (he/him) * [guybedford](https://github.com/guybedford) - **Guy Bedford** <> (he/him) +* [H4ad](https://github.com/H4ad) - + **Vinícius Lourenço Claro Cardoso** <> (he/him) * [HarshithaKP](https://github.com/HarshithaKP) - **Harshitha K P** <> (she/her) * [himself65](https://github.com/himself65) - **Zeyu "Alex" Yang** <> (he/him) -* [iansu](https://github.com/iansu) - - **Ian Sutherland** <> -* [indutny](https://github.com/indutny) - - **Fedor Indutny** <> -* [JacksonTian](https://github.com/JacksonTian) - - **Jackson Tian** <> * [JakobJingleheimer](https://github.com/JakobJingleheimer) - **Jacob Smith** <> (he/him) * [jasnell](https://github.com/jasnell) - @@ -366,12 +369,16 @@ For information about the governance of the Node.js project, see **Juan José Arboleda** <> (he/him) * [JungMinu](https://github.com/JungMinu) - **Minwoo Jung** <> (he/him) +* [KhafraDev](https://github.com/KhafraDev) - + **Matthew Aitken** <> (he/him) * [kuriyosh](https://github.com/kuriyosh) - **Yoshiki Kurihara** <> (he/him) +* [kvakil](https://github.com/kvakil) - + **Keyhan Vakil** <> * [legendecas](https://github.com/legendecas) - **Chengzhong Wu** <> (he/him) -* [Leko](https://github.com/Leko) - - **Shingo Inoue** <> (he/him) +* [lemire](https://github.com/lemire) - + **Daniel Lemire** <> * [linkgoron](https://github.com/linkgoron) - **Nitzan Uziely** <> * [LiviaMedeiros](https://github.com/LiviaMedeiros) - @@ -382,6 +389,8 @@ For information about the governance of the Node.js project, see **Luke Karrys** <> (he/him) * [Lxxyx](https://github.com/Lxxyx) - **Zijian Liu** <> (he/him) +* [marco-ippolito](https://github.com/marco-ippolito) - + **Marco Ippolito** <> (he/him) * [marsonya](https://github.com/marsonya) - **Akhil Marsonya** <> (he/him) * [mcollina](https://github.com/mcollina) - @@ -392,74 +401,62 @@ For information about the governance of the Node.js project, see **Mestery** <> (he/him) * [mhdawson](https://github.com/mhdawson) - **Michael Dawson** <> (he/him) -* [miladfarca](https://github.com/miladfarca) - - **Milad Fa** <> (he/him) * [mildsunrise](https://github.com/mildsunrise) - **Alba Mendez** <> (she/her) * [MoLow](https://github.com/MoLow) - **Moshe Atlow** <> (he/him) -* [mscdex](https://github.com/mscdex) - - **Brian White** <> +* [MrJithil](https://github.com/MrJithil) - + **Jithil P Ponnan** <> (he/him) * [MylesBorins](https://github.com/MylesBorins) - **Myles Borins** <> (he/him) -* [oyyd](https://github.com/oyyd) - - **Ouyang Yadong** <> (he/him) +* [ovflowd](https://github.com/ovflowd) - + **Claudio Wunder** <> (he/they) * [panva](https://github.com/panva) - **Filip Skokan** <> (he/him) -* [puzpuzpuz](https://github.com/puzpuzpuz) - - **Andrey Pechkurov** <> (he/him) +* [pimterry](https://github.com/pimterry) - + **Tim Perry** <> (he/him) * [Qard](https://github.com/Qard) - **Stephen Belanger** <> (he/him) * [RafaelGSS](https://github.com/RafaelGSS) - **Rafael Gonzaga** <> (he/him) -* [RaisinTen](https://github.com/RaisinTen) - - **Darshan Sen** <> (he/him) * [richardlau](https://github.com/richardlau) - **Richard Lau** <> -* [rickyes](https://github.com/rickyes) - - **Ricky Zhou** <<0x19951125@gmail.com>> (he/him) +* [rluvaton](https://github.com/rluvaton) - + **Raz Luvaton** <> (he/him) * [ronag](https://github.com/ronag) - **Robert Nagy** <> * [ruyadorno](https://github.com/ruyadorno) - - **Ruy Adorno** <> (he/him) -* [rvagg](https://github.com/rvagg) - - **Rod Vagg** <> -* [ryzokuken](https://github.com/ryzokuken) - - **Ujjwal Sharma** <> (he/him) + **Ruy Adorno** <> (he/him) * [santigimeno](https://github.com/santigimeno) - **Santiago Gimeno** <> -* [shisama](https://github.com/shisama) - - **Masashi Hirano** <> (he/him) * [ShogunPanda](https://github.com/ShogunPanda) - **Paolo Insogna** <> (he/him) * [srl295](https://github.com/srl295) - **Steven R Loomis** <> -* [starkwang](https://github.com/starkwang) - - **Weijia Wang** <> +* [StefanStojanovic](https://github.com/StefanStojanovic) - + **Stefan Stojanovic** <> (he/him) * [sxa](https://github.com/sxa) - **Stewart X Addison** <> (he/him) * [targos](https://github.com/targos) - **Michaël Zasso** <> (he/him) * [theanarkh](https://github.com/theanarkh) - **theanarkh** <> (he/him) -* [TimothyGu](https://github.com/TimothyGu) - - **Tiancheng "Timothy" Gu** <> (he/him) * [tniessen](https://github.com/tniessen) - **Tobias Nießen** <> (he/him) * [trivikr](https://github.com/trivikr) - **Trivikram Kamat** <> * [Trott](https://github.com/Trott) - **Rich Trott** <> (he/him) -* [vdeturckheim](https://github.com/vdeturckheim) - - **Vladimir de Turckheim** <> (he/him) +* [UlisesGascon](https://github.com/ulisesgascon) - + **Ulises Gascón** <> (he/him) +* [vmoroz](https://github.com/vmoroz) - + **Vladimir Morozov** <> (he/him) * [VoltrexKeyva](https://github.com/VoltrexKeyva) - **Mohammed Keyvanzadeh** <> (he/him) * [watilde](https://github.com/watilde) - **Daijiro Wachi** <> (he/him) -* [XadillaX](https://github.com/XadillaX) - - **Khaidi Chu** <> (he/him) -* [yashLadha](https://github.com/yashLadha) - - **Yash Ladha** <> (he/him) +* [zcbenz](https://github.com/zcbenz) - + **Cheng Zhao** <> (he/him) * [ZYSzys](https://github.com/ZYSzys) - **Yongsheng Zhang** <> (he/him) @@ -476,12 +473,20 @@ For information about the governance of the Node.js project, see **Aleksei Koziatinskii** <> * [andrasq](https://github.com/andrasq) - **Andras** <> -* [AnnaMag](https://github.com/AnnaMag) - - **Anna M. Kedzierska** <> * [AndreasMadsen](https://github.com/AndreasMadsen) - **Andreas Madsen** <> (he/him) +* [AnnaMag](https://github.com/AnnaMag) - + **Anna M. Kedzierska** <> +* [antsmartian](https://github.com/antsmartian) - + **Anto Aravinth** <> (he/him) * [aqrln](https://github.com/aqrln) - **Alexey Orlenko** <> (he/him) +* [AshCripps](https://github.com/AshCripps) - + **Ash Cripps** <> +* [bcoe](https://github.com/bcoe) - + **Ben Coe** <> (he/him) +* [bmeck](https://github.com/bmeck) - + **Bradley Farias** <> * [bmeurer](https://github.com/bmeurer) - **Benedikt Meurer** <> * [boneskull](https://github.com/boneskull) - @@ -492,14 +497,22 @@ For information about the governance of the Node.js project, see **Bartosz Sosnowski** <> * [calvinmetcalf](https://github.com/calvinmetcalf) - **Calvin Metcalf** <> +* [ChALkeR](https://github.com/ChALkeR) - + **Сковорода Никита Андреевич** <> (he/him) * [chrisdickinson](https://github.com/chrisdickinson) - **Chris Dickinson** <> * [claudiorodriguez](https://github.com/claudiorodriguez) - **Claudio Rodriguez** <> +* [danbev](https://github.com/danbev) - + **Daniel Bevenius** <> (he/him) * [DavidCai1993](https://github.com/DavidCai1993) - **David Cai** <> (he/him) * [davisjam](https://github.com/davisjam) - **Jamie Davis** <> (he/him) +* [devnexen](https://github.com/devnexen) - + **David Carlier** <> +* [devsnek](https://github.com/devsnek) - + **Gus Caplan** <> (they/them) * [digitalinfinity](https://github.com/digitalinfinity) - **Hitesh Kanwathirtha** <> (he/him) * [dmabupt](https://github.com/dmabupt) - @@ -512,6 +525,8 @@ For information about the governance of the Node.js project, see **Alexander Makarenko** <> * [eugeneo](https://github.com/eugeneo) - **Eugene Ostroukhov** <> +* [evanlucas](https://github.com/evanlucas) - + **Evan Lucas** <> (he/him) * [firedfox](https://github.com/firedfox) - **Daniel Wang** <> * [Fishrock123](https://github.com/Fishrock123) - @@ -528,16 +543,22 @@ For information about the governance of the Node.js project, see **Yang Guo** <> (he/him) * [hiroppy](https://github.com/hiroppy) - **Yuta Hiroto** <> (he/him) +* [iansu](https://github.com/iansu) - + **Ian Sutherland** <> * [iarna](https://github.com/iarna) - **Rebecca Turner** <> * [imran-iq](https://github.com/imran-iq) - **Imran Iqbal** <> * [imyller](https://github.com/imyller) - **Ilkka Myller** <> +* [indutny](https://github.com/indutny) - + **Fedor Indutny** <> * [isaacs](https://github.com/isaacs) - **Isaac Z. Schlueter** <> * [italoacasas](https://github.com/italoacasas) - **Italo A. Casas** <> (he/him) +* [JacksonTian](https://github.com/JacksonTian) - + **Jackson Tian** <> * [jasongin](https://github.com/jasongin) - **Jason Ginchereau** <> * [jbergstroem](https://github.com/jbergstroem) - @@ -558,6 +579,8 @@ For information about the governance of the Node.js project, see **Kunal Pathak** <> * [lance](https://github.com/lance) - **Lance Ball** <> (he/him) +* [Leko](https://github.com/Leko) - + **Shingo Inoue** <> (he/him) * [lucamaraschi](https://github.com/lucamaraschi) - **Luca Maraschi** <> (he/him) * [lundibundi](https://github.com/lundibundi) - @@ -574,6 +597,8 @@ For information about the governance of the Node.js project, see **Nicu Micleușanu** <> (he/him) * [mikeal](https://github.com/mikeal) - **Mikeal Rogers** <> +* [miladfarca](https://github.com/miladfarca) - + **Milad Fa** <> (he/him) * [misterdjules](https://github.com/misterdjules) - **Julien Gilli** <> * [mmarchini](https://github.com/mmarchini) - @@ -582,6 +607,8 @@ For information about the governance of the Node.js project, see **Christopher Monsanto** <> * [MoonBall](https://github.com/MoonBall) - **Chen Gang** <> +* [mscdex](https://github.com/mscdex) - + **Brian White** <> * [not-an-aardvark](https://github.com/not-an-aardvark) - **Teddy Katz** <> (he/him) * [ofrobots](https://github.com/ofrobots) - @@ -592,6 +619,8 @@ For information about the governance of the Node.js project, see **Alexis Campailla** <> * [othiym23](https://github.com/othiym23) - **Forrest L Norvell** <> (they/them/themself) +* [oyyd](https://github.com/oyyd) - + **Ouyang Yadong** <> (he/him) * [petkaantonov](https://github.com/petkaantonov) - **Petka Antonov** <> * [phillipj](https://github.com/phillipj) - @@ -606,10 +635,16 @@ For information about the governance of the Node.js project, see **Prince John Wesley** <> * [psmarshall](https://github.com/psmarshall) - **Peter Marshall** <> (he/him) +* [puzpuzpuz](https://github.com/puzpuzpuz) - + **Andrey Pechkurov** <> (he/him) +* [RaisinTen](https://github.com/RaisinTen) - + **Darshan Sen** <> (he/him) * [refack](https://github.com/refack) - **Refael Ackermann (רפאל פלחי)** <> (he/him/הוא/אתה) * [rexagod](https://github.com/rexagod) - **Pranshu Srivastava** <> (he/him) +* [rickyes](https://github.com/rickyes) - + **Ricky Zhou** <<0x19951125@gmail.com>> (he/him) * [rlidwka](https://github.com/rlidwka) - **Alex Kocharin** <> * [rmg](https://github.com/rmg) - @@ -624,6 +659,10 @@ For information about the governance of the Node.js project, see **Ingvar Stepanyan** <> * [rubys](https://github.com/rubys) - **Sam Ruby** <> +* [rvagg](https://github.com/rvagg) - + **Rod Vagg** <> +* [ryzokuken](https://github.com/ryzokuken) - + **Ujjwal Sharma** <> (he/him) * [saghul](https://github.com/saghul) - **Saúl Ibarra Corretgé** <> * [sam-github](https://github.com/sam-github) - @@ -634,8 +673,12 @@ For information about the governance of the Node.js project, see **Nikolai Vavilov** <> * [shigeki](https://github.com/shigeki) - **Shigeki Ohtsu** <> (he/him) +* [shisama](https://github.com/shisama) - + **Masashi Hirano** <> (he/him) * [silverwind](https://github.com/silverwind) - **Roman Reiss** <> +* [starkwang](https://github.com/starkwang) - + **Weijia Wang** <> * [stefanmb](https://github.com/stefanmb) - **Stefan Budeanu** <> * [tellnes](https://github.com/tellnes) - @@ -644,10 +687,14 @@ For information about the governance of the Node.js project, see **Sakthipriyan Vairamani** <> (he/him) * [thlorenz](https://github.com/thlorenz) - **Thorsten Lorenz** <> +* [TimothyGu](https://github.com/TimothyGu) - + **Tiancheng "Timothy" Gu** <> (he/him) * [trevnorris](https://github.com/trevnorris) - **Trevor Norris** <> * [tunniclm](https://github.com/tunniclm) - **Mike Tunnicliffe** <> +* [vdeturckheim](https://github.com/vdeturckheim) - + **Vladimir de Turckheim** <> (he/him) * [vkurchatkin](https://github.com/vkurchatkin) - **Vladimir Kurchatkin** <> * [vsemozhetbyt](https://github.com/vsemozhetbyt) - @@ -656,6 +703,10 @@ For information about the governance of the Node.js project, see **Thomas Watson** <> * [whitlockjc](https://github.com/whitlockjc) - **Jeremy Whitlock** <> +* [XadillaX](https://github.com/XadillaX) - + **Khaidi Chu** <> (he/him) +* [yashLadha](https://github.com/yashLadha) - + **Yash Ladha** <> (he/him) * [yhwang](https://github.com/yhwang) - **Yihong Wang** <> * [yorkie](https://github.com/yorkie) - @@ -672,10 +723,14 @@ maintaining the Node.js project. ### Triagers +* [atlowChemi](https://github.com/atlowChemi) - + **Chemi Atlow** <> (he/him) * [Ayase-252](https://github.com/Ayase-252) - **Qingyu Deng** <> * [bmuenzenmeyer](https://github.com/bmuenzenmeyer) - **Brian Muenzenmeyer** <> (he/him) +* [CanadaHonk](https://github.com/CanadaHonk) - + **Oliver Medhurst** <> (they/them) * [daeyeon](https://github.com/daeyeon) - **Daeyeon Jeong** <> (he/him) * [F3n67u](https://github.com/F3n67u) - @@ -684,20 +739,20 @@ maintaining the Node.js project. **Himadri Ganguly** <> (he/him) * [iam-frankqiu](https://github.com/iam-frankqiu) - **Frank Qiu** <> (he/him) -* [kvakil](https://github.com/kvakil) - - **Keyhan Vakil** <> (they/them) * [marsonya](https://github.com/marsonya) - **Akhil Marsonya** <> (he/him) * [meixg](https://github.com/meixg) - **Xuguang Mei** <> (he/him) +* [mertcanaltin](https://github.com/mertcanaltin) - + **Mert Can Altin** <> * [Mesteery](https://github.com/Mesteery) - **Mestery** <> (he/him) -* [MoLow](https://github.com/MoLow) - - **Moshe Atlow** <> (he/him) * [PoojaDurgad](https://github.com/PoojaDurgad) - **Pooja Durgad** <> -* [RaisinTen](https://github.com/RaisinTen) - - **Darshan Sen** <> +* [preveen-stack](https://github.com/preveen-stack) - + **Preveen Padmanabhan** <> (he/him) +* [RedYetiDev](https://github.com/redyetidev) - + **Aviv Keller** <> (they/them) * [VoltrexKeyva](https://github.com/VoltrexKeyva) - **Mohammed Keyvanzadeh** <> (he/him) @@ -714,29 +769,38 @@ Primary GPG keys for Node.js Releasers (some Releasers sign with subkeys): `141F07595B7B3FFE74309A937405533BE57C7D57` * **Danielle Adams** <> `74F12602B6F1C4E913FAA37AD3A89613643B6201` +* **Juan José Arboleda** <> + `DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7` +* **Marco Ippolito** <> + `CC68F5A3106FF448322E48ED27F5E38D5B0A215F` * **Michaël Zasso** <> `8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600` * **Myles Borins** <> `C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8` -* **RafaelGSS** <> +* **Rafael Gonzaga** <> `890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4` * **Richard Lau** <> `C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C` * **Ruy Adorno** <> `108F52B48DB57BB0CC439B2997B01419BD92F80A` +* **Ulises Gascón** <> + `A363A499291CBBC940DD62E41F10027AF002F8B0` To import the full set of trusted release keys (including subkeys possibly used to sign releases): ```bash -gpg --keyserver hkps://keys.openpgp.org --recv-keys 4ED778F539E3634C779C87C6D7062848A1AB005C -gpg --keyserver hkps://keys.openpgp.org --recv-keys 141F07595B7B3FFE74309A937405533BE57C7D57 -gpg --keyserver hkps://keys.openpgp.org --recv-keys 74F12602B6F1C4E913FAA37AD3A89613643B6201 -gpg --keyserver hkps://keys.openpgp.org --recv-keys 8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 -gpg --keyserver hkps://keys.openpgp.org --recv-keys C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 -gpg --keyserver hkps://keys.openpgp.org --recv-keys 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 -gpg --keyserver hkps://keys.openpgp.org --recv-keys C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C -gpg --keyserver hkps://keys.openpgp.org --recv-keys 108F52B48DB57BB0CC439B2997B01419BD92F80A +gpg --keyserver hkps://keys.openpgp.org --recv-keys 4ED778F539E3634C779C87C6D7062848A1AB005C # Beth Griggs +gpg --keyserver hkps://keys.openpgp.org --recv-keys 141F07595B7B3FFE74309A937405533BE57C7D57 # Bryan English +gpg --keyserver hkps://keys.openpgp.org --recv-keys 74F12602B6F1C4E913FAA37AD3A89613643B6201 # Danielle Adams +gpg --keyserver hkps://keys.openpgp.org --recv-keys DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 # Juan José Arboleda +gpg --keyserver hkps://keys.openpgp.org --recv-keys CC68F5A3106FF448322E48ED27F5E38D5B0A215F # Marco Ippolito +gpg --keyserver hkps://keys.openpgp.org --recv-keys 8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 # Michaël Zasso +gpg --keyserver hkps://keys.openpgp.org --recv-keys C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 # Myles Borins +gpg --keyserver hkps://keys.openpgp.org --recv-keys 890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 # Rafael Gonzaga +gpg --keyserver hkps://keys.openpgp.org --recv-keys C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C # Richard Lau +gpg --keyserver hkps://keys.openpgp.org --recv-keys 108F52B48DB57BB0CC439B2997B01419BD92F80A # Ruy Adorno +gpg --keyserver hkps://keys.openpgp.org --recv-keys A363A499291CBBC940DD62E41F10027AF002F8B0 # Ulises Gascón ``` See [Verifying binaries](#verifying-binaries) for how to use these keys to @@ -792,14 +856,11 @@ releases on a rotation basis as outlined in the * Datadog * [bengl](https://github.com/bengl) - **Bryan English** <> (he/him) - * [vdeturckheim](https://github.com/vdeturckheim) - - **Vladimir de Turckheim** <> (he/him) -* NearForm - * [RafaelGSS](https://github.com/RafaelGSS) - - **Rafael Gonzaga** <> (he/him) * NodeSource * [juanarbol](https://github.com/juanarbol) - **Juan José Arboleda** <> (he/him) + * [RafaelGSS](https://github.com/RafaelGSS) - + **Rafael Gonzaga** <> (he/him) * Platformatic * [mcollina](https://github.com/mcollina) - **Matteo Collina** <> (he/him) diff --git a/developer/src/inst/node/dist/node.exe b/developer/src/inst/node/dist/node.exe index 0c737e1d110..347919ba6c8 100644 Binary files a/developer/src/inst/node/dist/node.exe and b/developer/src/inst/node/dist/node.exe differ diff --git a/developer/src/kmc-analyze/src/osk-character-use/index.ts b/developer/src/kmc-analyze/src/osk-character-use/index.ts index ad97a32e45a..248944ccfcd 100644 --- a/developer/src/kmc-analyze/src/osk-character-use/index.ts +++ b/developer/src/kmc-analyze/src/osk-character-use/index.ts @@ -216,6 +216,9 @@ export class AnalyzeOskCharacterUse { for(let row of layer.row) { for(let key of row.key) { scanKey(key); + if(key.hint && !key.hint.match(/^\*.+\*$/)) { + strings.push(this.cleanString(key.hint)); + } let f: keyof TouchLayout.TouchLayoutFlick; for(f in key.flick ?? {}) { scanKey(key.flick[f]); diff --git a/developer/src/kmc-copy/src/KeymanProjectCopier.ts b/developer/src/kmc-copy/src/KeymanProjectCopier.ts index e463ec1b4c0..40f68fc1fc4 100644 --- a/developer/src/kmc-copy/src/KeymanProjectCopier.ts +++ b/developer/src/kmc-copy/src/KeymanProjectCopier.ts @@ -4,7 +4,7 @@ * Copy a keyboard or lexical model project */ -import { CompilerCallbacks, CompilerLogLevel, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KeymanDeveloperProject, KeymanDeveloperProjectOptions, KPJFileReader, KPJFileWriter, KpsFileReader, KpsFileWriter } from "@keymanapp/developer-utils"; +import { CloudUrls, GitHubUrls, CompilerCallbacks, CompilerLogLevel, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KeymanDeveloperProject, KeymanDeveloperProjectOptions, KPJFileReader, KPJFileWriter, KpsFileReader, KpsFileWriter } from "@keymanapp/developer-utils"; import { KeymanFileTypes } from "@keymanapp/common-types"; import { CopierMessages } from "./copier-messages.js"; @@ -86,6 +86,9 @@ export class KeymanProjectCopier implements KeymanCompiler { relocateExternalFiles: boolean = false; // TODO-COPY: support public async init(callbacks: CompilerCallbacks, options: CopierOptions): Promise { + if(!callbacks || !options) { + return false; + } this.callbacks = callbacks; this.options = options; this.cloudSource = new KeymanCloudSource(this.callbacks); @@ -97,7 +100,7 @@ export class KeymanProjectCopier implements KeymanCompiler { * artifacts on success. The files are passed in by name, and the compiler * will use callbacks as passed to the {@link KeymanProjectCopier.init} * function to read any input files by disk. - * @param source Source file or folder to copy. Can be a local file or folder, github:repo[:path], or cloud:id + * @param source Source file or folder to copy. Can be a local file or folder, https://github.com/.../repo[/path], or cloud:id * @returns Binary artifacts on success, null on failure. */ public async run(source: string): Promise { @@ -151,10 +154,10 @@ export class KeymanProjectCopier implements KeymanCompiler { * @returns path to .kpj (either local or remote) */ private async getSourceProject(source: string): Promise { - if(source.startsWith('github:')) { - // `github:owner/repo:path/to/kpj`, referencing a .kpj file + if(source.match(GitHubUrls.GITHUB_URI_OPTIONAL_PROTOCOL) || source.match(GitHubUrls.GITHUB_RAW_URI)) { + // `[https://]github.com/owner/repo/[tree|blob|raw]/[refs/...]/branch/path/to/kpj`, referencing a .kpj file return await this.getGitHubSourceProject(source); - } else if(source.startsWith('cloud:')) { + } else if(source.match(CloudUrls.CLOUD_URI) || source.match(CloudUrls.KEYMANCOM_CLOUD_URI)) { // `cloud:id`, referencing a Keyman Cloud keyboard return await this.getCloudSourceProject(source); } else if(this.callbacks.fs.existsSync(source) && source.endsWith(KeymanFileTypes.Source.Project) && !this.callbacks.isDirectory(source)) { @@ -194,64 +197,69 @@ export class KeymanProjectCopier implements KeymanCompiler { /** * Resolve path to GitHub source, which must be in the following format: - * `github:owner/repo[:branch]:path/to/kpj` + * `[https://]github.com/owner/repo/branch/path/to/kpj` * The path must be fully qualified, referencing the .kpj file; it * cannot just be the folder where the .kpj is found * @param source * @returns a promise: GitHub reference to the source for the keyboard, or null on failure */ private async getGitHubSourceProject(source: string): Promise { - const parts = source.split(':'); - if(parts.length < 3 || parts.length > 4 || !parts[1].match(/^[a-z0-9-]+\/[a-z0-9._-]+$/i)) { - // https://stackoverflow.com/questions/59081778/rules-for-special-characters-in-github-repository-name - this.callbacks.reportMessage(CopierMessages.Error_InvalidGitHubSource({source})); - return null; + const parts: GitHubUrls.GitHubRegexMatchArray = + GitHubUrls.GITHUB_URI_OPTIONAL_PROTOCOL.exec(source) ?? + GitHubUrls.GITHUB_RAW_URI.exec(source); + if(!parts) { + throw new Error('Expected GITHUB_URI_OPTIONAL_PROTOCOL or GITHUB_RAW_URI to match'); } - const origin = parts[1].split('/'); - - const ref: GitHubRef = new GitHubRef({ - owner: origin[0], - repo: origin[1], - branch: null, - path: null - }); + const ref: GitHubRef = new GitHubRef(parts); - if(parts.length == 4) { - ref.branch = parts[2]; - ref.path = parts[3]; - } else { + if(!ref.branch) { ref.branch = await this.cloudSource.getDefaultBranchFromGitHub(ref); if(!ref.branch) { this.callbacks.reportMessage(CopierMessages.Error_CouldNotFindDefaultBranchOnGitHub({ref: ref.toString()})); return null; } - ref.path = parts[2]; - + } + if(!ref.path) { + ref.path = '/'; } if(!ref.path.startsWith('/')) { ref.path = '/' + ref.path; } + if(ref.path != '/') { + if(!ref.path.endsWith('.kpj')) { + // Assumption, project filename matches folder name + if(ref.path.endsWith('/')) { + ref.path = ref.path.substring(0, ref.path.length-1); + } + ref.path = ref.path + '/' + this.callbacks.path.basename(ref.path) + '.kpj'; + } + } + return ref; } /** * Resolve path to Keyman Cloud source (which is on GitHub), which must be in * the following format: - * `cloud:keyboard_id|model_id` + * `cloud:keyboard_id`, or + * `cloud:model_id`, or + * `https://keyman.com/keyboards/keyboard_id` * The `keyboard_id` parameter should be a valid id (a-z0-9_), as found at - * https://keyman.com/keyboards; alternativel if it is a model_id, it should + * https://keyman.com/keyboards; alternatively if it is a model_id, it should * have the format author.bcp47.uniq * @param source * @returns a promise: GitHub reference to the source for the keyboard, or null on failure */ private async getCloudSourceProject(source: string): Promise { - const parts = source.split(':'); - const id = parts[1]; + const parts = CloudUrls.CLOUD_URI.exec(source) ?? CloudUrls.KEYMANCOM_CLOUD_URI.exec(source); + if(!parts) { + throw new Error('Expected CLOUD_URI or KEYMANCOM_CLOUD_URI to match'); + } + const id: string = parts.groups.id; const isModel = /^[^.]+\.[^.]+\.[^.]+$/.test(id); - const remote = await this.cloudSource.getSourceFromKeymanCloud(id, isModel); if(!remote) { return null; @@ -687,4 +695,11 @@ export class KeymanProjectCopier implements KeymanCompiler { return true; } /* c8 ignore stop */ + + /** @internal */ + public unitTestEndPoints = { + getGithubSourceProject: this.getGitHubSourceProject.bind(this), + getCloudSourceProject: this.getCloudSourceProject.bind(this) + }; + } diff --git a/developer/src/kmc-copy/src/cloud.ts b/developer/src/kmc-copy/src/cloud.ts index aeb5171895f..7b3ef1ff119 100644 --- a/developer/src/kmc-copy/src/cloud.ts +++ b/developer/src/kmc-copy/src/cloud.ts @@ -3,7 +3,7 @@ * * GitHub and Keyman Cloud interface wrappers */ -import { CompilerCallbacks } from "@keymanapp/developer-utils"; +import { CompilerCallbacks, GitHubUrls } from "@keymanapp/developer-utils"; import { CopierMessages } from "./copier-messages.js"; import { KeymanFileTypes } from "@keymanapp/common-types"; @@ -12,17 +12,24 @@ export class GitHubRef { public repo: string; public branch: string; public path: string; - constructor(owner: string | GitHubRef, repo?: string, branch?: string, path?: string) { + constructor(owner: string | GitHubRef | GitHubUrls.GitHubRegexMatchArray, repo?: string, branch?: string, path?: string) { if(typeof owner == 'string') { this.owner = owner; this.repo = repo; this.branch = branch; this.path = path; - } else { + } else if("groups" in owner) { + this.owner = owner.groups.owner; + this.repo = owner.groups.repo; + this.branch = owner.groups.branch; + this.path = owner.groups.path; + } else if("owner" in owner) { this.owner = owner.owner; this.repo = owner.repo; this.branch = owner.branch; this.path = owner.path; + } else { + throw new Error(`Unrecognized GitHubRef '${owner}'`) } } toString() { diff --git a/developer/src/kmc-copy/src/copier-messages.ts b/developer/src/kmc-copy/src/copier-messages.ts index 02294a90815..78a948ec79b 100644 --- a/developer/src/kmc-copy/src/copier-messages.ts +++ b/developer/src/kmc-copy/src/copier-messages.ts @@ -117,18 +117,7 @@ export class CopierMessages { `Dry run requested. No changes have been saved` ); - static ERROR_InvalidGitHubSource = SevError | 0x0011; - static Error_InvalidGitHubSource = (o:{source: string}) => m( - this.ERROR_InvalidGitHubSource, - `Source project specification '${def(o.source)}' is not a valid GitHub reference`, - `The source project specification for GitHub sources must match the pattern: - github:\\[:\\]:\\ - The path must include the .kpj filename and may optionally begin with a forward slash. - The following are valid examples: - github:keymanapp/keyboards:master:release/k/khmer_angkor/khmer_angkor.kpj - github:keymanapp/keyboards:release/k/khmer_angkor/khmer_angkor.kpj - github:keymanapp/keyboards:/release/k/khmer_angkor/khmer_angkor.kpj` - ); + // 0x0011 unused static ERROR_CannotDownloadFolderFromGitHub = SevError | 0x0012; static Error_CannotDownloadFolderFromGitHub = (o:{ref: string, message?: string, cause?: string}) => m( diff --git a/developer/src/kmc-copy/test/copier.tests.ts b/developer/src/kmc-copy/test/copier.tests.ts index 3cf9e4c0ee6..6d583862aea 100644 --- a/developer/src/kmc-copy/test/copier.tests.ts +++ b/developer/src/kmc-copy/test/copier.tests.ts @@ -12,6 +12,7 @@ import { assert } from 'chai'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { KeymanProjectCopier } from '../src/KeymanProjectCopier.js'; import { makePathToFixture } from './helpers/index.js'; +import { GitHubRef } from './cloud.js'; const { TEST_SAVE_ARTIFACTS, TEST_SAVE_FIXTURES } = env; let outputRoot: string = '/an/imaginary/root/'; @@ -374,7 +375,7 @@ describe('KeymanProjectCopier', function() { // armenian_mnemonic selected because (a) small, and (b) has v2.0 project, so // that exercises the folder retrieval as well - const result = await copier.run('github:keymanapp/keyboards:release/a/armenian_mnemonic/armenian_mnemonic.kpj'); + const result = await copier.run('github.com/keymanapp/keyboards/tree/master/release/a/armenian_mnemonic/armenian_mnemonic.kpj'); // We should have no messages and a successful result assert.isOk(result); @@ -416,6 +417,81 @@ describe('KeymanProjectCopier', function() { }); }); + // Keyman Cloud patterns + + const cloud_khmer_angkor: GitHubRef = { branch: 'master', owner: 'keymanapp', repo: 'keyboards', path: '/release/k/khmer_angkor/khmer_angkor.kpj' }; + const cloud_nrc_en_mtnt: GitHubRef = { branch: 'master', owner: 'keymanapp', repo: 'lexical-models', path: '/release/nrc/nrc.en.mtnt/nrc.en.mtnt.kpj' }; + const cloud_urls: [string,GitHubRef][] = [ + ['cloud:khmer_angkor', cloud_khmer_angkor], + ['https://keyman.com/keyboards/khmer_angkor', cloud_khmer_angkor], + ['https://keyman.com/keyboards/khmer_angkor/', cloud_khmer_angkor], + ['keyman.com/keyboards/khmer_angkor/', cloud_khmer_angkor], + ['http://keyman.com/keyboards/khmer_angkor#abc', cloud_khmer_angkor], + ['cloud:nrc.en.mtnt', cloud_nrc_en_mtnt], + ]; + + cloud_urls.forEach(url => { + it(`should parse URL '${url[0]}' and figure out the .kpj`, async function() { + // url --> + const copier = new KeymanProjectCopier(); + assert.isTrue(await copier.init(callbacks, { + dryRun: false, + outPath: '' + })); + + const ref = await copier.unitTestEndPoints.getCloudSourceProject(url[0]); + assert.isNotNull(ref); + assert.deepEqual(ref, url[1]); + }); + }) + + + // GitHub patterns that should match as inputs for kmc-copy source + + const armenian_mnemonic_urls = [ { + branch: 'master', urls: [ + 'github.com/keymanapp/keyboards/tree/master/release/a/armenian_mnemonic', + 'http://github.com/keymanapp/keyboards/tree/master/release/a/armenian_mnemonic', + 'https://github.com/keymanapp/keyboards/tree/master/release/a/armenian_mnemonic', + 'https://github.com/keymanapp/keyboards/tree/master/release/a/armenian_mnemonic/', + 'https://github.com/keymanapp/keyboards/tree/refs/heads/master/release/a/armenian_mnemonic', + 'https://github.com/keymanapp/keyboards/tree/refs/heads/master/release/a/armenian_mnemonic/', + 'https://github.com/keymanapp/keyboards/raw/refs/heads/master/release/a/armenian_mnemonic/armenian_mnemonic.kpj', + 'https://github.com/keymanapp/keyboards/raw/master/release/a/armenian_mnemonic/armenian_mnemonic.kpj', + 'https://github.com/keymanapp/keyboards/blob/refs/heads/master/release/a/armenian_mnemonic/armenian_mnemonic.kpj', + 'https://github.com/keymanapp/keyboards/blob/master/release/a/armenian_mnemonic/armenian_mnemonic.kpj', + + // And similar patterns for raw.githubusercontent.com + + 'https://raw.githubusercontent.com/keymanapp/keyboards/refs/heads/master/release/a/armenian_mnemonic/armenian_mnemonic.kpj', + 'https://raw.githubusercontent.com/keymanapp/keyboards/master/release/a/armenian_mnemonic/armenian_mnemonic.kpj', + ]}, { + branch: '78b6f98e5db4a249cc4231f8744f5fe4e5fd29f2', urls: [ + 'https://github.com/keymanapp/keyboards/blob/78b6f98e5db4a249cc4231f8744f5fe4e5fd29f2/release/a/armenian_mnemonic/armenian_mnemonic.kpj', + 'https://github.com/keymanapp/keyboards/tree/78b6f98e5db4a249cc4231f8744f5fe4e5fd29f2/release/a/armenian_mnemonic', + 'https://github.com/keymanapp/keyboards/tree/78b6f98e5db4a249cc4231f8744f5fe4e5fd29f2/release/a/armenian_mnemonic/', + 'https://raw.githubusercontent.com/keymanapp/keyboards/78b6f98e5db4a249cc4231f8744f5fe4e5fd29f2/release/a/armenian_mnemonic/armenian_mnemonic.kpj', + ]}]; + + armenian_mnemonic_urls.forEach(({branch,urls}) => urls.forEach(url => { + it(`should parse URL '${url}' and figure out the .kpj`, async function() { + // url --> + const copier = new KeymanProjectCopier(); + assert.isTrue(await copier.init(callbacks, { + dryRun: false, + outPath: '' + })); + + const ref = await copier.unitTestEndPoints.getGithubSourceProject(url); + assert.deepEqual(ref, { + branch, + owner: 'keymanapp', + repo: 'keyboards', + path: '/release/a/armenian_mnemonic/armenian_mnemonic.kpj' + }); + }); + })); + // TODO-COPY: additional tests it.skip('should copy a disorganized project into current structure', async function() {}); it.skip('should copy a standalone .kmn into a new project', async function() {}); diff --git a/developer/src/kmc-copy/test/fixtures/online/api.keyman.com/#keyboard#khmer_angkor b/developer/src/kmc-copy/test/fixtures/online/api.keyman.com/#keyboard#khmer_angkor new file mode 100644 index 00000000000..17b495a24bb --- /dev/null +++ b/developer/src/kmc-copy/test/fixtures/online/api.keyman.com/#keyboard#khmer_angkor @@ -0,0 +1,65 @@ +{ + "id": "khmer_angkor", + "name": "Khmer Angkor", + "license": "mit", + "authorName": "Makara Sok", + "authorEmail": "makara_sok@sil.org", + "description": "

Khmer Unicode keyboard layout based on the NiDA keyboard layout.\nAutomatically corrects many common keying errors.

", + "languages": { + "km": { + "examples": [ + { + "keys": "x j m E r", + "note": "Name of language", + "text": "\u1781\u17d2\u1798\u17c2\u179a" + } + ], + "font": { + "family": "Khmer Mondulkiri", + "source": [ + "Mondulkiri-R.ttf" + ] + }, + "oskFont": { + "family": "KbdKhmr", + "source": [ + "KbdKhmr.ttf" + ] + }, + "languageName": "Khmer", + "displayName": "Khmer" + } + }, + "lastModifiedDate": "2024-07-03T15:47:38.000Z", + "packageFilename": "khmer_angkor.kmp", + "packageFileSize": 4259005, + "jsFilename": "khmer_angkor.js", + "jsFileSize": 70494, + "packageIncludes": [ + "visualKeyboard", + "welcome", + "fonts", + "documentation" + ], + "version": "1.5", + "encodings": [ + "unicode" + ], + "platformSupport": { + "windows": "full", + "macos": "full", + "linux": "full", + "desktopWeb": "full", + "ios": "full", + "android": "full", + "mobileWeb": "full" + }, + "minKeymanVersion": "10.0", + "sourcePath": "release/k/khmer_angkor", + "helpLink": "https://help.keyman.com/keyboard/khmer_angkor", + "related": { + "khmer10": { + "deprecates": true + } + } +} \ No newline at end of file diff --git a/developer/src/kmc-copy/test/fixtures/online/api.keyman.com/#model#nrc.en.mtnt b/developer/src/kmc-copy/test/fixtures/online/api.keyman.com/#model#nrc.en.mtnt new file mode 100644 index 00000000000..bee31a4fe4a --- /dev/null +++ b/developer/src/kmc-copy/test/fixtures/online/api.keyman.com/#model#nrc.en.mtnt @@ -0,0 +1,23 @@ +{ + "languages": [ + "en", + "en-us", + "en-ca" + ], + "id": "nrc.en.mtnt", + "name": "English language model mined from MTNT", + "license": "mit", + "authorName": "Eddie Antonio Santos", + "authorEmail": "easantos@ualberta.ca", + "description": "

A unigram language model for English derived from the MTNT corpus http://www.cs.cmu.edu/~pmichel1/mtnt/. This corpus itself is gathered from Reddit, so it is unfiltered internet discussion. This is not humanity at its prettiest!

", + "lastModifiedDate": "2024-09-16T01:05:45.000Z", + "packageFilename": "https://keyman.com/go/package/download/model/nrc.en.mtnt?version=0.3.3&update=1", + "packageFileSize": 332955, + "jsFilename": "https://downloads.keyman.com/models/nrc.en.mtnt/0.3.3/nrc.en.mtnt.model.js", + "jsFileSize": 2713050, + "packageIncludes": [], + "version": "0.3.3", + "minKeymanVersion": "12.0", + "helpLink": "https://help.keyman.com/model/nrc.en.mtnt", + "sourcePath": "release/nrc/nrc.en.mtnt" +} \ No newline at end of file diff --git a/developer/src/kmc-generate/src/template/wordlist-lexical-model/README.md b/developer/src/kmc-generate/src/template/wordlist-lexical-model/README.md index c94a8f66b8a..704d52ba19d 100644 --- a/developer/src/kmc-generate/src/template/wordlist-lexical-model/README.md +++ b/developer/src/kmc-generate/src/template/wordlist-lexical-model/README.md @@ -11,7 +11,3 @@ Links Copyright --------- See [LICENSE.md](LICENSE.md) - -Supported Platforms -------------------- -$PLATFORMS_DOTLIST_README diff --git a/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/README.md b/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/README.md index 32fb70becc5..c3e9a3402b9 100644 --- a/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/README.md +++ b/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/README.md @@ -11,17 +11,3 @@ Links Copyright --------- See [LICENSE.md](LICENSE.md) - -Supported Platforms -------------------- - * Windows - * macOS - * Linux - * Web - * iPhone - * iPad - * Android phone - * Android tablet - * Mobile devices - * Desktop devices - * Tablet devices diff --git a/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/LICENSE.md b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/LICENSE.md new file mode 100644 index 00000000000..0a8c9054319 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 SIL International + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/.gitattributes b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/.gitattributes new file mode 100644 index 00000000000..7e3549570d7 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/.gitattributes @@ -0,0 +1 @@ +khmer_angkor.js -text \ No newline at end of file diff --git a/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.js b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.js new file mode 100644 index 00000000000..f95f54848cf --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.js @@ -0,0 +1,5462 @@ +if(typeof keyman === 'undefined') { + console.log('Keyboard requires KeymanWeb 10.0 or later'); + if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later"); +} else { +KeymanWeb.KR(new Keyboard_khmer_angkor()); +} +function Keyboard_khmer_angkor() +{ + var modCodes = keyman.osk.modifierCodes; + var keyCodes = keyman.osk.keyCodes; + + this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9; + this.KI="Keyboard_khmer_angkor"; + this.KN="Khmer Angkor"; + this.KMINVER="10.0"; + this.KV={F:' 1em "Khmer Busra Kbd"',K102:0}; + this.KV.KLS={ + "rightalt": ["‍","‌","@","","$","€","៙","៚","*","{","}","≈","","","","","ៜ","","ឯ","ឫ","ឨ","[","]","ឦ","ឱ","ឰ","ឩ","ឳ","\\","","","","+","-","×","÷",":","‘","’","ឝ","៘","៖","ៈ","","","","","","","<",">","#","&","ឞ",";","",",",".","/","","","","",""," "], + "rightalt-shift": ["","៱","៲","៳","៴","៵","៶","៷","៸","៹","៰","","","","","","᧠","᧡","᧢","᧣","᧤","᧥","᧦","᧧","᧨","᧩","᧪","᧫","","","","","᧬","᧭","᧮","᧯","᧰","᧱","᧲","᧳","᧴","᧵","᧶","","","","","","","᧷","᧸","᧹","᧺","᧻","᧼","᧽","᧾","᧿","","","","","","",""], + "default": ["«","១","២","៣","៤","៥","៦","៧","៨","៩","០","ឥ","ឲ","","","","ឆ","","","រ","ត","យ","","","","ផ","","ឪ","ឮ","","","","","ស","ដ","ថ","ង","ហ","","ក","ល","","","","","","","","","ឋ","ខ","ច","វ","ប","ន","ម","","។","","","","","","","​"], + "shift": ["»","!","ៗ","\"","៛","%","","","","(",")","","=","","","","ឈ","","","ឬ","ទ","","","","","ភ","","ឧ","ឭ","","","","","","ឌ","ធ","អ","ះ","ញ","គ","ឡ","","","","","","","","","ឍ","ឃ","ជ","","ព","ណ","","","៕","?","","","","","",""] + }; + this.KV.BK=(function(x){ + var + empty=Array.apply(null, Array(65)).map(String.prototype.valueOf,""), + result=[], v, i, + modifiers=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt']; + for(i=modifiers.length-1;i>=0;i--) { + v = x[modifiers[i]]; + if(v || result.length > 0) { + result=(v ? v : empty).slice().concat(result); + } + } + return result; + })(this.KV.KLS); + this.KDU=0; + this.KH=''; + this.KM=0; + this.KBVER="1.3"; + this.KMBM=modCodes.RALT | modCodes.SHIFT /* 0x0018 */; + this.KVKD="T_17D2_1780 T_17D2_1781 T_17D2_1782 T_17D2_1783 T_17D2_1784 T_17D2_1785 T_17D2_1786 T_17D2_1787 T_17D2_1788 T_17D2_1789 T_17D2_178A T_17D2_178B T_17D2_178C T_17D2_178D T_17D2_178E T_17D2_178F T_17D2_1790 T_17D2_1791 T_17D2_1792 T_17D2_1793 T_17D2_1794 T_17D2_1795 T_17D2_1796 T_17D2_1797 T_17D2_1798 T_17D2_1799 T_17D2_179A T_17D2_179B T_17D2_179C T_17D2_179D T_17D2_179E T_17D2_179F T_17D2_17A0 T_17D2_17A1 T_17D2_17A2 U_0030 U_0031 U_0032 U_0033 U_0034 U_0035 U_0036 U_0037 U_0038 U_0039"; + this.KVKL={ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_1", + "text": "១" + }, + { + "id": "K_2", + "text": "២" + }, + { + "id": "K_3", + "text": "៣" + }, + { + "id": "K_4", + "text": "៤" + }, + { + "id": "K_5", + "text": "៥" + }, + { + "id": "K_6", + "text": "៦" + }, + { + "id": "K_7", + "text": "៧" + }, + { + "id": "K_8", + "text": "៨" + }, + { + "id": "K_9", + "text": "៩" + }, + { + "id": "K_0", + "text": "០" + }, + { + "id": "K_HYPHEN", + "text": "ឥ" + }, + { + "id": "K_EQUAL", + "text": "ឲ" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "រ" + }, + { + "id": "K_T", + "text": "ត" + }, + { + "id": "K_Y", + "text": "យ" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ផ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឪ" + }, + { + "id": "T_new_138", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "K_BKQUOTE", + "text": "«" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "ស" + }, + { + "id": "K_D", + "text": "ដ" + }, + { + "id": "K_F", + "text": "ថ" + }, + { + "id": "K_G", + "text": "ង" + }, + { + "id": "K_H", + "text": "ហ" + }, + { + "id": "K_J", + "text": "" + }, + { + "id": "K_K", + "text": "ក" + }, + { + "id": "K_L", + "text": "ល" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឮ" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2" + }, + { + "id": "K_Z", + "text": "ឋ" + }, + { + "id": "K_X", + "text": "ខ" + }, + { + "id": "K_C", + "text": "ច" + }, + { + "id": "K_V", + "text": "វ" + }, + { + "id": "K_B", + "text": "ប" + }, + { + "id": "K_N", + "text": "ន" + }, + { + "id": "K_M", + "text": "ម" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "។" + }, + { + "id": "K_SLASH", + "text": "" + }, + { + "id": "T_new_164", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "5", + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "rightalt", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_1", + "text": "‌" + }, + { + "id": "K_2", + "text": "@" + }, + { + "id": "K_3", + "text": "" + }, + { + "id": "K_4", + "text": "$" + }, + { + "id": "K_5", + "text": "€" + }, + { + "id": "K_6", + "text": "៙" + }, + { + "id": "K_7", + "text": "៚" + }, + { + "id": "K_8", + "text": "*" + }, + { + "id": "K_9", + "text": "{" + }, + { + "id": "K_0", + "text": "}" + }, + { + "id": "K_HYPHEN", + "text": "≈" + }, + { + "id": "K_EQUAL", + "text": "" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "K_Q", + "text": "ៜ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "ឯ" + }, + { + "id": "K_R", + "text": "ឫ" + }, + { + "id": "K_T", + "text": "ឨ" + }, + { + "id": "K_Y", + "text": "[" + }, + { + "id": "K_U", + "text": "]" + }, + { + "id": "K_I", + "text": "ឦ" + }, + { + "id": "K_O", + "text": "ឱ" + }, + { + "id": "K_P", + "text": "ឰ" + }, + { + "id": "K_LBRKT", + "text": "ឩ" + }, + { + "id": "K_RBRKT", + "text": "ឳ" + }, + { + "id": "T_new_307", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "K_BKQUOTE", + "text": "‍" + }, + { + "id": "K_A", + "text": "+" + }, + { + "id": "K_S", + "text": "-" + }, + { + "id": "K_D", + "text": "×" + }, + { + "id": "K_F", + "text": "÷" + }, + { + "id": "K_G", + "text": ":" + }, + { + "id": "K_H", + "text": "‘" + }, + { + "id": "K_J", + "text": "’" + }, + { + "id": "K_K", + "text": "ឝ" + }, + { + "id": "K_L", + "text": "៘" + }, + { + "id": "K_COLON", + "text": "៖" + }, + { + "id": "K_QUOTE", + "text": "ៈ" + }, + { + "id": "K_BKSLASH", + "text": "\\" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2" + }, + { + "id": "K_Z", + "text": "<" + }, + { + "id": "K_X", + "text": ">" + }, + { + "id": "K_C", + "text": "#" + }, + { + "id": "K_V", + "text": "&" + }, + { + "id": "K_B", + "text": "ឞ" + }, + { + "id": "K_N", + "text": ";" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "," + }, + { + "id": "K_PERIOD", + "text": "." + }, + { + "id": "K_SLASH", + "text": "/" + }, + { + "id": "T_new_333", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "5", + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": " ", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_1", + "text": "!" + }, + { + "id": "K_2", + "text": "ៗ" + }, + { + "id": "K_3", + "text": "\"" + }, + { + "id": "K_4", + "text": "៛" + }, + { + "id": "K_5", + "text": "%" + }, + { + "id": "K_6", + "text": "" + }, + { + "id": "K_7", + "text": "" + }, + { + "id": "K_8", + "text": "" + }, + { + "id": "K_9", + "text": "(" + }, + { + "id": "K_0", + "text": ")" + }, + { + "id": "K_HYPHEN", + "text": "" + }, + { + "id": "K_EQUAL", + "text": "=" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "K_Q", + "text": "ឈ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "ឬ" + }, + { + "id": "K_T", + "text": "ទ" + }, + { + "id": "K_Y", + "text": "" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ភ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឧ" + }, + { + "id": "T_new_364", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "K_BKQUOTE", + "text": "»" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "" + }, + { + "id": "K_D", + "text": "ឌ" + }, + { + "id": "K_F", + "text": "ធ" + }, + { + "id": "K_G", + "text": "អ" + }, + { + "id": "K_H", + "text": "ះ" + }, + { + "id": "K_J", + "text": "ញ" + }, + { + "id": "K_K", + "text": "គ" + }, + { + "id": "K_L", + "text": "ឡ" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឭ" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_oE2" + }, + { + "id": "K_Z", + "text": "ឍ" + }, + { + "id": "K_X", + "text": "ឃ" + }, + { + "id": "K_C", + "text": "ជ" + }, + { + "id": "K_V", + "text": "" + }, + { + "id": "K_B", + "text": "ព" + }, + { + "id": "K_N", + "text": "ណ" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "៕" + }, + { + "id": "K_SLASH", + "text": "?" + }, + { + "id": "T_new_390", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "5", + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + } + ], + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + }, + "phone": { + "layer": [ + { + "id": "default", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "sk": [ + { + "text": "ឈ", + "id": "K_Q", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1786" + }, + { + "text": "", + "id": "T_17D2_1788" + } + ] + }, + { + "id": "K_W", + "text": "", + "sk": [ + { + "text": "", + "id": "K_W", + "layer": "shift" + } + ] + }, + { + "id": "K_E", + "text": "", + "sk": [ + { + "text": "", + "id": "K_E", + "layer": "shift" + }, + { + "text": "", + "id": "K_S", + "layer": "shift" + }, + { + "text": "", + "id": "K_V", + "layer": "shift" + }, + { + "text": "ឯ", + "id": "U_17AF" + }, + { + "text": "ឰ", + "id": "U_17B0" + } + ] + }, + { + "id": "K_R", + "text": "រ", + "sk": [ + { + "text": "", + "id": "T_17D2_179A" + }, + { + "text": "ឫ", + "id": "U_17AB" + }, + { + "text": "ឬ", + "id": "U_17AC" + } + ] + }, + { + "id": "K_T", + "text": "ត", + "sk": [ + { + "text": "ទ", + "id": "K_T", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178F" + }, + { + "text": "", + "id": "T_17D2_1791", + "layer": "default" + } + ] + }, + { + "id": "K_Y", + "text": "យ", + "sk": [ + { + "text": "", + "id": "T_17D2_1799" + } + ] + }, + { + "id": "K_U", + "text": "", + "sk": [ + { + "text": "", + "id": "K_U", + "layer": "shift" + }, + { + "text": "", + "id": "K_Y", + "layer": "shift" + }, + { + "text": "ឧ", + "id": "U_17A7" + }, + { + "text": "ឪ", + "id": "U_17AA", + "layer": "shift" + }, + { + "text": "ឩ", + "id": "U_17A9", + "layer": "shift" + }, + { + "text": "ឨ", + "id": "U_17A8" + } + ] + }, + { + "id": "K_I", + "text": "", + "sk": [ + { + "text": "", + "id": "K_I", + "layer": "shift" + }, + { + "text": "ឥ", + "id": "U_17A5" + }, + { + "text": "ឦ", + "id": "U_17A6", + "layer": "shift" + } + ] + }, + { + "id": "K_O", + "text": "", + "sk": [ + { + "text": "", + "id": "K_O", + "layer": "shift" + }, + { + "text": "", + "id": "K_LBRKT" + }, + { + "text": "", + "id": "K_LBRKT", + "layer": "shift" + }, + { + "text": "", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": "ឱ", + "id": "U_17B1" + }, + { + "text": "ឲ", + "id": "U_17B2" + }, + { + "text": "ឳ", + "id": "U_17B3", + "layer": "shift" + } + ] + }, + { + "id": "K_P", + "text": "ផ", + "sk": [ + { + "text": "ភ", + "id": "K_P", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1795" + }, + { + "text": "", + "id": "T_17D2_1797", + "layer": "default" + } + ] + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "K_A", + "text": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_A", + "layer": "shift" + } + ] + }, + { + "id": "K_S", + "text": "ស", + "sk": [ + { + "text": "", + "id": "T_17D2_179F" + }, + { + "text": "ឝ", + "id": "U_179D" + }, + { + "text": "ឞ", + "id": "U_179E" + } + ] + }, + { + "id": "K_D", + "text": "ដ", + "sk": [ + { + "text": "ឌ", + "id": "K_D", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178A" + }, + { + "text": "", + "id": "T_17D2_178C", + "layer": "default" + } + ] + }, + { + "id": "K_F", + "text": "ថ", + "sk": [ + { + "text": "ធ", + "id": "K_F", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1790" + }, + { + "text": "", + "id": "T_17D2_1792", + "layer": "default" + } + ] + }, + { + "id": "K_G", + "text": "ង", + "sk": [ + { + "text": "អ", + "id": "K_G", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1784" + }, + { + "text": "", + "id": "T_17D2_17A2", + "layer": "default" + } + ] + }, + { + "id": "K_H", + "text": "ហ", + "sk": [ + { + "text": "", + "id": "T_17D2_17A0" + }, + { + "text": "ះ", + "id": "K_H", + "layer": "shift" + }, + { + "text": "ៈ", + "id": "U_17C8" + } + ] + }, + { + "id": "K_J", + "text": "ញ", + "layer": "shift", + "sk": [ + { + "text": "", + "id": "T_17D2_1789" + } + ] + }, + { + "id": "K_K", + "text": "ក", + "sk": [ + { + "text": "គ", + "id": "K_K", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1780" + }, + { + "text": "", + "id": "T_17D2_1782" + } + ] + }, + { + "id": "K_L", + "text": "ល", + "sk": [ + { + "text": "ឡ", + "id": "K_L", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_179B" + }, + { + "text": "ឭ", + "id": "U_17AD" + }, + { + "text": "ឮ", + "id": "U_17AE" + } + ] + }, + { + "id": "K_COLON", + "text": "" + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "K_Z", + "text": "ឋ", + "sk": [ + { + "text": "ឍ", + "id": "K_Z", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178B" + }, + { + "text": "", + "id": "T_17D2_178D", + "layer": "default" + } + ] + }, + { + "id": "K_X", + "text": "ខ", + "sk": [ + { + "text": "ឃ", + "id": "K_X", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1781" + }, + { + "text": "", + "id": "T_17D2_1783", + "layer": "default" + } + ] + }, + { + "id": "K_C", + "text": "ច", + "sk": [ + { + "text": "ជ", + "id": "K_C", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1785" + }, + { + "text": "", + "id": "T_17D2_1787", + "layer": "default" + } + ] + }, + { + "id": "K_V", + "text": "វ", + "sk": [ + { + "text": "", + "id": "T_17D2_179C" + } + ] + }, + { + "id": "K_B", + "text": "ប", + "sk": [ + { + "text": "ព", + "id": "K_B", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1794" + }, + { + "text": "", + "id": "T_17D2_1796", + "layer": "default" + } + ] + }, + { + "id": "K_N", + "text": "ន", + "sk": [ + { + "text": "ណ", + "id": "K_N", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1793" + }, + { + "text": "", + "id": "T_17D2_178E", + "layer": "default" + } + ] + }, + { + "id": "K_M", + "text": "ម", + "sk": [ + { + "text": "", + "id": "T_17D2_1798" + }, + { + "text": "", + "id": "K_M", + "layer": "shift" + } + ] + }, + { + "id": "K_COMMA", + "text": "", + "sk": [ + { + "text": "", + "id": "K_COMMA", + "layer": "shift" + }, + { + "text": "", + "id": "K_6", + "layer": "shift" + }, + { + "text": "", + "id": "K_7", + "layer": "shift" + }, + { + "text": "", + "id": "K_8", + "layer": "shift" + }, + { + "text": "", + "id": "K_HYPHEN", + "layer": "shift" + }, + { + "text": "", + "id": "U_17D1", + "layer": "shift" + }, + { + "text": "", + "id": "U_17DD", + "layer": "shift" + }, + { + "text": "", + "id": "U_17CE", + "layer": "shift" + } + ] + }, + { + "id": "K_QUOTE", + "text": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "", + "id": "K_SLASH" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_NUMLOCK", + "text": "១២៣", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "sk": [ + { + "text": " ", + "id": "U_0020", + "layer": "default" + } + ] + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_1", + "text": "១", + "sk": [ + { + "text": "1", + "id": "U_0031" + } + ] + }, + { + "id": "K_2", + "text": "២", + "sk": [ + { + "text": "2", + "id": "U_0032" + } + ] + }, + { + "id": "K_3", + "text": "៣", + "sk": [ + { + "text": "3", + "id": "U_0033" + } + ] + }, + { + "id": "K_4", + "text": "៤", + "sk": [ + { + "text": "4", + "id": "U_0034" + } + ] + }, + { + "id": "K_5", + "text": "៥", + "sk": [ + { + "text": "5", + "id": "U_0035" + } + ] + }, + { + "id": "K_6", + "text": "៦", + "sk": [ + { + "text": "6", + "id": "U_0036" + } + ] + }, + { + "id": "K_7", + "text": "៧", + "sk": [ + { + "text": "7", + "id": "U_0037" + } + ] + }, + { + "id": "K_8", + "text": "៨", + "sk": [ + { + "text": "8", + "id": "U_0038" + } + ] + }, + { + "id": "K_9", + "text": "៩", + "sk": [ + { + "text": "9", + "id": "U_0039" + } + ] + }, + { + "id": "K_0", + "text": "០", + "sk": [ + { + "text": "0", + "id": "U_0030" + }, + { + "text": "", + "id": "U_17D3" + } + ] + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "U_0040", + "text": "@", + "sk": [ + { + "text": "©", + "id": "U_00A9" + }, + { + "text": "®", + "id": "U_00AE" + } + ] + }, + { + "id": "U_0023", + "text": "#", + "sk": [ + { + "text": "№", + "id": "U_2116" + }, + { + "text": "~", + "id": "U_007E" + } + ] + }, + { + "id": "U_17DB", + "text": "៛", + "sk": [ + { + "text": "$", + "id": "U_0024" + }, + { + "text": "฿", + "id": "U_0E3F" + }, + { + "text": "¢", + "id": "U_00A2" + }, + { + "text": "£", + "id": "U_00A3" + }, + { + "text": "¥", + "id": "U_00A5" + } + ] + }, + { + "id": "U_0026", + "text": "&" + }, + { + "id": "U_0025", + "text": "%", + "sk": [ + { + "text": "‰", + "id": "U_2030" + }, + { + "text": "‱", + "id": "U_2031" + } + ] + }, + { + "id": "U_002B", + "text": "+", + "sk": [ + { + "text": "-", + "id": "U_002D" + }, + { + "text": "×", + "id": "U_00D7" + }, + { + "text": "÷", + "id": "U_00F7" + }, + { + "text": "±", + "id": "U_00B1" + } + ] + }, + { + "id": "U_003D", + "text": "=", + "sk": [ + { + "text": "_", + "id": "U_005F" + }, + { + "text": "≠", + "id": "U_2260" + } + ] + }, + { + "id": "U_002A", + "text": "*", + "sk": [ + { + "text": "^", + "id": "U_005E" + } + ] + }, + { + "id": "U_003F", + "text": "?", + "sk": [ + { + "text": "¿", + "id": "U_00BF" + } + ] + }, + { + "id": "U_0021", + "text": "!", + "sk": [ + { + "text": "¡", + "id": "U_00A1" + } + ] + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "U_2018", + "text": "‘", + "sk": [ + { + "text": "’", + "id": "U_2019" + } + ] + }, + { + "id": "U_201C", + "text": "“", + "sk": [ + { + "text": "”", + "id": "U_201D" + } + ] + }, + { + "id": "U_00AB", + "text": "«", + "sk": [ + { + "text": "»", + "id": "U_00BB" + } + ] + }, + { + "id": "U_002F", + "text": "/", + "sk": [ + { + "text": "\\", + "id": "U_005C" + }, + { + "text": "|", + "id": "U_007C" + }, + { + "text": "¦", + "id": "U_00A6" + } + ] + }, + { + "id": "U_0028", + "text": "(", + "sk": [ + { + "text": ")", + "id": "U_0029" + }, + { + "text": "[", + "id": "U_005B" + }, + { + "text": "]", + "id": "U_005D" + }, + { + "text": "{", + "id": "U_007B" + }, + { + "text": "}", + "id": "U_007D" + } + ] + }, + { + "id": "U_17D9", + "text": "៙", + "sk": [ + { + "text": "៚", + "id": "U_17DA" + }, + { + "text": "ៜ", + "id": "U_17DC" + }, + { + "text": "§", + "id": "U_00A7" + }, + { + "text": "Ø", + "id": "U_00D8" + } + ] + }, + { + "id": "U_17D7", + "text": "ៗ" + }, + { + "id": "U_003C", + "text": "<", + "sk": [ + { + "text": "≤", + "id": "U_2264" + }, + { + "text": ">", + "id": "U_003E" + }, + { + "text": "≥", + "id": "U_2265" + } + ] + }, + { + "id": "U_17D6", + "text": "៖", + "sk": [ + { + "text": ":", + "id": "U_003A" + }, + { + "text": ";", + "id": "U_003B" + }, + { + "text": "…", + "id": "U_2026" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "sp": "1" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_LCONTROL", + "text": "១២៣", + "width": "140", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "layer": "shift" + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + } + ], + "displayUnderlying": false, + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + } +}; + this.s_c_key_11=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','']; + this.s_c_out_12="កខគឃងចឆជឈញដឋឌឍណតថទធនបផពភមយរលវសហឡអឝឞ"; + this.s_v_gen_key_13=['','','','','','','','','','','','','','','','']; + this.s_v_gen_14="ាិីឹឺុូួើឿៀេែៃោៅ"; + this.s_v_pseudo_key_15=['','','']; + this.s_v_pseudo_16="ំះៈ"; + this.s_v_key_17=['','','','','','','','','','','','','','','','','','','']; + this.s_v_out_18="ាិីឹឺុូួើឿៀេែៃោៅំះៈ"; + this.s_v_any_19="ាិីឹឺុូួើឿៀេែៃោៅំះៈ"; + this.s_v_combo_R_20="េោុិីឹែ"; + this.s_v_combo_N_21="ាុ"; + this.s_v_combo_22="េោុិីឹែាុ"; + this.s_ind_v_key_23=['','','','','','','','','','','','','','','']; + this.s_ind_v_out_24="ឥឦឧឨឩឪឫឬឭឮឯឰឱឲឳ"; + this.s_diacritic_key_25=['','','','','','','','','','','']; + this.s_diacritic_out_26="់័៌៏៍ៈ៎៑៝ៜ្"; + this.s_c_shifter_key_27=['','']; + this.s_c_shifter_28="៉៊"; + this.s_punct_key_29=['','','','','','','','']; + this.s_punct_out_30="។៕៖ៗ៘៙៚៓"; + this.s_latin_punct_key_31=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','']; + this.s_latin_punct_out_32="«»()!\"%=?{}\\@*,×./[]‍‌+-÷:≈‘’;<>#&"; + this.s_spaces_key_33=['','','']; + this.s_spaces_out_34="​  "; + this.s_currency_key_35=['','','']; + this.s_currency_out_36="៛$€"; + this.s_digit_key_37=['','','','','','','','','','']; + this.s_digit_out_38="០១២៣៤៥៦៧៨៩"; + this.s_lek_attak_key_39=['','','','','','','','','','']; + this.s_lek_attak_out_40="៰៱៲៳៴៵៶៷៸៹"; + this.s_lunar_date_key_41=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','']; + this.s_lunar_date_out_42="᧬᧻᧹᧮᧢᧯᧰᧱᧧᧲᧳᧴᧽᧼᧨᧩᧠᧣᧭᧤᧦᧺᧡᧸᧥᧷᧵᧾᧿᧪᧫᧶"; + this.s_input_subcons_43=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','']; + this.s_subcons_44="កខគឃងចឆជឈញដឋឌឍណតថទធនបផពភមយរលវឝឞសហឡអ"; + this.s_arabic_digit_key_45=['','','','','','','','','','']; + this.s_arabic_digit_out_46="0123456789"; + this.s_v_above_47="ិីឹឺើ័"; + this.s_shiftable_c_1st_48="សហអ"; + this.s_shiftable_BA_49="ប"; + this.s_shiftable_c_2nd_50="ងញមយរវនល"; + this.s_shiftable_c_2nd_with_BA_51="ងញមយរវនលប"; + this.s_c_2nd_combo_LO_52="យមងបវ"; + this.s_c_2nd_combo_MO_53="យលងរ"; + this.s_c_1st_combo_LO_54="បហអ"; + this.s_c_1st_combo_MO_55="ហសអ"; + this.s_c_combo_SA_56="បយលមនញងរវអ"; + this.s_c_combo_QA_57="ឆឈបផតទ"; + this.s_c_combo_HA_58="វឣ"; + this.s62="touch"; + this.KVS=[]; + this.gs=function(t,e) { + return this.g_main_0(t,e); + }; + this.gs=function(t,e) { + return this.g_main_0(t,e); + }; + this.g_main_0=function(t,e) { + var k=KeymanWeb,r=0,m=0; + if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_BKSP /* 0x08 */)) { + if(k.KFCM(2,t,['្',{t:'a',a:this.s_c_out_12}])&&k.KIFS(31,this.s62,t)){ + r=m=1; // Line 266 + k.KDC(2,t); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_combo_N_21},'ំ'])){ + r=m=1; // Line 229 + k.KDC(2,t); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_combo_R_20},'ះ'])){ + r=m=1; // Line 230 + k.KDC(2,t); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_B /* 0x42 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឞ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_K /* 0x4B */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឝ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_QUOTE /* 0xDE */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ៈ"); + } + else if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"ៈ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_E /* 0x45 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឯ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_I /* 0x49 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឦ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_O /* 0x4F */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឱ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_P /* 0x50 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឰ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_R /* 0x52 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឫ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_T /* 0x54 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឨ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_LBRKT /* 0xDB */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឩ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_RBRKT /* 0xDD */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឳ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_3 /* 0x33 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៑"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_Q /* 0x51 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"ៜ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_W /* 0x57 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៝"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_EQUAL /* 0xBB */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៎"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_6 /* 0x36 */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៙"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_7 /* 0x37 */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៚"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_L /* 0x4C */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៘"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_M /* 0x4D */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៓"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_COLON /* 0xBA */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៖"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_0 /* 0x30 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"}"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_1 /* 0x31 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"‌"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_2 /* 0x32 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"@"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_8 /* 0x38 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"*"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_9 /* 0x39 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"{"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_A /* 0x41 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"+"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"#"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_D /* 0x44 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"×"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_F /* 0x46 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"÷"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_G /* 0x47 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,":"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_H /* 0x48 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"‘"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_J /* 0x4A */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"’"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_N /* 0x4E */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,";"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"-"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_U /* 0x55 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"]"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_V /* 0x56 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"&"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_X /* 0x58 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,">"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_Y /* 0x59 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"["); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_Z /* 0x5A */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"<"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_COMMA /* 0xBC */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,","); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_HYPHEN /* 0xBD */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"≈"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_PERIOD /* 0xBE */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"."); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_SLASH /* 0xBF */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"/"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_BKQUOTE /* 0xC0 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"‍"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_BKSLASH /* 0xDC */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"\\"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_4 /* 0x34 */)) { + if(1){ + r=m=1; // Line 195 + k.KDC(0,t); + k.KO(-1,t,"$"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_5 /* 0x35 */)) { + if(1){ + r=m=1; // Line 195 + k.KDC(0,t); + k.KO(-1,t,"€"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_0 /* 0x30 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៰"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_1 /* 0x31 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៱"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_2 /* 0x32 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៲"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_3 /* 0x33 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៳"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_4 /* 0x34 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៴"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_5 /* 0x35 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៵"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_6 /* 0x36 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៶"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_7 /* 0x37 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៷"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_8 /* 0x38 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៸"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_9 /* 0x39 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៹"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_A /* 0x41 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧬"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_B /* 0x42 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧻"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧹"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_D /* 0x44 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧮"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_E /* 0x45 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧢"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_F /* 0x46 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧯"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_G /* 0x47 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧰"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_H /* 0x48 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧱"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_I /* 0x49 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧧"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_J /* 0x4A */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧲"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_K /* 0x4B */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧳"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_L /* 0x4C */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧴"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_M /* 0x4D */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧽"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_N /* 0x4E */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧼"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_O /* 0x4F */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧨"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_P /* 0x50 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧩"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_Q /* 0x51 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧠"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_R /* 0x52 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧣"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧭"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_T /* 0x54 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧤"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_U /* 0x55 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧦"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_V /* 0x56 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧺"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_W /* 0x57 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧡"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_X /* 0x58 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧸"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_Y /* 0x59 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧥"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_Z /* 0x5A */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧷"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_COLON /* 0xBA */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧵"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_COMMA /* 0xBC */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧾"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_PERIOD /* 0xBE */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧿"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_LBRKT /* 0xDB */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧪"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_RBRKT /* 0xDD */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧫"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_QUOTE /* 0xDE */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧶"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_SPACE /* 0x20 */)) { + if(1){ + r=m=1; // Line 199 + k.KDC(0,t); + k.KO(-1,t," "); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x100)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ក"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x101)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ខ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x102)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្គ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x103)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឃ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x104)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ង"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x105)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ច"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x106)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឆ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x107)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ជ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x108)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឈ"); + } + } + if(m) {} + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x109)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ញ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10A)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ដ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10B)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឋ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10C)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឌ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10D)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឍ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10E)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ណ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10F)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ត"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x110)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ថ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x111)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ទ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x112)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ធ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x113)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ន"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x114)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ប"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x115)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ផ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x116)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ព"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x117)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ភ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x118)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ម"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x119)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្យ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11A)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្រ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11B)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ល"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11C)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្វ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11D)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឝ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11E)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឞ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11F)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ស"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x120)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ហ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x121)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឡ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x122)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្អ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPSTAR /* 0x6A */)) { + if(1){ + r=m=1; // Line 268 + k.KDC(0,t); + k.KO(-1,t,"*"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPSTAR /* 0x6A */)) { + if(1){ + r=m=1; // Line 269 + k.KDC(0,t); + k.KO(-1,t,"*"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPPLUS /* 0x6B */)) { + if(1){ + r=m=1; // Line 270 + k.KDC(0,t); + k.KO(-1,t,"+"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPPLUS /* 0x6B */)) { + if(1){ + r=m=1; // Line 271 + k.KDC(0,t); + k.KO(-1,t,"+"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPMINUS /* 0x6D */)) { + if(1){ + r=m=1; // Line 272 + k.KDC(0,t); + k.KO(-1,t,"-"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPMINUS /* 0x6D */)) { + if(1){ + r=m=1; // Line 273 + k.KDC(0,t); + k.KO(-1,t,"-"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPDOT /* 0x6E */)) { + if(1){ + r=m=1; // Line 274 + k.KDC(0,t); + k.KO(-1,t,"."); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPDOT /* 0x6E */)) { + if(1){ + r=m=1; // Line 275 + k.KDC(0,t); + k.KO(-1,t,"."); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPSLASH /* 0x6F */)) { + if(1){ + r=m=1; // Line 276 + k.KDC(0,t); + k.KO(-1,t,"/"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPSLASH /* 0x6F */)) { + if(1){ + r=m=1; // Line 277 + k.KDC(0,t); + k.KO(-1,t,"/"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_SPACE /* 0x20 */)) { + if(k.KFCM(1,t,['​'])){ + r=m=1; // Line 225 + k.KDC(1,t); + k.KO(-1,t," "); + } + else if(1){ + r=m=1; // Line 199 + k.KDC(0,t); + k.KO(-1,t,"​"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_SPACE /* 0x20 */)) { + if(1){ + r=m=1; // Line 199 + k.KDC(0,t); + k.KO(-1,t," "); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_1 /* 0x31 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"!"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_QUOTE /* 0xDE */)) { + if(k.KFCM(3,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ'])){ + r=m=1; // Line 244 + k.KDC(3,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្អ៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54}])){ + r=m=1; // Line 245 + k.KDC(3,t); + k.KO(-1,t,"ល្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55}])){ + r=m=1; // Line 246 + k.KDC(3,t); + k.KO(-1,t,"ម្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56}])){ + r=m=1; // Line 247 + k.KDC(3,t); + k.KO(-1,t,"ស្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៉"); + k.KB(t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ'])){ + r=m=1; // Line 248 + k.KDC(3,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្ហ៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['អ','្','ង'])){ + r=m=1; // Line 249 + k.KDC(3,t); + k.KO(-1,t,"អ្ង៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['អ','្','វ'])){ + r=m=1; // Line 250 + k.KDC(3,t); + k.KO(-1,t,"អ្វ៉"); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_c_shifter_28}])){ + r=m=1; // Line 215 + k.KDC(1,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_shiftable_c_1st_48}])){ + r=m=1; // Line 239 + k.KDC(1,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៉"); + k.KB(t); + } + else if(1){ + r=m=1; // Line 192 + k.KDC(0,t); + k.KO(-1,t,"៉"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_3 /* 0x33 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"\""); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_4 /* 0x34 */)) { + if(1){ + r=m=1; // Line 195 + k.KDC(0,t); + k.KO(-1,t,"៛"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_5 /* 0x35 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"%"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_7 /* 0x37 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"័"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_QUOTE /* 0xDE */)) { + if(k.KFCM(2,t,['្',{t:'a',a:this.s_c_out_12}])){ + r=m=1; // Line 214 + k.KDC(2,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_out_12,2,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_v_gen_14}])){ + r=m=1; // Line 211 + k.KDC(1,t); + k.KIO(-1,this.s_v_gen_14,1,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_v_pseudo_16}])){ + r=m=1; // Line 212 + k.KDC(1,t); + k.KIO(-1,this.s_v_pseudo_16,1,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_c_shifter_28}])){ + r=m=1; // Line 213 + k.KDC(1,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KB(t); + } + else if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"់"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_9 /* 0x39 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"("); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_0 /* 0x30 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,")"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_8 /* 0x38 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៏"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_EQUAL /* 0xBB */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"="); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_COMMA /* 0xBC */)) { + if(1){ + r=m=1; // Line 206 + k.KDC(0,t); + k.KO(-1,t,"ុំ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_HYPHEN /* 0xBD */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឥ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_PERIOD /* 0xBE */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"។"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_SLASH /* 0xBF */)) { + if(k.KFCM(3,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52}])){ + r=m=1; // Line 254 + k.KDC(3,t); + k.KO(-1,t,"ល្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៊"); + k.KB(t); + } + else if(k.KFCM(3,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53}])){ + r=m=1; // Line 255 + k.KDC(3,t); + k.KO(-1,t,"ម្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៊"); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_c_shifter_28}])){ + r=m=1; // Line 215 + k.KDC(1,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51}])){ + r=m=1; // Line 240 + k.KDC(1,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៊"); + k.KB(t); + } + else if(1){ + r=m=1; // Line 192 + k.KDC(0,t); + k.KO(-1,t,"៊"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_0 /* 0x30 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"០"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_1 /* 0x31 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"១"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_2 /* 0x32 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"២"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_3 /* 0x33 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៣"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_4 /* 0x34 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៤"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_5 /* 0x35 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៥"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_6 /* 0x36 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៦"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_7 /* 0x37 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៧"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_8 /* 0x38 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៨"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_9 /* 0x39 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៩"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_COLON /* 0xBA */)) { + if(1){ + r=m=1; // Line 205 + k.KDC(0,t); + k.KO(-1,t,"ោះ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_COLON /* 0xBA */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ើ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_COMMA /* 0xBC */)) { + if(1){ + r=m=1; // Line 207 + k.KDC(0,t); + k.KO(-1,t,"ុះ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_EQUAL /* 0xBB */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឲ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_PERIOD /* 0xBE */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៕"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_SLASH /* 0xBF */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"?"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_2 /* 0x32 */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"ៗ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_A /* 0x41 */)) { + if(1){ + r=m=1; // Line 203 + k.KDC(0,t); + k.KO(-1,t,"ាំ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_B /* 0x42 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ព"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ជ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_D /* 0x44 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឌ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_E /* 0x45 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ែ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_F /* 0x46 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ធ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_G /* 0x47 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"អ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_H /* 0x48 */)) { + if(k.KFCM(1,t,['ះ'])){ + r=m=1; // Line 219 + k.KDC(1,t); + k.KO(-1,t,"ៈ"); + } + else if(k.KFCM(1,t,['ៈ'])){ + r=m=1; // Line 220 + k.KDC(1,t); + k.KO(-1,t,"ះ"); + } + else if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ះ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_I /* 0x49 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ី"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_J /* 0x4A */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ញ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_K /* 0x4B */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"គ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_L /* 0x4C */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឡ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_M /* 0x4D */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ំ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_N /* 0x4E */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ណ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_O /* 0x4F */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ៅ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_P /* 0x50 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ភ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_Q /* 0x51 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឈ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_R /* 0x52 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឬ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ៃ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_T /* 0x54 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ទ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_U /* 0x55 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ូ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_V /* 0x56 */)) { + if(1){ + r=m=1; // Line 204 + k.KDC(0,t); + k.KO(-1,t,"េះ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_W /* 0x57 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ឺ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_X /* 0x58 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឃ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_Y /* 0x59 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ួ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_Z /* 0x5A */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឍ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_LBRKT /* 0xDB */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ៀ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_BKSLASH /* 0xDC */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឮ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_RBRKT /* 0xDD */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឪ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_6 /* 0x36 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៍"); + } + } + if(m) {} + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_HYPHEN /* 0xBD */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៌"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_BKQUOTE /* 0xC0 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"«"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_A /* 0x41 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ា"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_B /* 0x42 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ប"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ច"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_D /* 0x44 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ដ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_E /* 0x45 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"េ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_F /* 0x46 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ថ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_G /* 0x47 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ង"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_H /* 0x48 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ហ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_I /* 0x49 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ិ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_J /* 0x4A */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"្"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_K /* 0x4B */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ក"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_L /* 0x4C */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ល"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_M /* 0x4D */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ម"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_N /* 0x4E */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ន"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_O /* 0x4F */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ោ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_P /* 0x50 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ផ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_Q /* 0x51 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឆ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_R /* 0x52 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"រ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ស"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_T /* 0x54 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ត"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_U /* 0x55 */)) { + if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ា','ំ'])){ + r=m=1; // Line 234 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ា','ំ'])){ + r=m=1; // Line 235 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ុ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_V /* 0x56 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"វ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_W /* 0x57 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ឹ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_X /* 0x58 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ខ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_Y /* 0x59 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"យ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_Z /* 0x5A */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឋ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_LBRKT /* 0xDB */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ឿ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_BKSLASH /* 0xDC */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឭ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_RBRKT /* 0xDD */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឧ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_BKQUOTE /* 0xC0 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"»"); + } + } + if(m==1) { + + k.KDC(-1,t); + r=this.g_normalise_1(t,e); + m=2; + } + return r; + }; + this.g_normalise_1=function(t,e) { + var k=KeymanWeb,r=1,m=0; + if(k.KFCM(7,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ','ំ','ា','ំ'])){ + m=1; // Line 376 + k.KDC(7,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ','ំ','ា','ំ'])){ + m=1; // Line 381 + k.KDC(7,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ','ំ','ា','ំ'])){ + m=1; // Line 386 + k.KDC(7,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ស','្','ប','ុ','ំ','ា','ំ'])){ + m=1; // Line 391 + k.KDC(7,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ','ំ','ា','ំ'])){ + m=1; // Line 396 + k.KDC(7,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ','ំ','ា','ំ'])){ + m=1; // Line 401 + k.KDC(7,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['អ','្','ង','ុ','ំ','ា','ំ'])){ + m=1; // Line 406 + k.KDC(7,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['អ','្','វ','ុ','ំ','ា','ំ'])){ + m=1; // Line 411 + k.KDC(7,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ហ','្','ប','ុ','ំ','ា','ំ'])){ + m=1; // Line 416 + k.KDC(7,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ហ','្',{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ំ','ា','ំ'])){ + m=1; // Line 422 + k.KDC(7,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ','ំ','ា','ំ'])){ + m=1; // Line 429 + k.KDC(7,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ','ំ','ា','ំ'])){ + m=1; // Line 434 + k.KDC(7,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['្','ដ',{t:'a',a:this.s_v_combo_N_21},'ំ','្','រ'])){ + m=1; // Line 340 + k.KDC(6,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_N_21,3,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['្','ដ',{t:'a',a:this.s_v_combo_R_20},'ះ','្','រ'])){ + m=1; // Line 341 + k.KDC(6,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_R_20,3,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s_v_combo_N_21},'ំ','្','ដ'])){ + m=1; // Line 344 + k.KDC(6,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_N_21,3,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s_v_combo_R_20},'ះ','្','ដ'])){ + m=1; // Line 345 + k.KDC(6,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_R_20,3,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s_v_combo_N_21},'ំ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 350 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,6,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_N_21,3,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s_v_combo_R_20},'ះ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 351 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,6,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_R_20,3,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ','ា','ំ'])){ + m=1; // Line 374 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ','ា','ំ'])){ + m=1; // Line 379 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ','ា','ំ'])){ + m=1; // Line 384 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្','ប','ុ','ា','ំ'])){ + m=1; // Line 389 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ','ា','ំ'])){ + m=1; // Line 394 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ','ា','ំ'])){ + m=1; // Line 399 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','ង','ុ','ា','ំ'])){ + m=1; // Line 404 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','វ','ុ','ា','ំ'])){ + m=1; // Line 409 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ហ','្','ប','ុ','ា','ំ'])){ + m=1; // Line 414 + k.KDC(6,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ប"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ហ','្',{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ា','ំ'])){ + m=1; // Line 420 + k.KDC(6,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ','ា','ំ'])){ + m=1; // Line 427 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ','ា','ំ'])){ + m=1; // Line 432 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'៊',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 454 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_gen_14,5,t); + k.KIO(-1,this.s_v_pseudo_16,6,t); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'៊',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 455 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_gen_14,5,t); + k.KIO(-1,this.s_v_pseudo_16,6,t); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','៉','ា','ំ'])){ + m=1; // Line 489 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'៉','ា','ំ'])){ + m=1; // Line 490 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'៉','ា','ំ'])){ + m=1; // Line 491 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្','ប','៉','ា','ំ'])){ + m=1; // Line 492 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'៉','ា','ំ'])){ + m=1; // Line 493 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','៉','ា','ំ'])){ + m=1; // Line 494 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','ង','៉','ា','ំ'])){ + m=1; // Line 495 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','វ','៉','ា','ំ'])){ + m=1; // Line 496 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ា','ុ','ំ'])){ + m=1; // Line 503 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ','ំ','ា'])){ + m=1; // Line 504 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ា','ុ','ំ'])){ + m=1; // Line 506 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ','ំ','ា'])){ + m=1; // Line 507 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ា','ុ','ំ'])){ + m=1; // Line 509 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ','ំ','ា'])){ + m=1; // Line 510 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ា','ុ','ំ'])){ + m=1; // Line 512 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ','ំ','ា'])){ + m=1; // Line 513 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ា','ុ','ំ'])){ + m=1; // Line 515 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ','ំ','ា'])){ + m=1; // Line 516 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','ង','ា','ុ','ំ'])){ + m=1; // Line 518 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','ង','ុ','ំ','ា'])){ + m=1; // Line 519 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','វ','ា','ុ','ំ'])){ + m=1; // Line 521 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','វ','ុ','ំ','ា'])){ + m=1; // Line 522 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ុ"); + k.KO(-1,t,"ា"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ា','ុ','ំ'])){ + m=1; // Line 529 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ','ំ','ា'])){ + m=1; // Line 530 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ា','ុ','ំ'])){ + m=1; // Line 532 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ','ំ','ា'])){ + m=1; // Line 533 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','េ','ុ','ី'])){ + m=1; // Line 541 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ','េ','ី'])){ + m=1; // Line 542 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','៉','េ','ី'])){ + m=1; // Line 543 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'េ','ុ','ី'])){ + m=1; // Line 545 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ','េ','ី'])){ + m=1; // Line 546 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'៉','េ','ី'])){ + m=1; // Line 547 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'េ','ុ','ី'])){ + m=1; // Line 549 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ','េ','ី'])){ + m=1; // Line 550 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'៉','េ','ី'])){ + m=1; // Line 551 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'េ','ុ','ី'])){ + m=1; // Line 553 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ','េ','ី'])){ + m=1; // Line 554 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'៉','េ','ី'])){ + m=1; // Line 555 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','េ','ុ','ី'])){ + m=1; // Line 557 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ','េ','ី'])){ + m=1; // Line 558 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','៉','េ','ី'])){ + m=1; // Line 559 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','ង','េ','ុ','ី'])){ + m=1; // Line 561 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','ង','ុ','េ','ី'])){ + m=1; // Line 562 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','ង','៉','េ','ី'])){ + m=1; // Line 563 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','វ','េ','ុ','ី'])){ + m=1; // Line 565 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','វ','ុ','េ','ី'])){ + m=1; // Line 566 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','វ','៉','េ','ី'])){ + m=1; // Line 567 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'េ','ុ','ី'])){ + m=1; // Line 575 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ','េ','ី'])){ + m=1; // Line 576 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'៊','េ','ី'])){ + m=1; // Line 577 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'េ','ុ','ី'])){ + m=1; // Line 579 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ','េ','ី'])){ + m=1; // Line 580 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'៊','េ','ី'])){ + m=1; // Line 581 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s_c_out_12},'េ','ឺ'])){ + m=1; // Line 631 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_out_12,4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s_c_out_12},'េ','ឹ'])){ + m=1; // Line 632 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_out_12,4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s_c_out_12},'េ','ី'])){ + m=1; // Line 633 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_out_12,4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_combo_N_21},'ំ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 331 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,5,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KIO(-1,this.s_v_combo_N_21,2,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_combo_R_20},'ះ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 332 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,5,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KIO(-1,this.s_v_combo_R_20,2,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(5,t,['្','ដ',{t:'a',a:this.s_v_any_19},'្','រ'])){ + m=1; // Line 339 + k.KDC(5,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_any_19,3,t); + } + else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s_v_any_19},'្','ដ'])){ + m=1; // Line 343 + k.KDC(5,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_any_19,3,t); + } + else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s_c_shifter_28},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 347 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,5,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_c_shifter_28,3,t); + } + else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s_v_any_19},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 349 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,5,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_any_19,3,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 373 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 375 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 378 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 380 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 383 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 385 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + if(m) {} + else if(k.KFCM(5,t,['ស','្','ប','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 388 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ស','្','ប',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 390 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 393 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 395 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 398 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 400 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['អ','្','ង','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 403 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['អ','្','ង',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 405 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['អ','្','វ','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 408 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['អ','្','វ',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 410 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ហ','្','ប','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 413 + k.KDC(5,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ហ','្','ប',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 415 + k.KDC(5,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ហ','្',{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 419 + k.KDC(5,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ហ','្',{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 421 + k.KDC(5,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 426 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 428 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 431 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 433 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ','ំ','ា','ំ'])){ + m=1; // Line 441 + k.KDC(5,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ំ','ា','ំ'])){ + m=1; // Line 448 + k.KDC(5,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'៊',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 452 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'៊',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 453 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['្',{t:'a',a:this.s_shiftable_c_2nd_50},'៊',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 463 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_50,2,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_gen_14,4,t); + k.KIO(-1,this.s_v_pseudo_16,5,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 481 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 482 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 483 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ស','្','ប','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 484 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 485 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 486 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['អ','្','ង','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 487 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['អ','្','វ','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 488 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ព','័','ន','្','ឋ'])){ + m=1; // Line 619 + k.KDC(5,t); + k.KO(-1,t,"ព"); + k.KO(-1,t,"័"); + k.KO(-1,t,"ន"); + k.KO(-1,t,"្ធ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 312 + k.KDC(4,t); + k.KIO(-1,this.s_v_gen_14,3,t); + k.KIO(-1,this.s_v_pseudo_16,4,t); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_v_combo_N_21},'ំ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 325 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,4,t); + k.KIO(-1,this.s_v_combo_N_21,1,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_v_combo_R_20},'ះ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 326 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,4,t); + k.KIO(-1,this.s_v_combo_R_20,1,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_any_19},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 330 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,4,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KIO(-1,this.s_v_any_19,2,t); + } + else if(k.KFCM(4,t,['្','ដ','្','រ'])){ + m=1; // Line 336 + k.KDC(4,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + } + else if(k.KFCM(4,t,['្','រ','្','ដ'])){ + m=1; // Line 337 + k.KDC(4,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + } + else if(k.KFCM(4,t,['្','រ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 348 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,4,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 362 + k.KDC(4,t); + k.KIO(-1,this.s_v_gen_14,2,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ','ា','ំ'])){ + m=1; // Line 439 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ា','ំ'])){ + m=1; // Line 446 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'៊',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 460 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_gen_14,3,t); + k.KIO(-1,this.s_v_pseudo_16,4,t); + } + else if(k.KFCM(4,t,['្',{t:'a',a:this.s_shiftable_c_2nd_50},'៊',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 462 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_50,2,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(4,t,['ប','្','យ',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 467 + k.KDC(4,t); + k.KO(-1,t,"ប្យ"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ស','្','ប',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 468 + k.KDC(4,t); + k.KO(-1,t,"ស្ប"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ឆ','្','ប',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 469 + k.KDC(4,t); + k.KO(-1,t,"ឆ្ប"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ប','្','យ',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 470 + k.KDC(4,t); + k.KO(-1,t,"ប្យ"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ស','្','ប',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 471 + k.KDC(4,t); + k.KO(-1,t,"ស្ប"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ឆ','្','ប',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 472 + k.KDC(4,t); + k.KO(-1,t,"ឆ្ប"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'៉',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 477 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_gen_14,3,t); + k.KIO(-1,this.s_v_pseudo_16,4,t); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ា','ុ','ំ'])){ + m=1; // Line 500 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ','ំ','ា'])){ + m=1; // Line 501 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ា','ុ','ំ'])){ + m=1; // Line 526 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ំ','ា'])){ + m=1; // Line 527 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'េ','ុ','ី'])){ + m=1; // Line 537 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ','េ','ី'])){ + m=1; // Line 538 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'៉','េ','ី'])){ + m=1; // Line 539 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'េ','ុ','ី'])){ + m=1; // Line 571 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'ុ','េ','ី'])){ + m=1; // Line 572 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'៊','េ','ី'])){ + m=1; // Line 573 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(4,t,['ព','ន','្','ឋ'])){ + m=1; // Line 618 + k.KDC(4,t); + k.KO(-1,t,"ព"); + k.KO(-1,t,"ន"); + k.KO(-1,t,"្ធ"); + } + else if(k.KFCM(4,t,['្','យ','េ','ឺ'])){ + m=1; // Line 627 + k.KDC(4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(4,t,['្','យ','េ','ឹ'])){ + m=1; // Line 628 + k.KDC(4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(4,t,['្','យ','េ','ី'])){ + m=1; // Line 629 + k.KDC(4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_gen_14}])){ + m=1; // Line 308 + k.KDC(3,t); + k.KIO(-1,this.s_v_gen_14,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 309 + k.KDC(3,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 310 + k.KDC(3,t); + k.KIO(-1,this.s_v_gen_14,2,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 311 + k.KDC(3,t); + k.KIO(-1,this.s_v_gen_14,2,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + } + else if(k.KFCM(3,t,['្',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 320 + k.KDC(3,t); + k.KIO(-1,this.s_v_gen_14,2,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_any_19},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 324 + k.KDC(3,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,3,t); + k.KIO(-1,this.s_v_any_19,1,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_c_shifter_28},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 355 + k.KDC(3,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,3,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 360 + k.KDC(3,t); + k.KIO(-1,this.s_c_shifter_28,3,t); + k.KIO(-1,this.s_v_gen_14,1,t); + k.KIO(-1,this.s_v_pseudo_16,2,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_any_19},{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 361 + k.KDC(3,t); + k.KIO(-1,this.s_c_shifter_28,3,t); + k.KIO(-1,this.s_v_any_19,2,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 438 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_1st_48},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 440 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,2,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 445 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 447 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,2,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'៊',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 459 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_1st_48},'៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 476 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_c_out_12},{t:'a',a:this.s_v_gen_14},'៌'])){ + m=1; // Line 585 + k.KDC(3,t); + k.KIO(-1,this.s_c_out_12,1,t); + k.KO(-1,t,"៌"); + k.KIO(-1,this.s_v_gen_14,2,t); + } + else if(k.KFCM(3,t,['ណ','្','ត'])){ + m=1; // Line 589 + k.KDC(3,t); + k.KO(-1,t,"ណ"); + k.KO(-1,t,"្ដ"); + } + else if(k.KFCM(3,t,['ន','្','ដ'])){ + m=1; // Line 590 + k.KDC(3,t); + k.KO(-1,t,"ន"); + k.KO(-1,t,"្ត"); + } + else if(k.KFCM(3,t,['ទ','្','ប'])){ + m=1; // Line 594 + k.KDC(3,t); + k.KO(-1,t,"ឡ"); + } + else if(k.KFCM(3,t,['ប','្','ញ'])){ + m=1; // Line 596 + k.KDC(3,t); + k.KO(-1,t,"ឫ"); + } + else if(k.KFCM(3,t,['ព','្','ញ'])){ + m=1; // Line 602 + k.KDC(3,t); + k.KO(-1,t,"ឭ"); + } + else if(k.KFCM(3,t,['ព','្','ឋ'])){ + m=1; // Line 605 + k.KDC(3,t); + k.KO(-1,t,"ឰ"); + } + else if(k.KFCM(3,t,['ដ','្','ធ'])){ + m=1; // Line 613 + k.KDC(3,t); + k.KO(-1,t,"ដ្ឋ"); + } + else if(k.KFCM(3,t,['ទ','្','ឋ'])){ + m=1; // Line 614 + k.KDC(3,t); + k.KO(-1,t,"ទ្ធ"); + } + else if(k.KFCM(3,t,['ឪ','្','យ'])){ + m=1; // Line 622 + k.KDC(3,t); + k.KO(-1,t,"ឱ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"យ"); + } + else if(k.KFCM(3,t,['ឳ','្','យ'])){ + m=1; // Line 623 + k.KDC(3,t); + k.KO(-1,t,"ឱ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"យ"); + } + else if(k.KFCM(3,t,['ញ','្','វ'])){ + m=1; // Line 625 + k.KDC(3,t); + k.KO(-1,t,"ព"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វា"); + } + else if(k.KFCM(2,t,['េ','ា'])){ + m=1; // Line 291 + k.KDC(2,t); + k.KO(-1,t,"ោ"); + } + else if(k.KFCM(2,t,['ា','េ'])){ + m=1; // Line 292 + k.KDC(2,t); + k.KO(-1,t,"ោ"); + } + else if(k.KFCM(2,t,['េ','ី'])){ + m=1; // Line 293 + k.KDC(2,t); + k.KO(-1,t,"ើ"); + } + else if(k.KFCM(2,t,['ី','េ'])){ + m=1; // Line 294 + k.KDC(2,t); + k.KO(-1,t,"ើ"); + } + else if(k.KFCM(2,t,['ំ','ុ'])){ + m=1; // Line 298 + k.KDC(2,t); + k.KO(-1,t,"ុំ"); + } + else if(k.KFCM(2,t,['ំ','ា'])){ + m=1; // Line 299 + k.KDC(2,t); + k.KO(-1,t,"ាំ"); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_gen_14}])){ + m=1; // Line 304 + k.KDC(2,t); + k.KIO(-1,this.s_v_gen_14,2,t); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 313 + k.KDC(2,t); + k.KIO(-1,this.s_v_pseudo_16,2,t); + } + if(m) {} + else if(k.KFCM(2,t,['្','្'])){ + m=1; // Line 318 + k.KDC(2,t); + k.KO(-1,t,"្"); + } + else if(k.KFCM(2,t,['្',{t:'a',a:this.s_v_any_19}])){ + m=1; // Line 319 + k.KDC(2,t); + k.KIO(-1,this.s_v_any_19,2,t); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_any_19},{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 359 + k.KDC(2,t); + k.KIO(-1,this.s_c_shifter_28,2,t); + k.KIO(-1,this.s_v_any_19,1,t); + } + else if(k.KFCM(2,t,['ឫ','ុ'])){ + m=1; // Line 597 + k.KDC(2,t); + k.KO(-1,t,"ឬ"); + } + else if(k.KFCM(2,t,['ឭ','ា'])){ + m=1; // Line 599 + k.KDC(2,t); + k.KO(-1,t,"ញ"); + } + else if(k.KFCM(2,t,['ឮ','ា'])){ + m=1; // Line 600 + k.KDC(2,t); + k.KO(-1,t,"ញ"); + } + else if(k.KFCM(2,t,['ឭ','ុ'])){ + m=1; // Line 603 + k.KDC(2,t); + k.KO(-1,t,"ឮ"); + } + else if(k.KFCM(2,t,['ឧ','ិ'])){ + m=1; // Line 607 + k.KDC(2,t); + k.KO(-1,t,"ឱ"); + } + else if(k.KFCM(2,t,['ឧ','៌'])){ + m=1; // Line 608 + k.KDC(2,t); + k.KO(-1,t,"ឱ"); + } + else if(k.KFCM(2,t,['ឧ','៍'])){ + m=1; // Line 609 + k.KDC(2,t); + k.KO(-1,t,"ឱ"); + } + return r; + }; +} diff --git a/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.kmp b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.kmp new file mode 100644 index 00000000000..c3449523e39 Binary files /dev/null and b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.kmp differ diff --git a/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.kmx b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.kmx new file mode 100644 index 00000000000..d8495d187c4 Binary files /dev/null and b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/build/khmer_angkor.kmx differ diff --git a/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/khmer_angkor.kpj b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/khmer_angkor.kpj new file mode 100644 index 00000000000..4b239df20ce --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/khmer_angkor.kpj @@ -0,0 +1,187 @@ + + + + $PROJECTPATH\build + True + True + False + keyboard + + + + id_f347675c33d2e6b1c705c787fad4941a + khmer_angkor.kmn + source\khmer_angkor.kmn + 1.3 + .kmn +
+ Khmer Angkor + © 2015-2022 SIL International + More than just a Khmer Unicode keyboard. +
+
+ + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + khmer_angkor.kps + source\khmer_angkor.kps + + .kps +
+ Khmer Angkor + © 2015-2022 SIL International +
+
+ + id_8a1efc7c4ab7cfece8aedd847679ca27 + khmer_angkor.ico + source\khmer_angkor.ico + + .ico + id_f347675c33d2e6b1c705c787fad4941a + + + id_8dc195db32d1fd0514de0ad51fff5df0 + khmer_angkor.js + source\..\build\khmer_angkor.js + + .js + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_10596632fcbf4138d24bcccf53e6ae01 + khmer_angkor.kvk + source\..\build\khmer_angkor.kvk + + .kvk + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_0a851f95ce553ecd62cbee6c32ced68f + khmer_angkor.kmx + source\..\build\khmer_angkor.kmx + + .kmx + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_d8b6eb05f4b7e2945c10e04c1f49e4c8 + keyboard_layout.png + source\welcome\keyboard_layout.png + + .png + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_724e5b4c63f10bc0abf7077f7c3172fc + welcome.htm + source\welcome\welcome.htm + + .htm + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_35857cb2b54f123612735ec948400082 + FONTLOG.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\FONTLOG.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_7e3afe5bb59b888b08b48cd5817d8de4 + Mondulkiri-B.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-B.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_b9734e80f86c69ea5ae4dfa9f0083d09 + Mondulkiri-BI.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-BI.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_25abe4d2b0abc03a5be5b666a8de776e + Mondulkiri-I.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-I.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_b766568498108eee46ed1601ff69c47d + Mondulkiri-R.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_84544d04133cab3dbfc86b91ad1a4e17 + OFL.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\OFL.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_0c33fbefd1c20f487b1bea2343b3bb2c + OFL-FAQ.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\OFL-FAQ.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_a59d89fca36a310147645fa2604e521b + KAK_Documentation_EN.pdf + source\welcome\KAK_Documentation_EN.pdf + + .pdf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_5643c4cd3933b3ada0b4af6579305ec4 + KAK_Documentation_KH.pdf + source\welcome\KAK_Documentation_KH.pdf + + .pdf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_acb0dd94c60e345d999670e999cbd159 + image002.png + source\welcome\image002.png + + .png + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_4edf70bc019f05b5ad39a2ea727ad547 + khmer_busra_kbd.ttf + source\..\..\..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_bc823844e4399751e1867016801f7327 + splash.gif + source\splash.gif + + .gif + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + +
+
diff --git a/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/source/khmer_angkor.kps b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/source/khmer_angkor.kps new file mode 100644 index 00000000000..48ff90c6935 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/error_description_is_missing/source/khmer_angkor.kps @@ -0,0 +1,48 @@ + + + + 15.0.266.0 + 7.0 + + + ..\LICENSE.md + + + + Khmer Angkor + © 2015-2022 SIL International + Makara Sok + + https://keyman.com/keyboards/khmer_angkor + + + + ..\LICENSE.md + File LICENSE.md + 0 + .md + + + ..\build\khmer_angkor.kmx + Keyboard Khmer Angkor + 0 + .kmx + + + ..\build\khmer_angkor.js + Keyboard Khmer Angkor + 0 + .js + + + + + Khmer Angkor + khmer_angkor + 1.3 + + English + + + + diff --git a/developer/src/kmc-keyboard-info/test/keyboard-info-compiler-messages.tests.ts b/developer/src/kmc-keyboard-info/test/keyboard-info-compiler-messages.tests.ts index 379583944c0..79e5c488bcc 100644 --- a/developer/src/kmc-keyboard-info/test/keyboard-info-compiler-messages.tests.ts +++ b/developer/src/kmc-keyboard-info/test/keyboard-info-compiler-messages.tests.ts @@ -38,6 +38,31 @@ describe('KeyboardInfoCompilerMessages', function () { return verifyCompilerMessagesObject(KeyboardInfoCompilerMessages, CompilerErrorNamespace.KeyboardInfoCompiler); }); + async function testMessage(message: number, folderName: string, shouldPass: boolean) { + const jsFilename = makePathToFixture(folderName, 'build', 'khmer_angkor.js'); + const kpsFilename = makePathToFixture(folderName, 'source', 'khmer_angkor.kps'); + const kmpFilename = makePathToFixture(folderName, 'build', 'khmer_angkor.kmp'); + + const sources = { + kmpFilename, + sourcePath: 'release/t/test', + kpsFilename, + jsFilename, + forPublishing: true, + }; + + const compiler = new KeyboardInfoCompiler(); + assert.isTrue(await compiler.init(callbacks, {sources})); + const result = await compiler.run(kmpFilename, null); + if(shouldPass) { + assert.isNotNull(result); + } else { + assert.isNull(result); + } + + assert.isTrue(callbacks.hasMessage(message)); + } + // ERROR_FileDoesNotExist (loadJsFile) it('should generate ERROR_FileDoesNotExist error if .js file does not exist', async function() { @@ -202,7 +227,7 @@ describe('KeyboardInfoCompilerMessages', function () { const kmpFilename = makePathToFixture('no-kmp', 'build', 'khmer_angkor.kmp'); const sources = { - kmpFilename: '', + kmpFilename: '', // divergence from testMessage sourcePath: 'release/k/no-kmp', kpsFilename, jsFilename: jsFilename, @@ -221,25 +246,7 @@ describe('KeyboardInfoCompilerMessages', function () { // ERROR_NoLicenseFound it('should generate ERROR_NoLicenseFound error if licence file is not in .kps options', async function() { - const jsFilename = makePathToFixture('no-license-in-kps-sources', 'build', 'khmer_angkor.js'); - const kpsFilename = makePathToFixture('no-license-in-kps-sources', 'source', 'khmer_angkor.kps'); - const kmpFilename = makePathToFixture('no-license-in-kps-sources', 'build', 'khmer_angkor.kmp'); - - const sources = { - kmpFilename, - sourcePath: 'release/k/no-license-in-kps-sources', - kpsFilename, - jsFilename: jsFilename, - forPublishing: true, - }; - - const compiler = new KeyboardInfoCompiler(); - assert.isTrue(await compiler.init(callbacks, {sources})); - const result = await compiler.run(kmpFilename, null); - assert.isNull(result); - - assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_NoLicenseFound), - `ERROR_NoLicenseFound not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); + await testMessage(KeyboardInfoCompilerMessages.ERROR_NoLicenseFound, 'no-license-in-kps-sources', false); }); // ERROR_FontFileMetaDataIsInvalid @@ -267,55 +274,27 @@ describe('KeyboardInfoCompilerMessages', function () { assert.isNull(result); assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_FontFileMetaDataIsInvalid), `ERROR_FontFileMetaDataIsInvalid not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); - assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_FontFileMetaDataIsInvalid).includes(kmpJsonData.files[0].name), - kmpJsonData.files[0].name+' not found in the message'); + assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_FontFileMetaDataIsInvalid) + .includes("../shared/fonts/khmer/mondulkiri/font-meta-data-is-invalid.ttf"), + '../shared/fonts/khmer/mondulkiri/font-meta-data-is-invalid.ttf not found in the message'); }); // ERROR_InvalidAuthorEmail it('should generate ERROR_InvalidAuthorEmail error if multiple email addresses are listed in .kps', async function() { - const jsFilename = makePathToFixture('multiple-email-addresses', 'build', 'khmer_angkor.js'); - const kpsFilename = makePathToFixture('multiple-email-addresses', 'source', 'khmer_angkor.kps'); - const kmpFilename = makePathToFixture('multiple-email-addresses', 'build', 'khmer_angkor.kmp'); - - const sources = { - kmpFilename, - sourcePath: 'release/k/multiple-email-addresses', - kpsFilename, - jsFilename: jsFilename, - forPublishing: true, - }; - - const compiler = new KeyboardInfoCompiler(); - assert.isTrue(await compiler.init(callbacks, {sources})); - const result = await compiler.run(kmpFilename, null); - assert.isNull(result); - - assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_InvalidAuthorEmail), - `ERROR_InvalidAuthorEmail not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); + await testMessage(KeyboardInfoCompilerMessages.ERROR_InvalidAuthorEmail, 'multiple-email-addresses', false); }); // HINT_ScriptDoesNotMatch it('should generate HINT_ScriptDoesNotMatch if there is a mismatching language script in .kps', async function() { - const jsFilename = makePathToFixture('hint_script_does_not_match', 'build', 'khmer_angkor.js'); - const kpsFilename = makePathToFixture('hint_script_does_not_match', 'source', 'khmer_angkor.kps'); - const kmpFilename = makePathToFixture('hint_script_does_not_match', 'build', 'khmer_angkor.kmp'); - - const sources = { - kmpFilename, - sourcePath: 'release/h/hint_script_does_not_match', - kpsFilename, - jsFilename: jsFilename, - forPublishing: true, - }; + await testMessage(KeyboardInfoCompilerMessages.HINT_ScriptDoesNotMatch, 'hint_script_does_not_match', true); + }); - const compiler = new KeyboardInfoCompiler(); - assert.isTrue(await compiler.init(callbacks, {sources})); - const result = await compiler.run(kmpFilename, null); - assert.isNotNull(result); + // ERROR_DescriptionIsMissing - assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.HINT_ScriptDoesNotMatch)); + it('should generate ERROR_DescriptionIsMissing if there is no description in .kps', async function() { + await testMessage(KeyboardInfoCompilerMessages.ERROR_DescriptionIsMissing,'error_description_is_missing', false); }); }); diff --git a/developer/src/kmc-kmn/src/compiler/osk.ts b/developer/src/kmc-kmn/src/compiler/osk.ts index f2216273e8e..e26c124b15b 100644 --- a/developer/src/kmc-kmn/src/compiler/osk.ts +++ b/developer/src/kmc-kmn/src/compiler/osk.ts @@ -96,6 +96,13 @@ export function remapTouchLayout(source: TouchLayout.TouchLayoutFile, map: PuaMa for(const row of layer.row) { for(const key of row.key) { scanKey(key); + + if(key.hint && !key.hint.match(/^\*.+\*$/)) { + const hint = remap(key.hint, map); + dirty = dirty || hint != key.hint; + key.hint = hint; + } + let f: keyof TouchLayout.TouchLayoutFlick; for(f in key.flick ?? {}) { scanKey(key.flick[f]); diff --git a/developer/src/kmc-ldml/test/fixtures/sections/keys/import-local.xml b/developer/src/kmc-ldml/test/fixtures/sections/keys/import-local.xml new file mode 100644 index 00000000000..d97d711301d --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/sections/keys/import-local.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-ldml/test/fixtures/sections/keys/keys-Zyyy-morepunctuation.xml b/developer/src/kmc-ldml/test/fixtures/sections/keys/keys-Zyyy-morepunctuation.xml new file mode 100644 index 00000000000..768e81e7cd2 --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/sections/keys/keys-Zyyy-morepunctuation.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts index 3bb67278fbe..bd438c4ca11 100644 --- a/developer/src/kmc-ldml/test/helpers/index.ts +++ b/developer/src/kmc-ldml/test/helpers/index.ts @@ -38,7 +38,8 @@ export const compilerTestCallbacks = new TestCompilerCallbacks(); export const compilerTestOptions: LdmlCompilerOptions = { readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) + cldrImportsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [], // will be fixed up in loadSectionFixture } }; @@ -59,8 +60,14 @@ export async function loadSectionFixture(compilerClass: SectionCompilerNew, file const data = callbacks.loadFile(inputFilename); assert.isNotNull(data, `Failed to read file ${inputFilename}`); + compilerTestOptions.readerOptions.localImportsPaths = [ path.dirname(inputFilename) ]; + const reader = new LDMLKeyboardXMLSourceFileReader(compilerTestOptions.readerOptions, callbacks); const source = reader.load(data); + if (!source) { + // print any callbacks here + assert.sameDeepMembers(callbacks.messages, [], `Errors loading ${inputFilename}`); + } assert.isNotNull(source, `Failed to load XML from ${inputFilename}`); if (!reader.validate(source)) { diff --git a/developer/src/kmc-ldml/test/keys.tests.ts b/developer/src/kmc-ldml/test/keys.tests.ts index b639c194cc3..cfb37e6baf5 100644 --- a/developer/src/kmc-ldml/test/keys.tests.ts +++ b/developer/src/kmc-ldml/test/keys.tests.ts @@ -199,6 +199,19 @@ describe('keys', function () { assert.equal(flickw.flicks[0].keyId.value, 'dd'); }, }, + { + subpath: 'sections/keys/import-local.xml', + callback: (keys, subpath, callbacks) => { + assert.isNotNull(keys); + assert.equal((keys).keys.length, 2 + KeysCompiler.reserved_count); + const [snail] = (keys).keys.filter(({ id }) => id.value === 'snail'); + assert.ok(snail,`Missing the snail`); + assert.equal(snail.to.value, `@`, `Snail's value`); + const [interrobang] = (keys).keys.filter(({ id }) => id.value === 'interrobang'); + assert.ok(interrobang,`Missing the interrobang`); + assert.equal(interrobang.to.value, `‽`, `Interrobang's value`); + }, + }, ], keysDependencies); }); diff --git a/developer/src/kmc-package/test/fixtures/bcp47/invalid_bcp47_1.kps b/developer/src/kmc-package/test/fixtures/bcp47/invalid_bcp47_1.kps new file mode 100644 index 00000000000..8a656e38baa --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/bcp47/invalid_bcp47_1.kps @@ -0,0 +1,27 @@ + + + + 10.0.700.0 + 7.0 + + + 1.0 + valid_bcp47 + + + + test.kmx + + + + + Invalid BCP 47 + test + 1.0 + + Not a language tag + + + + + diff --git a/developer/src/kmc-package/test/fixtures/bcp47/test.kmx b/developer/src/kmc-package/test/fixtures/bcp47/test.kmx new file mode 100644 index 00000000000..9e4bd15d391 Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/bcp47/test.kmx differ diff --git a/developer/src/kmc-package/test/fixtures/bcp47/valid_bcp47.kps b/developer/src/kmc-package/test/fixtures/bcp47/valid_bcp47.kps new file mode 100644 index 00000000000..72461e04c28 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/bcp47/valid_bcp47.kps @@ -0,0 +1,39 @@ + + + + 10.0.700.0 + 7.0 + + + + + + + + + + + + 1.0 + valid_bcp47 + + + + test.kmx + + + + + Valid BCP 47 + test + 1.0 + + Central Khmer (Khmer, Cambodia) + SENCOTEN + Unregistered variant of Straits Salish + Private variant of "Old" Khmer + + + + + diff --git a/developer/src/kmc-package/test/package-compiler.tests.ts b/developer/src/kmc-package/test/package-compiler.tests.ts index 7952e2145a2..16c523bda9a 100644 --- a/developer/src/kmc-package/test/package-compiler.tests.ts +++ b/developer/src/kmc-package/test/package-compiler.tests.ts @@ -12,6 +12,7 @@ import { makePathToFixture } from './helpers/index.js'; import { KmpCompiler } from '../src/compiler/kmp-compiler.js'; import { PackageCompilerMessages } from '../src/compiler/package-compiler-messages.js'; +import { PackageValidation } from '../src/compiler/package-validation.js'; const debug = false; @@ -29,6 +30,13 @@ describe('KmpCompiler', function () { assert.isTrue(await kmpCompiler.init(callbacks, null)); }); + this.afterEach(function() { + if(this.currentTest?.isFailed()) { + callbacks.printMessages(); + } + callbacks.clear(); + }); + for (let modelID of MODELS) { const kpsPath = modelID.includes('withfolders') ? makePathToFixture(modelID, 'source', `${modelID}.model.kps`) : makePathToFixture(modelID, `${modelID}.model.kps`); @@ -284,4 +292,20 @@ describe('KmpCompiler', function () { assert.equal(kmpJson.keyboards[0].version, '4.0'); // picks up example.kmx's version }); + it(`should handle a range of valid BCP47 tags`, function () { + const inputFilename = makePathToFixture('bcp47', 'valid_bcp47.kps'); + const kmpJson = kmpCompiler.transformKpsToKmpObject(inputFilename); + assert.isNotNull(kmpJson); + const validation = new PackageValidation(callbacks, {}); + assert.isTrue(validation.validate(inputFilename, kmpJson)); + }); + + it(`should reject an invalid BCP47 tag`, function () { + const inputFilename = makePathToFixture('bcp47', 'invalid_bcp47_1.kps'); + const kmpJson = kmpCompiler.transformKpsToKmpObject(inputFilename); + assert.isNotNull(kmpJson); + const validation = new PackageValidation(callbacks, {}); + assert.isFalse(validation.validate(inputFilename, kmpJson)); + }); + }); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts b/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts index 6ac98c9b92e..6a6300b646b 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts @@ -4,6 +4,7 @@ import { CompilerOptions, CompilerCallbacks } from '@keymanapp/developer-utils'; import { LDMLKeyboardXMLSourceFileReader } from '@keymanapp/developer-utils'; import { BuildActivity } from './BuildActivity.js'; import { fileURLToPath } from 'url'; +import { dirname } from 'node:path'; export class BuildLdmlKeyboard extends BuildActivity { public get name(): string { return 'LDML keyboard'; } @@ -13,7 +14,8 @@ export class BuildLdmlKeyboard extends BuildActivity { public async build(infile: string, outfile: string, callbacks: CompilerCallbacks, options: CompilerOptions): Promise { // TODO-LDML: consider hardware vs touch -- touch-only layout will not have a .kvk const ldmlCompilerOptions: kmcLdml.LdmlCompilerOptions = {...options, readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) + cldrImportsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [ dirname(infile) ], // local dir }}; const compiler = new kmcLdml.LdmlKeyboardCompiler(); return await super.runCompiler(compiler, infile, outfile, callbacks, ldmlCompilerOptions); diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts index c958673c2fa..5343c81702d 100644 --- a/developer/src/kmc/src/commands/buildTestData/index.ts +++ b/developer/src/kmc/src/commands/buildTestData/index.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from 'url'; import { CommandLineBaseOptions } from 'src/util/baseOptions.js'; import { exitProcess } from '../../util/sysexits.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; +import { dirname } from 'node:path'; export async function buildTestData(infile: string, _options: any, commander: any): Promise { const options: CommandLineBaseOptions = commander.optsWithGlobals(); @@ -17,7 +18,8 @@ export async function buildTestData(infile: string, _options: any, commander: an saveDebug: false, shouldAddCompilerVersion: false, readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) + cldrImportsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [ dirname(infile) ], // local dir } }; diff --git a/developer/src/kmc/src/commands/copy.ts b/developer/src/kmc/src/commands/copy.ts index 9231ba3a5ec..2f22b24a0a4 100644 --- a/developer/src/kmc/src/commands/copy.ts +++ b/developer/src/kmc/src/commands/copy.ts @@ -27,8 +27,9 @@ export function declareCopy(program: Command) { * a .kpj file, e.g. ./keyboards/khmer_angkor/khmer_angkor.kpj * a local folder (with a .kpj file in it), e.g. ./keyboards/khmer_angkor * a cloud keyboard or lexical model, cloud:id, e.g. cloud:khmer_angkor - * a GitHub repository, optional branch, and path, github:owner/repo[:branch]:path - e.g. github:keyman-keyboards/khmer_angkor:main:/khmer_angkor.kpj + * a GitHub repository, branch, and path, [https://]github.com/owner/repo/tree/branch/path + e.g. https://github.com/keyman-keyboards/khmer_angkor/tree/main/khmer_angkor.kpj or + github.com/keymanapp/keyboards/tree/master/release/k/khmer_angkor `); } diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index da2ac61a37d..33536e936b4 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -162,7 +162,7 @@ gtestcompilertest = executable('gtest-compiler-tests', 'gtest-compiler.tests.cpp include_directories: inc, name_suffix: name_suffix, link_args: links + tests_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: [ icuuc_dep, gtest_dep, gmock_dep ], ) @@ -173,7 +173,7 @@ gtest_km_u16_test = executable('gtest-km_u16-tests', 'gtest-km_u16.tests.cpp', include_directories: inc, name_suffix: name_suffix, link_args: links + tests_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: [ icuuc_dep, gtest_dep, gmock_dep ], ) diff --git a/developer/src/kmcmplib/wasm.build.linux.in b/developer/src/kmcmplib/wasm.build.linux.in index 27cb7ba68fe..90a31bf75b9 100644 --- a/developer/src/kmcmplib/wasm.build.linux.in +++ b/developer/src/kmcmplib/wasm.build.linux.in @@ -2,3 +2,4 @@ c = ['$EMSCRIPTEN_BASE/emcc'] cpp = ['$EMSCRIPTEN_BASE/em++'] ar = ['$EMSCRIPTEN_BASE/emar'] +strip = ['$EMSCRIPTEN_BASE/emstrip'] diff --git a/developer/src/kmcmplib/wasm.build.mac.in b/developer/src/kmcmplib/wasm.build.mac.in index 27cb7ba68fe..90a31bf75b9 100644 --- a/developer/src/kmcmplib/wasm.build.mac.in +++ b/developer/src/kmcmplib/wasm.build.mac.in @@ -2,3 +2,4 @@ c = ['$EMSCRIPTEN_BASE/emcc'] cpp = ['$EMSCRIPTEN_BASE/em++'] ar = ['$EMSCRIPTEN_BASE/emar'] +strip = ['$EMSCRIPTEN_BASE/emstrip'] diff --git a/developer/src/kmcmplib/wasm.build.win.in b/developer/src/kmcmplib/wasm.build.win.in index 68c21fb00f5..2c9ed6b6128 100644 --- a/developer/src/kmcmplib/wasm.build.win.in +++ b/developer/src/kmcmplib/wasm.build.win.in @@ -2,3 +2,4 @@ c = ['python.exe', '$EMSCRIPTEN_BASE/emcc.py'] cpp = ['python.exe', '$EMSCRIPTEN_BASE/em++.py'] ar = ['python.exe', '$EMSCRIPTEN_BASE/emar.py'] +strip = ['python.exe', '$EMSCRIPTEN_BASE/emstrip.py'] diff --git a/developer/src/kmcmplib/wasm.defs.build b/developer/src/kmcmplib/wasm.defs.build index 4a598a1e1c1..9e0859b49ba 100644 --- a/developer/src/kmcmplib/wasm.defs.build +++ b/developer/src/kmcmplib/wasm.defs.build @@ -1,7 +1,8 @@ [binaries] -c = ['emcc.py'] -cpp = ['em++.py'] -ar = ['emar.py'] +c = ['emcc'] +cpp = ['em++'] +ar = ['emar'] +strip = ['emstrip'] exe_wrapper = 'node' [properties] diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj index d1c711368f1..045506b9af4 100644 --- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj +++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj @@ -25,8 +25,10 @@ 1645D5952036C6FF0076C51B /* KeymanPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1645D5942036C6FF0076C51B /* KeymanPackage.swift */; }; 1645D5972036C9F80076C51B /* KMPKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1645D5962036C9F80076C51B /* KMPKeyboard.swift */; }; 165EB3A12098993900040A69 /* KeyboardError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165EB3A02098993900040A69 /* KeyboardError.swift */; }; + 29084CAB2CD48B5D004070E7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29084CAA2CD48B5D004070E7 /* Images.xcassets */; }; 296EF2C72AFA26C700E3E384 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296EF2C62AFA26C700E3E384 /* ZIPFoundation.xcframework */; }; 29B30C232B564F9900C342A4 /* KeymanEngineLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B30C222B564F9900C342A4 /* KeymanEngineLogger.swift */; }; + 29E202BD2CCB7541008B4740 /* KeyboardHeightViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E202BC2CCB7541008B4740 /* KeyboardHeightViewController.swift */; }; 377D10DE26846B8900467431 /* SpacebarTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D10DD26846B8900467431 /* SpacebarTextViewController.swift */; }; 6CD5DFAA150F6DC8007A5DDE /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 6CD5DFA8150F6DC8007A5DDE /* icon.png */; }; 6CD5DFAB150F6DC8007A5DDE /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6CD5DFA9150F6DC8007A5DDE /* icon@2x.png */; }; @@ -298,6 +300,7 @@ 1645D5942036C6FF0076C51B /* KeymanPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeymanPackage.swift; sourceTree = ""; }; 1645D5962036C9F80076C51B /* KMPKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPKeyboard.swift; sourceTree = ""; }; 165EB3A02098993900040A69 /* KeyboardError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardError.swift; sourceTree = ""; }; + 29084CAA2CD48B5D004070E7 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 293EA3DB2705955300545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ha; path = ha.lproj/ResourceInfoView.strings; sourceTree = ""; }; 293EA3DC2705964200545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ha; path = ha.lproj/Localizable.strings; sourceTree = ""; }; 293EA3DD270596B700545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ha; path = ha.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -333,6 +336,7 @@ 29C1E17128001F7600759EDE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/ResourceInfoView.strings"; sourceTree = ""; }; 29C1E17228001F8800759EDE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; 29C1E17328001FA200759EDE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-PT"; path = "pt-PT.lproj/Localizable.stringsdict"; sourceTree = ""; }; + 29E202BC2CCB7541008B4740 /* KeyboardHeightViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardHeightViewController.swift; sourceTree = ""; }; 377D10DD26846B8900467431 /* SpacebarTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacebarTextViewController.swift; sourceTree = ""; }; 6C0A140E151EA930007FA4AD /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 6C0A140F151EA930007FA4AD /* Keyman.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; lineEnding = 0; path = Keyman.xcconfig; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.xcconfig; }; @@ -705,6 +709,7 @@ 9A60763C22892485003BCFBA /* Settings.storyboard */, 9A60764322893A4E003BCFBA /* SettingsViewController.swift */, 377D10DD26846B8900467431 /* SpacebarTextViewController.swift */, + 29E202BC2CCB7541008B4740 /* KeyboardHeightViewController.swift */, ); name = Settings; sourceTree = ""; @@ -760,6 +765,7 @@ CEA1486F2407808F00C6ECD2 /* Localizable.strings */, CE96E42D24D1229A005B8E5A /* Localizable.stringsdict */, CE7A26D023CEE5790005955C /* Keyboard Colors.xcassets */, + 29084CAA2CD48B5D004070E7 /* Images.xcassets */, C06D372E1F81F4E100F61AE0 /* Info.plist */, F273AB9615641D9300A47CEE /* Classes */, ); @@ -1260,6 +1266,7 @@ buildActionMask = 2147483647; files = ( C06D37601F82095200F61AE0 /* Keyman.bundle in Resources */, + 29084CAB2CD48B5D004070E7 /* Images.xcassets in Resources */, CEA1486C2407808F00C6ECD2 /* Localizable.strings in Resources */, 9ADC459F22E1895D004C78C6 /* LanguageLMDetailViewController.xib in Resources */, CEA14870240780E100C6ECD2 /* ResourceInfoView.xib in Resources */, @@ -1447,6 +1454,7 @@ CE67D961228A6F190029F2B5 /* KeyboardCommandStructs.swift in Sources */, CE87751E24C68DA500B1475A /* KeyboardSearchViewController.swift in Sources */, CE8B0BBF248764ED0045EB2E /* KMPResource.swift in Sources */, + 29E202BD2CCB7541008B4740 /* KeyboardHeightViewController.swift in Sources */, 9AD4F53C229F85AC007992D3 /* LanguageSettingsViewController.swift in Sources */, CE969BE8251AD8B500376D6A /* PackageWebViewController.swift in Sources */, CE7A26DB23CEEF640005955C /* Colors.swift in Sources */, diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift b/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift index 3b4ed5a1d62..01d28628381 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift @@ -41,6 +41,8 @@ public enum Key { static let synchronizeSWLexicalModel = "KeymanSynchronizeSWLexicalModel" static let migrationLevel = "KeymanEngineMigrationLevel" + static let portraitKeyboardHeight = "PortraitKeyboardHeight" + static let landscapeKeyboardHeight = "LandscapeKeyboardHeight" // JSON keys for language REST calls static let options = "options" @@ -93,6 +95,9 @@ public enum Defaults { public static let lexicalModel: InstallableLexicalModel = { return lexicalModelPackage.findResource(withID: lexicalModelID)! }() + + // default for ancient/unrecognized devices + static let unknownDeviceKeyboardHeight: CGFloat = 216.0 } public enum Resources { diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift b/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift index 5cfed55ea29..8991660dae3 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift @@ -331,7 +331,27 @@ public extension UserDefaults { set(prefs, forKey: Key.userCorrectSettings) } } - + + var portraitKeyboardHeight: Double { + get { + return double(forKey: Key.portraitKeyboardHeight) + } + + set(height) { + set(height, forKey: Key.portraitKeyboardHeight) + } + } + + var landscapeKeyboardHeight: Double { + get { + return double(forKey: Key.landscapeKeyboardHeight) + } + + set(height) { + set(height, forKey: Key.landscapeKeyboardHeight) + } + } + func predictSettingForLanguage(languageID: String) -> Bool { if let dict = predictionEnablements { return dict[languageID] ?? true diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift index 003a4e15a8f..16acd10f9ef 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift @@ -72,6 +72,7 @@ private class CustomInputView: UIInputView, UIInputViewAudioFeedback { } func setConstraints() { + os_log("CustomInputView setConstraints", log: KeymanEngineLogger.ui, type: .info) let innerView = keymanWeb.view! let guide = self.safeAreaLayoutGuide @@ -88,18 +89,48 @@ private class CustomInputView: UIInputView, UIInputViewAudioFeedback { kbdWidthConstraint.priority = .defaultHigh kbdWidthConstraint.isActive = true - let bannerHeight = InputViewController.topBarHeight + self.buildKeyboardHeightConstraints(bannerHeight: InputViewController.topBarHeight) + } + /** + * Due to new custom keyboard height as chosen by the user. + * The value for the new keyboard height originates from KeyboardHeightViewController. + */ + func keyboardHeightChanged() { + os_log("CustomInputView keyboardHeightChanged", log: KeymanEngineLogger.ui, type: .info) + + // deactivate constraints for both orientations (though one should already be inactive) + landscapeConstraint?.isActive = false + portraitConstraint?.isActive = false + + // rebuild both portrait and landscape constraints + self.buildKeyboardHeightConstraints(bannerHeight: InputViewController.topBarHeight) + + // activate constraints for the current orientation + if InputViewController.isPortrait { + portraitConstraint?.isActive = true + } else { + landscapeConstraint?.isActive = true + } + + self.setNeedsLayout() + } + + private func buildKeyboardHeightConstraints(bannerHeight: CGFloat) { + os_log("CustomInputView buildKeyboardHeightConstraints", log: KeymanEngineLogger.ui, type: .info) + let innerView = keymanWeb.view! + // Cannot be met by the in-app keyboard, but helps to 'force' height for the system keyboard. - let portraitHeight = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.constraintTargetHeight(isPortrait: true)) - portraitHeight.identifier = "Height constraint for portrait mode" - portraitHeight.priority = .defaultHigh - let landscapeHeight = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.constraintTargetHeight(isPortrait: false)) - landscapeHeight.identifier = "Height constraint for landscape mode" - landscapeHeight.priority = .defaultHigh - - portraitConstraint = portraitHeight - landscapeConstraint = landscapeHeight + let portraitHeightConstraint = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.readKeyboardHeight(isPortrait: true)!) + portraitHeightConstraint.identifier = "Height constraint for portrait mode" + portraitHeightConstraint.priority = .defaultHigh + + let landscapeHeightConstraint = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.readKeyboardHeight(isPortrait: false)!) + landscapeHeightConstraint.identifier = "Height constraint for landscape mode" + landscapeHeightConstraint.priority = .defaultHigh + + portraitConstraint = portraitHeightConstraint + landscapeConstraint = landscapeHeightConstraint // .isActive will be set according to the current portrait/landscape perspective. } @@ -153,7 +184,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { } var expandedHeight: CGFloat { - return keymanWeb.keyboardHeight + InputViewController.topBarHeight + return keymanWeb.keyboardSize.height + InputViewController.topBarHeight } public convenience init() { @@ -504,7 +535,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { } public var kmwHeight: CGFloat { - return keymanWeb.keyboardHeight + return keymanWeb.keyboardSize.height } func clearModel() { @@ -512,13 +543,24 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { } private func setInnerConstraints() { - let iv = self.inputView as! CustomInputView - iv.setConstraints() + let customInputView = self.inputView as! CustomInputView + customInputView.setConstraints() self.updateViewConstraints() fixLayout() } + /** + * Due to new custom keyboard height as chosen by the user. + * The value for the new keyboard height originates from KeyboardHeightViewController. + */ + func keyboardHeightChanged() { + os_log("InputViewController keyboardHeightChanged", log: KeymanEngineLogger.ui, type: .debug) + if let customInputView = self.inputView as? CustomInputView { + customInputView.keyboardHeightChanged() + } + } + func fixLayout() { view.setNeedsLayout() view.layoutIfNeeded() diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift index c18fb8c7ad8..0335a1035c7 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift @@ -217,6 +217,8 @@ class KeyboardScaleMap { screenSize: CGSize = UIScreen.main.bounds.size, asPhone: Bool? = nil) -> KeyboardSize? { if let scaling = shared.scalings[KeyboardScaleMap.hashKey(for: device)] { + let kbHeight = (forPortrait ? scaling.portrait : scaling.landscape).keyboardHeight + os_log("KeyboardScaleMap getDeviceDefaultKeyboardScale keyboard height: %f", log:KeymanEngineLogger.ui, type: .debug, kbHeight) return forPortrait ? scaling.portrait : scaling.landscape } @@ -240,6 +242,9 @@ class KeyboardScaleMap { // keyboard dimensions. It's not a perfect rule, but should suffice for a stop-gap solution. let scaling = shared.scalings[KeyboardScaleMap.hashKey(for: mappedDevice)]! + let kbHeight = (forPortrait ? scaling.portrait : scaling.landscape).keyboardHeight + os_log("KeyboardScaleMap getDeviceDefaultKeyboardScale keyboard height for missing device: %f", log:KeymanEngineLogger.ui, type: .debug, kbHeight) + return forPortrait ? scaling.portrait : scaling.landscape } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift index 50a22e6fe45..c101ebd8324 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift @@ -102,6 +102,7 @@ class KeymanWebViewController: UIViewController { // after it has been replaced by KMW's OSK resizing operation.) keyboardSize = view.bounds.size + os_log("KeymanWebViewController viewWillLayoutSubviews to keyboardSize %{public}s", log:KeymanEngineLogger.ui, type: .debug, NSCoder.string(for:keyboardSize)) } open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -695,36 +696,100 @@ extension KeymanWebViewController: KeymanWebDelegate { // MARK: - Manage views extension KeymanWebViewController { // MARK: - Sizing - public var keyboardHeight: CGFloat { - return keyboardSize.height - } @objc func menuKeyHeld(_ keymanWeb: KeymanWebViewController) { self.delegate?.menuKeyHeld(self) } - func constraintTargetHeight(isPortrait: Bool) -> CGFloat { - return KeyboardScaleMap.getDeviceDefaultKeyboardScale(forPortrait: isPortrait)?.keyboardHeight ?? 216 // default for ancient devices - } + func determineDefaultKeyboardHeight(isPortrait: Bool) -> CGFloat { + os_log("determineDefaultKeyboardHeight", log:KeymanEngineLogger.ui, type: .info) - var keyboardWidth: CGFloat { - return keyboardSize.width + let keyboardHeight = KeyboardScaleMap.getDeviceDefaultKeyboardScale(forPortrait: isPortrait)?.keyboardHeight ?? Defaults.unknownDeviceKeyboardHeight + + return keyboardHeight; } func initKeyboardSize() { - var width: CGFloat - var height: CGFloat + let width: CGFloat width = UIScreen.main.bounds.width - - if Util.isSystemKeyboard { - height = constraintTargetHeight(isPortrait: InputViewController.isPortrait) + var height: CGFloat + + // get orientation differently if system or in-app keyboard + let portrait = Util.isSystemKeyboard ? InputViewController.isPortrait : UIDevice.current.orientation.isPortrait + + /** + * If keyboard height is saved in UserDefaults, then use it + */ + if let savedHeight = self.readKeyboardHeight(isPortrait: portrait) { + height = savedHeight } else { - height = constraintTargetHeight(isPortrait: UIDevice.current.orientation.isPortrait) + /** + * Otherwise, get default keyboard height for this orientation and write to UserDefaults + */ + height = self.determineDefaultKeyboardHeight(isPortrait: portrait) + self.writeKeyboardHeightIfDoesNotExist(isPortrait: portrait, height: height) + + /** + * If we need to write out the keyboard height for one orientation, then we + * expect that the other must be written also. + * Write it out now, but only if a value for keyboard height does not already exist. + */ + height = self.determineDefaultKeyboardHeight(isPortrait: !portrait) + self.writeKeyboardHeightIfDoesNotExist(isPortrait: !portrait, height: height) } + + /** + * no need to check for Util.isSystemKeyboard because this is shared storage + * the UserDefaults are readable and writeable by both system and in-app + */ - keyboardSize = CGSize(width: width, height: height) + self.keyboardSize = CGSize(width: width, height: height) + os_log("KeymanWebViewController initKeyboardSize %{public}s", log:KeymanEngineLogger.ui, type: .default, NSCoder.string(for:keyboardSize)) } + /** + * reads and returns keyboard height if it is found in UserDefaults, otherwise returns nil + */ + func readKeyboardHeight(isPortrait: Bool) -> CGFloat? { + var height: CGFloat? = nil + + if (isPortrait) { + if (Storage.active.userDefaults.object(forKey: Key.portraitKeyboardHeight) != nil) { + height = Storage.active.userDefaults.portraitKeyboardHeight + } + } else { // landscape + if (Storage.active.userDefaults.object(forKey: Key.landscapeKeyboardHeight) != nil) { + height = Storage.active.userDefaults.landscapeKeyboardHeight + } + } + + let message = "readKeyboardHeight, for isPortrait \(isPortrait) value \(String(describing: height))" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .info, message) + + return height; + } + + /** + * Write out the keyboard height to the UserDefaults but only if it does not exist there yet. + * If it exists, then we assume it was configured by the user and do not want to + * overwrite that value with a default value derived for this device. + */ + func writeKeyboardHeightIfDoesNotExist(isPortrait: Bool, height: CGFloat) { + let writeMessage = "writeKeyboardHeightIfDoesNotExist, isPortrait: \(isPortrait) height: \(height)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .info, writeMessage) + if (isPortrait) { + if (Storage.active.userDefaults.object(forKey: Key.portraitKeyboardHeight) == nil) { + Storage.active.userDefaults.portraitKeyboardHeight = height + os_log("portrait keyboardHeight default value written", log:KeymanEngineLogger.ui, type: .info, writeMessage) + } + } else { + if (Storage.active.userDefaults.object(forKey: Key.landscapeKeyboardHeight) == nil) { + Storage.active.userDefaults.landscapeKeyboardHeight = height + os_log("landscape keyboardHeight default value written", log:KeymanEngineLogger.ui, type: .info, writeMessage) + } + } + } + var keyboardSize: CGSize { get { if kbSize.equalTo(CGSize.zero) { @@ -750,15 +815,6 @@ extension KeymanWebViewController { } } - @objc func resizeDelay() { - // + 1000 to work around iOS bug with resizing on landscape orientation. Technically we only - // need this for landscape but it doesn't hurt to do it with both. 1000 is a big number that - // should hopefully work on all devices. - let kbWidth = keyboardWidth - let kbHeight = keyboardHeight - view.frame = CGRect(x: 0.0, y: 0.0, width: kbWidth, height: kbHeight + 1000) - } - // Keyman interaction func resizeKeyboard() { fixLayout() @@ -768,6 +824,7 @@ extension KeymanWebViewController { // the first time. setOskWidth(Int(kbSize.width)) setOskHeight(Int(kbSize.height)) + os_log("KeymanWebViewController resizeKeyboard to kbSize %{public}s", log:KeymanEngineLogger.ui, type: .debug, NSCoder.string(for:kbSize)) } func resetKeyboardState() { diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardHeightViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardHeightViewController.swift new file mode 100644 index 00000000000..4326dc52d49 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardHeightViewController.swift @@ -0,0 +1,440 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Created by Shawn Schantz on 2024-10-25. + * + * View controller for adjusting the height of the keyboard + */ + +import Foundation +import UIKit +import os.log + +class KeyboardHeightViewController: UIViewController { + let contentView: UIView = UIView() + let defaultButton = UIButton(type: .roundedRect) + let label: UILabel = UILabel() + let keyboardImage: UIImageView = UIImageView() + let keyboardResizer: UIImageView = UIImageView() + var keyboardConstraintsArray: [NSLayoutConstraint] = [] + var resizerConstraintsArray: [NSLayoutConstraint] = [] + var isPortrait = true + var keyboardHeight = 0.0 + var defaultPortraitHeight = 0.0 + var defaultLandscapeHeight = 0.0 + var minKeyboardHeight = 10.0 + var maxKeyboardHeight = 10.0 + + public init() { + super.init(nibName: nil, bundle: nil) + _ = view + } + + override func viewDidLoad() { + super.viewDidLoad() + + os_log("KeyboardHeightViewController viewDidLoad", log:KeymanEngineLogger.ui, type: .info) + + self.determineOrientation() + self.determineDefaultKeyboardHeights() + self.applyKeyboardHeight() + + title = NSLocalizedString("adjust-keyboard-height-title", bundle: engineBundle, comment: "") + navigationItem.setHidesBackButton(false, animated: true) + navigationItem.leftBarButtonItem?.isEnabled = true + + navigationController?.toolbar?.barTintColor = UIColor.orange + + self.configureContentView() + self.configureDefaultHeightButton() + self.configureLabel() + self.configureKeyboardImage() + self.updateKeyboardImage() + self.configureKeyboardConstraints() + self.configureKeyboardResizer() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.calculateKeyboardHeightLimits() + } + + private func determineOrientation() { + self.isPortrait = UIScreen.main.bounds.height > UIScreen.main.bounds.width + let message = "determineOrientation, isPortrait: \(self.isPortrait)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message) + } + + private func determineDefaultKeyboardHeights() { + // if no KeyboardScaleMap found for device, then default to Defaults.defaultUnknownKeyboardHeight + let portraitKeyboardScale = KeyboardScaleMap.getDeviceDefaultKeyboardScale(forPortrait: true) + self.defaultPortraitHeight = Double(portraitKeyboardScale?.keyboardHeight ?? Defaults.unknownDeviceKeyboardHeight) + + let landscapeKeyboardScale = KeyboardScaleMap.getDeviceDefaultKeyboardScale(forPortrait: false) + self.defaultLandscapeHeight = Double(landscapeKeyboardScale?.keyboardHeight ?? Defaults.unknownDeviceKeyboardHeight) + } + + /** + * Read the previously set keyboard heights from UserDefaults. + * Running the first time, these values would be defaults set in KeymanWebviewController.initKeyboardSize() + * These values should be set prior to now; if not, log an error and use default values + */ + private func applyKeyboardHeight() { + if (self.isPortrait) { + if (Storage.active.userDefaults.object(forKey: Key.portraitKeyboardHeight) != nil) { + self.keyboardHeight = Storage.active.userDefaults.portraitKeyboardHeight + let message = "applyKeyboardHeight, from UserDefaults loaded portrait value \(self.keyboardHeight)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message) + } else { + self.keyboardHeight = self.defaultPortraitHeight + let message = "applyKeyboardHeight, portraitHeight not found in UserDefaults, using default value \(self.keyboardHeight)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .error, message) + } + } else { + if (Storage.active.userDefaults.object(forKey: Key.portraitKeyboardHeight) != nil) { + self.keyboardHeight = Storage.active.userDefaults.landscapeKeyboardHeight + let message = "applyKeyboardHeight, from UserDefaults loaded landscape value \(self.keyboardHeight)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message) + } else { + self.keyboardHeight = self.defaultLandscapeHeight + let message = "applyKeyboardHeight, landscapeHeight not found in UserDefaults, using default value \(self.keyboardHeight)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .error, message) + } + } + } + + + /** + * Set some reasonable limits on how small or large a keyboard can be set to during resizing. + */ + private func calculateKeyboardHeightLimits() { + // minimum height + self.minKeyboardHeight = 100.0 + + // set maxKeyboardHeight to 100 pts smaller than the contentView height + self.maxKeyboardHeight = contentView.frame.height - 100.0 + + let message = "minKeyboardHeight: \(minKeyboardHeight) maxKeyboardHeight: \(maxKeyboardHeight)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message) + } + + /** + * Given the vertical attemptedTranslation in points due to the user dragging the keyboard resizer view, + * return the amount that the user is allowed to grow or shrink the keyboard without exceeding its size limits. + */ + private func applyKeyboardSizeLimits(attemptedTranslation: Double) -> Double { + guard attemptedTranslation != 0 else { + return 0 + } + + // Are we trying to reduce or enlarge the keyboard? + // An increase in keyboardResizer .y translation moves it down and reduces the keyboard height. + let reducingKeyboardHeight = attemptedTranslation > 0 + + // by default, unless we exceed keyboard height limitations, we will approve all of the translation + var approvedTranslation = attemptedTranslation + + // Calculate new proposed height by subtracting the translation. + let proposedKeyboardHeight = self.keyboardHeight - attemptedTranslation + + // calculations vary depending on reducing or enlarging the keyboard + if (reducingKeyboardHeight) { + // if we are reducing too much, then calculate how much translation is allowed + if (proposedKeyboardHeight < self.minKeyboardHeight) { + // if we are already at the minimum size, return 0 + if(self.keyboardHeight <= self.minKeyboardHeight) { + approvedTranslation = 0; + } else { + // how much space to translate the resizer downward, a positive number + let availableToReduce = self.keyboardHeight - self.minKeyboardHeight + approvedTranslation = availableToReduce + } + } + } else { // enlarging the keyboard height + if (proposedKeyboardHeight > self.maxKeyboardHeight) { + // if we are already at the maximum size, return 0 + if(self.keyboardHeight >= self.maxKeyboardHeight) { + approvedTranslation = 0; + } else { + // how much space to translate the resizer upward, a negative number + let availableToEnlarge = self.keyboardHeight - self.maxKeyboardHeight + approvedTranslation = availableToEnlarge + } + } + } + + // round to nearest whole number + approvedTranslation = approvedTranslation.rounded(.toNearestOrAwayFromZero) + let messageBegan = "approvedTranslation: \(approvedTranslation)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, messageBegan) + return approvedTranslation + } + + /** + * Called when the keyboardResizer view is being moved to adjust the keyboard height. + */ + @objc func handleDrag(_ dragRecognizer: UIPanGestureRecognizer) { + switch dragRecognizer.state { + case .began: + os_log("keyboardResizer handleDrag began", log:KeymanEngineLogger.ui, type: .info) + + case .changed: + self.provideResizeFeedback(drag: dragRecognizer) + + case .ended: + // reset transforms before actual keyboard height change + self.keyboardResizer.transform = .identity + self.keyboardImage.transform = .identity + + let verticalTranslation = dragRecognizer.translation(in: self.contentView).y + let approvedTranslation = self.applyKeyboardSizeLimits(attemptedTranslation: verticalTranslation) + if (approvedTranslation != 0) { + let newKeyboardHeight = keyboardHeight - approvedTranslation; + self.writeNewKeyboardHeight(newHeight: newKeyboardHeight) + } else { + os_log("handleDrag .ended with no resizing", log:KeymanEngineLogger.ui, type: .info) + } + default: break + } + } + + /** + * Stretch the keyboard image and move the keyboardResizier object as it is being resized. + */ + private func provideResizeFeedback(drag: UIPanGestureRecognizer) { + // how much did we drag vertically + let verticalTranslation = drag.translation(in: self.contentView).y + + // enforce keyboard size limits on translation + let approvedTranslation = self.applyKeyboardSizeLimits(attemptedTranslation: verticalTranslation) + + if (approvedTranslation != 0) { + let keyboardTranslation = CGAffineTransform(translationX: 0, y: approvedTranslation) + self.keyboardResizer.transform = keyboardTranslation + + let newKeyboardHeight = self.keyboardHeight - approvedTranslation; + let yScaleFactor = newKeyboardHeight/self.keyboardHeight + + let keyboardScale = CGAffineTransform(scaleX: 1, y: yScaleFactor) + + // concatenate both the translate and the scale for the keyboard image + let keyboardHeightTranslation = CGAffineTransform(translationX: 0, y: approvedTranslation/2) + let keyboardImageTransform = keyboardScale.concatenating(keyboardHeightTranslation) + self.keyboardImage.transform = keyboardImageTransform + } + } + + /** + * Write the new height of the keyboard to to UserDefaults. + */ + private func writeNewKeyboardHeight (newHeight: Double) { + let message = "writeNewKeyboardHeight, newHeight :\(newHeight) isPortrait: \(isPortrait)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .info, message) + + keyboardHeight = newHeight + self.updateKeyboardConstraints() + + if (isPortrait) { + Storage.active.userDefaults.portraitKeyboardHeight = newHeight + } else { + Storage.active.userDefaults.landscapeKeyboardHeight = newHeight + } + } + + private func configureKeyboardConstraints() { + // create constraints for each orientation and apply when device is rotated + self.keyboardConstraintsArray = [ + keyboardImage.leftAnchor.constraint(equalTo: contentView.leftAnchor), + keyboardImage.rightAnchor.constraint(equalTo: contentView.rightAnchor), + keyboardImage.topAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -keyboardHeight), + keyboardImage.heightAnchor.constraint(equalToConstant: keyboardHeight) + ] + NSLayoutConstraint.activate(self.keyboardConstraintsArray) + } + + /** + * recalculate constraints for new portrait keyboard height + */ + private func updateKeyboardConstraints() { + if (!self.keyboardConstraintsArray.isEmpty) { + NSLayoutConstraint.deactivate(self.keyboardConstraintsArray) + } + self.keyboardConstraintsArray = [ + keyboardImage.leftAnchor.constraint(equalTo: contentView.leftAnchor), + keyboardImage.rightAnchor.constraint(equalTo: contentView.rightAnchor), + keyboardImage.topAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -self.keyboardHeight), + keyboardImage.heightAnchor.constraint(equalToConstant: self.keyboardHeight) + ] + NSLayoutConstraint.activate(self.keyboardConstraintsArray) + + self.updateKeyboardResizerConstraints() + } + + private func configureContentView() { + contentView.isUserInteractionEnabled = true + contentView.backgroundColor = .white + view.addSubview(contentView) + + contentView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + contentView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), + contentView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), + contentView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + } + + func configureDefaultHeightButton() { + defaultButton.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold) + let buttonTitle = NSLocalizedString("button-label-reset-default-keyboard-height", bundle: engineBundle, comment: "") + defaultButton.setTitle(buttonTitle, for: .normal) + defaultButton.sizeToFit() + defaultButton.layer.cornerRadius = 8.0 + defaultButton.addTarget(self, action: #selector(self.restoreDefaultKeyboardHeight), for: .touchUpInside) + contentView.addSubview(defaultButton) + + defaultButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + defaultButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10.0), + defaultButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10.0) + ]) + } + + @objc private func restoreDefaultKeyboardHeight() { + os_log("restored to default", log:KeymanEngineLogger.ui, type: .info) + let defaultHeight = self.isPortrait ? self.defaultPortraitHeight : self.defaultLandscapeHeight + + UIView.animate(withDuration: 1.1, delay: 0.1, usingSpringWithDamping: 0.35, initialSpringVelocity: 4.5, options: .curveEaseOut, animations: { + self.writeNewKeyboardHeight(newHeight: defaultHeight) + self.view.layoutIfNeeded() + }, completion: nil) + } + + private func configureLabel() { + label.backgroundColor = .white + label.textColor = .black + label.textAlignment = NSTextAlignment.left + label.font = UIFont.systemFont(ofSize: 16.0) + self.setLabelTextForOrientation() + label.numberOfLines = 0 + contentView.addSubview(label) + + label.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: self.defaultButton.bottomAnchor, constant: 5.0), + label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10.0), + label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant:-25.0), + label.heightAnchor.constraint(greaterThanOrEqualToConstant: 50.0) + ]) + } + + private func setLabelTextForOrientation() { + let dragText = NSLocalizedString("keyboard-drag-instructions", bundle: engineBundle, comment: "") + var rotateText: String? = nil; + + if (self.isPortrait) { + rotateText = NSLocalizedString("portrait-keyboard-rotate-instructions", bundle: engineBundle, comment: "") + } else { + rotateText = NSLocalizedString("landscape-keyboard-rotate-instructions", bundle: engineBundle, comment: "") + } + + label.text = "\(dragText)\n\(rotateText!)" + } + + private func configureKeyboardImage() { + keyboardImage.isUserInteractionEnabled = true + keyboardImage.contentMode = UIView.ContentMode.scaleToFill + keyboardImage.backgroundColor=UIColor.cyan + contentView.addSubview(keyboardImage) + + keyboardImage.translatesAutoresizingMaskIntoConstraints = false + } + + private func configureKeyboardResizer() { + var arrowSymbolImage: UIImage? = nil + keyboardResizer.isUserInteractionEnabled = true + + // if using iOS earlier than 13.0, use bitmap image of arrow and non-dynamic gray + if #available(iOSApplicationExtension 13.0, *) { + arrowSymbolImage = UIImage(systemName: "arrow.up.and.down.square.fill") + keyboardResizer.backgroundColor = UIColor.systemGray3 // adapts for light/dark mode + } else { + arrowSymbolImage = UIImage(named:"arrow.up.and.down", in:engineBundle, compatibleWith:nil) + keyboardResizer.backgroundColor = UIColor.systemGray + } + keyboardResizer.image = arrowSymbolImage + keyboardResizer.layer.cornerRadius = 5 + contentView.addSubview(keyboardResizer) + keyboardResizer.translatesAutoresizingMaskIntoConstraints = false + + self.updateKeyboardResizerConstraints() + + let drag = UIPanGestureRecognizer(target: self, action: #selector(handleDrag)) + drag.maximumNumberOfTouches = 1 + drag.minimumNumberOfTouches = 1 + keyboardResizer.addGestureRecognizer(drag) + } + + private func updateKeyboardResizerConstraints() { + // first, clear existing constraints + if (!resizerConstraintsArray.isEmpty) { + NSLayoutConstraint.deactivate(resizerConstraintsArray) + } + + // add new constraints using the current value of the keyboard height + resizerConstraintsArray = [ + keyboardResizer.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + keyboardResizer.topAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -keyboardHeight-30), + keyboardResizer.heightAnchor.constraint(equalToConstant: 60), + keyboardResizer.widthAnchor.constraint(equalToConstant: 60) + ] + NSLayoutConstraint.activate(resizerConstraintsArray) + } + + private func updateKeyboardImage() { + var kbImage: UIImage? = nil + if (self.isPortrait) { + kbImage = UIImage(named:"keyboard.compact", in:engineBundle, compatibleWith:nil) + } else { + kbImage = UIImage(named:"keyboard.regular", in:engineBundle, compatibleWith:nil) + } + keyboardImage.image = kbImage + + let kbImageMessage = "updateKeyboardImage, kbImage: \(String(describing: kbImage))" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, kbImageMessage) + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + let rotateMessage = "viewWillTransition to size \(size) with current UIScreen.main.bounds.size size = \(UIScreen.main.bounds.size)" + os_log("%{public}s", log:KeymanEngineLogger.ui, type: .info, rotateMessage) + super.viewWillTransition(to: size, with: coordinator) + + /** + determine which orientation the device had rotated to and adjust content as necessary + */ + self.determineOrientation() + + coordinator.animate(alongsideTransition: { _ in + self.applyKeyboardHeight() + self.calculateKeyboardHeightLimits() + self.setLabelTextForOrientation() + self.updateKeyboardImage() + self.updateKeyboardConstraints() + }, completion: nil) + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if (traitCollection.userInterfaceStyle == .dark) { + os_log("keyboardHeight traitCollectionDidChange to .dark", log:KeymanEngineLogger.ui, type: .debug) + } else { + os_log("keyboardHeight traitCollectionDidChange to .light", log:KeymanEngineLogger.ui, type: .debug) + } + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index d1d134b2494..4ab0da660b6 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -926,6 +926,11 @@ public class Manager: NSObject, UIGestureRecognizerDelegate { inputViewController.setContextState(text: text, range: range) } + func keyboardHeightChanged() { + os_log("Manager keyboardHeightChanged", log:KeymanEngineLogger.settings, type: .default) + self.inputViewController.keyboardHeightChanged() + } + var vibrationSupportLevel: VibrationSupport { let device = Device.current diff --git a/ios/engine/KMEI/KeymanEngine/Classes/SettingsViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/SettingsViewController.swift index 543453bb0c1..3104cba43f2 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/SettingsViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/SettingsViewController.swift @@ -12,6 +12,8 @@ import os.log open class SettingsViewController: UITableViewController { private var itemsArray = [[String: String]]() private var userLanguages: [String: Language] = [:] + private var previousPortraitKeyboardHeight: Double = 0.0 + private var previousLandscapeKeyboardHeight: Double = 0.0 override open func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -32,9 +34,16 @@ open class SettingsViewController: UITableViewController { } @objc func doneClicked(_ sender: Any) { - // While the called method might should be renamed, it does the job well enough. // This resets KMW so that any new and/or updated resources can be properly loaded. + os_log("SettingsViewController doneClicked", log:KeymanEngineLogger.settings, type: .info) Manager.shared.dismissKeyboardPicker(self) + + let newPortraitHeight = Storage.active.userDefaults.portraitKeyboardHeight + let newLandscapeHeight = Storage.active.userDefaults.landscapeKeyboardHeight + + if ((previousPortraitKeyboardHeight != newPortraitHeight) || (previousLandscapeKeyboardHeight != newLandscapeHeight)) { + Manager.shared.keyboardHeightChanged() + } } open func launchSettings(launchingVC: UIViewController, sender: Any?) -> Void { @@ -53,7 +62,8 @@ open class SettingsViewController: UITableViewController { public init(/*storage: Storage*/) { // self.storage = storage super.init(nibName: nil, bundle: nil) - + os_log("init settings", log:KeymanEngineLogger.settings, type: .default) + itemsArray = [[String: String]]() itemsArray.append([ "title": NSLocalizedString("menu-installed-languages-title", bundle: engineBundle, comment: ""), @@ -79,6 +89,12 @@ open class SettingsViewController: UITableViewController { "reuseid": "spacebartext" ]) + itemsArray.append([ + "title": NSLocalizedString("menu-settings-adjust-keyboard-height", bundle: engineBundle, comment: ""), + "subtitle": "", + "reuseid": "adjustkeyboardheight" + ]) + if let _ = URL(string: UIApplication.openSettingsURLString) { itemsArray.append([ "title": NSLocalizedString("menu-settings-system-keyboard-menu", bundle: engineBundle, comment: ""), @@ -176,7 +192,7 @@ open class SettingsViewController: UITableViewController { enableReportingSwitch.rightAnchor.constraint(equalTo: cell.layoutMarginsGuide.rightAnchor).isActive = true enableReportingSwitch.centerYAnchor.constraint(equalTo: cell.layoutMarginsGuide.centerYAnchor).isActive = true - case "systemkeyboardsettings", "installfile", "forcederror", "spacebartext": + case "systemkeyboardsettings", "installfile", "forcederror", "spacebartext", "adjustkeyboardheight": break default: let message = "unknown cellIdentifier(\"\(cellIdentifier ?? "EMPTY")\")" @@ -233,6 +249,10 @@ open class SettingsViewController: UITableViewController { cell.detailTextLabel?.text = NSLocalizedString("menu-settings-spacebar-hint-"+Manager.shared.spacebarText.rawValue, bundle: engineBundle, comment: "") cell.detailTextLabel?.isEnabled = true break + case "adjustkeyboardheight": + cell.accessoryType = .disclosureIndicator + cell.detailTextLabel?.isEnabled = false + break case "showbanner", "showgetstarted": cell.detailTextLabel?.isEnabled = false default: @@ -280,6 +300,8 @@ open class SettingsViewController: UITableViewController { SentryManager.forceError() case "spacebartext": showSpacebarText() + case "adjustkeyboardheight": + showAdjustKeyboardHeight() default: break } @@ -429,4 +451,21 @@ open class SettingsViewController: UITableViewController { } } + func showAdjustKeyboardHeight() { + let vc = KeyboardHeightViewController() + if let nc = navigationController { + self.previousPortraitKeyboardHeight = Storage.active.userDefaults.portraitKeyboardHeight + self.previousLandscapeKeyboardHeight = Storage.active.userDefaults.landscapeKeyboardHeight + + let message = "selected Adjust Keyboard Height, previousPortraitKeyboardHeight: \(previousPortraitKeyboardHeight), previousLandscapeKeyboardHeight: \(previousLandscapeKeyboardHeight)" + os_log("%{public}s", log:KeymanEngineLogger.settings, type: .default, message) + + nc.pushViewController(vc, animated: true) + setIsDoneButtonEnabled(nc, true) + } else { + let message = ("No navigation controller for showing keyboard height view") + os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message) + SentryManager.capture(message) + } + } } diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/Contents.json b/ios/engine/KMEI/KeymanEngine/Images.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/arrow.up.and.down.imageset/Contents.json b/ios/engine/KMEI/KeymanEngine/Images.xcassets/arrow.up.and.down.imageset/Contents.json new file mode 100644 index 00000000000..1dc6b4c7677 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Images.xcassets/arrow.up.and.down.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "arrow.up.and.down.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/arrow.up.and.down.imageset/arrow.up.and.down.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/arrow.up.and.down.imageset/arrow.up.and.down.png new file mode 100644 index 00000000000..3b903f6facc Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/arrow.up.and.down.imageset/arrow.up.and.down.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/Contents.json b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/Contents.json new file mode 100644 index 00000000000..ad0b164d5d6 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "keyboard.compact.light@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "keyboard.compact.dark@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "keyboard.compact.light@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "keyboard.compact.dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "keyboard.compact.light@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "keyboard.compact.dark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@1x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@1x.png new file mode 100644 index 00000000000..ecf0a7b564c Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@1x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@2x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@2x.png new file mode 100644 index 00000000000..f74d9222e42 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@2x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@3x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@3x.png new file mode 100644 index 00000000000..e0779e9a25a Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.dark@3x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@1x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@1x.png new file mode 100644 index 00000000000..76acd4598c7 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@1x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@2x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@2x.png new file mode 100644 index 00000000000..390c853ddb9 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@2x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@3x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@3x.png new file mode 100644 index 00000000000..2d97178aeb3 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.compact.imageset/keyboard.compact.light@3x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/Contents.json b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/Contents.json new file mode 100644 index 00000000000..d5ce1d37de0 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "keyboard.regular.light@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "keyboard.regular.dark@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "keyboard.regular.light@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "keyboard.regular.dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "keyboard.regular.light@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "keyboard.regular.dark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@1x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@1x.png new file mode 100644 index 00000000000..2b404e91d9b Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@1x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@2x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@2x.png new file mode 100644 index 00000000000..6a6e44e8076 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@2x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@3x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@3x.png new file mode 100644 index 00000000000..0c0e2b49d82 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.dark@3x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@1x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@1x.png new file mode 100644 index 00000000000..b57f50670c9 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@1x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@2x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@2x.png new file mode 100644 index 00000000000..3f0b1228e69 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@2x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@3x.png b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@3x.png new file mode 100644 index 00000000000..fd80b073543 Binary files /dev/null and b/ios/engine/KMEI/KeymanEngine/Images.xcassets/keyboard.regular.imageset/keyboard.regular.light@3x.png differ diff --git a/ios/engine/KMEI/KeymanEngine/en.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/en.lproj/Localizable.strings index db0966351ea..05a1723ebb0 100644 --- a/ios/engine/KMEI/KeymanEngine/en.lproj/Localizable.strings +++ b/ios/engine/KMEI/KeymanEngine/en.lproj/Localizable.strings @@ -226,6 +226,24 @@ /* Text showing name of spacebar caption - language + keyboard */ "menu-settings-spacebar-item-languageKeyboard" = "Language and keyboard"; +/* Label for the "Adjust Keyboard Height" item on the main settings screen */ +"menu-settings-adjust-keyboard-height" = "Adjust Keyboard Height"; + +/* Title for the "Adjust Keyboard Height" settings child screen */ +"adjust-keyboard-height-title" = "Adjust Keyboard"; + +/* Label for "Reset to Default Keyboard Height" button on the adjust height screen */ +"button-label-reset-default-keyboard-height" = "Reset to Default Keyboard Height"; + +/* Instruction text to drag keyboard to resize */ +"keyboard-drag-instructions" = "Drag arrow control to adjust keyboard height."; + +/* Instruction to rotate to adjust landscape keyboard height (displayed when device is portrait) */ +"portrait-keyboard-rotate-instructions" = "Rotate device to adjust for landscape."; + +/* Instruction to rotate to adjust portrait keyboard height (displayed when device is landscape) */ +"landscape-keyboard-rotate-instructions" = "Rotate device to adjust for portrait."; + /* Short text for notification: download failure for keyboard */ "notification-download-failure-keyboard" = "Keyboard download failed"; diff --git a/linux/Dockerfile b/linux/Dockerfile index ea07ec699ad..fac302b4744 100644 --- a/linux/Dockerfile +++ b/linux/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023 SIL International. All rights reserved. +# Copyright (c) 2022-2023 SIL Global. All rights reserved. # # builder image for a linux build # see ../docs/build/linux-ubuntu.md @@ -7,7 +7,7 @@ ARG OS_VERSION=latest ARG OS_PLATFORM=amd64 FROM --platform=${OS_PLATFORM} ubuntu:${OS_VERSION} -LABEL org.opencontainers.image.authors="SIL International." +LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman Linux Build Image" diff --git a/linux/debian/changelog b/linux/debian/changelog index 2f89e332c04..3a2dae55f35 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,12 @@ +keyman (17.0.332-1) unstable; urgency=medium + + * set environment variable for rendering of downloads dialog (#12617) + * add `keymanFacename` to .ldml file (#12283) + * ignore exceptions trying to install cache (#11885) + * New upstream release. + + -- Eberhard Beilharz Wed, 11 Dec 2024 15:53:59 +0100 + keyman (17.0.326-1) unstable; urgency=medium * New upstream release @@ -22,12 +31,6 @@ keyman (17.0.279-1) unstable; urgency=medium -- Eberhard Beilharz Thu, 29 Feb 2024 19:17:08 +0100 -keyman (17.0.274-2) UNRELEASED; urgency=medium - - * Add libicu-dev dependency to libkeymancore-dev (closes: #1064915) - - -- Eberhard Beilharz Wed, 28 Feb 2024 17:54:58 +0100 - keyman (17.0.274-1) unstable; urgency=medium * Fix autopkg tests diff --git a/linux/debian/com.keyman.config.appdata.xml b/linux/debian/com.keyman.config.appdata.xml index 9df49a37dc3..e7f71524227 100644 --- a/linux/debian/com.keyman.config.appdata.xml +++ b/linux/debian/com.keyman.config.appdata.xml @@ -5,10 +5,15 @@ MIT MIT Keyman - SIL International + + SIL Global + https://keyman.com/cdn/dev/img/keyman-logo.png Keyman for Linux Keyman makes it possible for you to type in over 2,000 languages + + mild +

diff --git a/linux/debian/com.keyman.ibus_keyman.metainfo.xml b/linux/debian/com.keyman.ibus_keyman.metainfo.xml index 1f021d2fce3..7b109278a17 100644 --- a/linux/debian/com.keyman.ibus_keyman.metainfo.xml +++ b/linux/debian/com.keyman.ibus_keyman.metainfo.xml @@ -2,9 +2,11 @@ com.keyman.ibus_keyman MIT - GPL-2+ + GPL-2.0+ Keyman - SIL International + + SIL Global + https://keyman.com/cdn/dev/img/keyman-logo.png ibus-keyman diff --git a/linux/debian/copyright b/linux/debian/copyright index ee6603bded6..3956e1463d0 100644 --- a/linux/debian/copyright +++ b/linux/debian/copyright @@ -4,24 +4,24 @@ Upstream-Contact: Keyman team Source: https://github.com/keymanapp/keyman Files: * -Copyright: 2018-2024 SIL International +Copyright: 2018-2024 SIL Global License: MIT Files: linux/ibus-keyman/* -Copyright: 2004-2024 SIL International +Copyright: 2004-2024 SIL Global License: GPL-2+ Files: linux/ibus-keyman/src/keymanutil.c linux/ibus-keyman/src/keymanutil.h linux/ibus-keyman/src/kmpdetails.c linux/ibus-keyman/src/kmpdetails.h -Copyright: 2009-2024 SIL International +Copyright: 2009-2024 SIL Global License: GPL-2+ or MIT Files: linux/ibus-keyman/src/keyman-service.c linux/ibus-keyman/src/keyman-service.h linux/keyman-config/buildtools/help2md -Copyright: 2018-2024 SIL International +Copyright: 2018-2024 SIL Global License: GPL-3+ Files: linux/ibus-keyman/tests/ibusimcontext.c @@ -31,7 +31,7 @@ Copyright: 2008-2010, Peng Huang 2008-2013, Peng Huang 2008-2021, Red Hat, Inc. 2015-2021, Takao Fujiwara - 2021-2024, SIL International + 2021-2024, SIL Global License: LGPL-2.1+ Files: linux/keyman-config/buildtools/help2man @@ -41,7 +41,7 @@ License: GPL-3+ Files: debian/com.keyman.config.appdata.xml debian/com.keyman.ibus_keyman.metainfo.xml Copyright: 2019 Daniel Glassey - 2022-2024 SIL International + 2022-2024 SIL Global License: MIT License: MIT diff --git a/linux/debian/libkeymancore2.symbols b/linux/debian/libkeymancore2.symbols index 968f155551b..590b675ddba 100644 --- a/linux/debian/libkeymancore2.symbols +++ b/linux/debian/libkeymancore2.symbols @@ -1,8 +1,8 @@ libkeymancore.so.2 libkeymancore2 #MINVER# * Build-Depends-Package: libkeymancore-dev - (c++|optional)"typeinfo name for std::codecvt_utf8_utf16@Base" 17.0.244 - (c++|optional)std::piecewise_construct@Base 18.0.145 +# We only use a C API, so ignore all C++ symbols in the std namespace that bleed through + (c++|regex|optional)"std::" 18.0.156 km_core_context_clear@Base 17.0.195 km_core_context_get@Base 17.0.195 km_core_context_item_list_size@Base 17.0.195 @@ -18,7 +18,6 @@ libkeymancore.so.2 libkeymancore2 #MINVER# km_core_keyboard_get_key_list@Base 17.0.195 km_core_keyboard_imx_list_dispose@Base 17.0.195 km_core_keyboard_key_list_dispose@Base 17.0.195 - km_core_keyboard_load@Base 17.0.195 km_core_keyboard_load_from_blob@Base 18.0.101 km_core_options_list_size@Base 17.0.195 km_core_process_event@Base 17.0.195 diff --git a/linux/debian/tests/test-build b/linux/debian/tests/test-build index 9a48325793c..45186784abe 100755 --- a/linux/debian/tests/test-build +++ b/linux/debian/tests/test-build @@ -41,7 +41,7 @@ int main(int argc, char *argv[]) { km_core_option_item opts[] = {KM_CORE_OPTIONS_END}; km_core_keyboard *kb = NULL; km_core_state *state = NULL; - km_core_keyboard_load(NULL, &kb); + km_core_keyboard_load_from_blob(NULL, NULL, 0, &kb); km_core_state_create(kb, opts, &state); km_core_actions const *a = km_core_state_get_actions(state); } @@ -62,7 +62,7 @@ echo "build 3: OK" # km_core_option_item opts[] = {KM_CORE_OPTIONS_END}; # km_core_keyboard *kb = NULL; # km_core_state *state = NULL; -# km_core_keyboard_load(NULL, &kb); +# km_core_keyboard_load_from_blob(NULL, NULL, 0, &kb); # km_core_state_create(kb, opts, &state); # km_core_actions const *a = km_core_state_get_actions(state); # } diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index 1dffba9cf32..296467e8634 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2009-2023 SIL International + * Copyright (C) 2009-2023 SIL Global * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public diff --git a/linux/ibus-keyman/src/engine.h b/linux/ibus-keyman/src/engine.h index 5aaa6303a7f..21a99314fbb 100644 --- a/linux/ibus-keyman/src/engine.h +++ b/linux/ibus-keyman/src/engine.h @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2018 SIL International + * Copyright (C) 2018 SIL Global * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public diff --git a/linux/ibus-keyman/src/keyman-service.c b/linux/ibus-keyman/src/keyman-service.c index b5c4afc75f9..fa2bdb702b2 100644 --- a/linux/ibus-keyman/src/keyman-service.c +++ b/linux/ibus-keyman/src/keyman-service.c @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2018-2023 SIL International + * Copyright (C) 2018-2023 SIL Global * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public diff --git a/linux/ibus-keyman/src/keyman-service.h b/linux/ibus-keyman/src/keyman-service.h index 0f7221478a3..a989d6b5136 100644 --- a/linux/ibus-keyman/src/keyman-service.h +++ b/linux/ibus-keyman/src/keyman-service.h @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2018-2023 SIL International + * Copyright (C) 2018-2023 SIL Global * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index 80256f7d4b6..9f48f0ffce4 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2018 SIL International + * Copyright (C) 2018 SIL Global * * keymanutil is dual licensed under the MIT or GPL licenses as described below. * diff --git a/linux/ibus-keyman/src/keymanutil.h b/linux/ibus-keyman/src/keymanutil.h index a4d5355bda0..896db5e6524 100644 --- a/linux/ibus-keyman/src/keymanutil.h +++ b/linux/ibus-keyman/src/keymanutil.h @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2018-2023 SIL International + * Copyright (C) 2018-2023 SIL Global * * keymanutil is dual licensed under the MIT or GPL licenses as described below. * diff --git a/linux/ibus-keyman/src/kmpdetails.c b/linux/ibus-keyman/src/kmpdetails.c index ba6e26faea8..5d98c3bf34d 100644 --- a/linux/ibus-keyman/src/kmpdetails.c +++ b/linux/ibus-keyman/src/kmpdetails.c @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2018 SIL International + * Copyright (C) 2018 SIL Global * * kmpdetails is dual licensed under the MIT or GPL licenses as described below. * diff --git a/linux/ibus-keyman/src/main.c b/linux/ibus-keyman/src/main.c index 477d744d9ba..b7da28e97d5 100644 --- a/linux/ibus-keyman/src/main.c +++ b/linux/ibus-keyman/src/main.c @@ -3,7 +3,7 @@ /* * Keyman Input Method for IBUS (The Input Bus) * - * Copyright (C) 2018 SIL International + * Copyright (C) 2018 SIL Global * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public diff --git a/linux/ibus-keyman/src/test/testdata/kmp.json b/linux/ibus-keyman/src/test/testdata/kmp.json index d2ee4fa9e13..4320826b2fa 100644 --- a/linux/ibus-keyman/src/test/testdata/kmp.json +++ b/linux/ibus-keyman/src/test/testdata/kmp.json @@ -14,7 +14,7 @@ "description": "LIBTRALO" }, "copyright": { - "description": "\u00A9 2008-2018 SIL International" + "description": "\u00A9 2008-2018 SIL Global" }, "author": { "description": "support@keyman.com", diff --git a/linux/ibus-keyman/src/test/testdata/kmp1.json b/linux/ibus-keyman/src/test/testdata/kmp1.json index 5d9954c330e..5ca7bd87d01 100644 --- a/linux/ibus-keyman/src/test/testdata/kmp1.json +++ b/linux/ibus-keyman/src/test/testdata/kmp1.json @@ -14,7 +14,7 @@ "description": "LIBTRALO" }, "copyright": { - "description": "\u00A9 2008-2018 SIL International" + "description": "\u00A9 2008-2018 SIL Global" }, "author": { "description": "support@keyman.com", diff --git a/linux/ibus-keyman/src/test/testdata/kmp2.json b/linux/ibus-keyman/src/test/testdata/kmp2.json index 5e889091f72..187f2dbbb63 100644 --- a/linux/ibus-keyman/src/test/testdata/kmp2.json +++ b/linux/ibus-keyman/src/test/testdata/kmp2.json @@ -14,7 +14,7 @@ "description": "LIBTRALO" }, "copyright": { - "description": "\u00A9 2008-2018 SIL International" + "description": "\u00A9 2008-2018 SIL Global" }, "author": { "description": "support@keyman.com", diff --git a/linux/ibus-keyman/tests/ibusimcontext.c b/linux/ibus-keyman/tests/ibusimcontext.c index 8cf389d7490..c6d7af1e981 100644 --- a/linux/ibus-keyman/tests/ibusimcontext.c +++ b/linux/ibus-keyman/tests/ibusimcontext.c @@ -4,7 +4,7 @@ * Copyright (C) 2008-2013 Peng Huang * Copyright (C) 2015-2022 Takao Fujiwara * Copyright (C) 2008-2022 Red Hat, Inc. - * Copyright (C) 2021-2022 SIL International + * Copyright (C) 2021-2022 SIL Global * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/linux/ibus-keyman/tests/ibusimcontext.h b/linux/ibus-keyman/tests/ibusimcontext.h index f45b404a26a..94e8687bde0 100644 --- a/linux/ibus-keyman/tests/ibusimcontext.h +++ b/linux/ibus-keyman/tests/ibusimcontext.h @@ -3,7 +3,7 @@ /* ibus - The Input Bus * Copyright (C) 2008-2010 Peng Huang * Copyright (C) 2008-2010 Red Hat, Inc. - * Copyright (C) 2021-2022 SIL International + * Copyright (C) 2021-2022 SIL Global * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/linux/keyman-config/COPYING b/linux/keyman-config/COPYING index 5260aa6d9e7..485c2c057c6 100644 --- a/linux/keyman-config/COPYING +++ b/linux/keyman-config/COPYING @@ -1,4 +1,4 @@ -Copyright (c) 2018 SIL International +Copyright (c) 2018 SIL Global Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/linux/keyman-config/Makefile b/linux/keyman-config/Makefile index f057dcad0ce..d36bf16a0fd 100644 --- a/linux/keyman-config/Makefile +++ b/linux/keyman-config/Makefile @@ -48,7 +48,7 @@ MOFILES := $(POFILES:.po=/LC_MESSAGES/keyman-config.mo) update-template: version xgettext --package-name "keyman" --package-version "$(VERSION)" \ - --msgid-bugs-address "" --copyright-holder "SIL International" \ + --msgid-bugs-address "" --copyright-holder "SIL Global" \ --language=Python --directory=. --output-dir=locale --output=keyman-config.pot \ --add-comments=i18n: --sort-by-file --width=98 \ keyman_config/*.py km-* diff --git a/linux/keyman-config/buildtools/help2md b/linux/keyman-config/buildtools/help2md index 23a3f5b26bd..a32685d1ded 100755 --- a/linux/keyman-config/buildtools/help2md +++ b/linux/keyman-config/buildtools/help2md @@ -5,7 +5,7 @@ # Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009, # 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2020, 2021 Free Software # Foundation, Inc. -# Copyright (C) 2021 SIL International +# Copyright (C) 2021 SIL Global # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -76,7 +76,7 @@ my $version_info = enc_user sprintf _(<<'EOT'), $this_program, $this_version; Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2020, 2021 Free Software Foundation, Inc. -Copyright (C) 2021 SIL International +Copyright (C) 2021 SIL Global This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/linux/keyman-config/keyman_config/keyman_option.py b/linux/keyman-config/keyman_config/keyman_option.py index 791d799cb83..b7f16195fda 100644 --- a/linux/keyman-config/keyman_config/keyman_option.py +++ b/linux/keyman-config/keyman_config/keyman_option.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 ''' - Keyman is copyright (C) SIL International. MIT License. + Keyman is copyright (C) SIL Global. MIT License. Implementation of the Keyman DConf options ''' diff --git a/linux/keyman-config/keyman_config/sentry_handling.py b/linux/keyman-config/keyman_config/sentry_handling.py index a16ba8016c9..e7b3d40da3a 100644 --- a/linux/keyman-config/keyman_config/sentry_handling.py +++ b/linux/keyman-config/keyman_config/sentry_handling.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 ''' -Keyman is copyright (C) SIL International. MIT License. +Keyman is copyright (C) SIL Global. MIT License. Implements the Sentry error handling ''' diff --git a/linux/keyman-config/locale/keyman-config.pot b/linux/keyman-config/locale/keyman-config.pot index 50b0d4d8ea9..ed377151f7e 100644 --- a/linux/keyman-config/locale/keyman-config.pot +++ b/linux/keyman-config/locale/keyman-config.pot @@ -1,5 +1,5 @@ # SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR SIL International +# Copyright (C) YEAR SIL Global # This file is distributed under the same license as the keyman package. # FIRST AUTHOR , YEAR. # diff --git a/linux/scripts/debian.sh b/linux/scripts/debian.sh index 66cc7ba1575..3a896c10b25 100755 --- a/linux/scripts/debian.sh +++ b/linux/scripts/debian.sh @@ -36,7 +36,7 @@ for proj in ${projects}; do EXTRA_ARGS="--distribution ${DIST} --force-distribution" fi # shellcheck disable=SC2086 - dch --newversion "${version}-${DEBREVISION-1}" ${EXTRA_ARGS} "Re-release to Debian" + dch --newversion "${version}-${DEBREVISION-1}" ${EXTRA_ARGS} "" debuild -d -S -sa -Zxz cd "${BASEDIR}" done diff --git a/linux/scripts/upload-to-debian.sh b/linux/scripts/upload-to-debian.sh index 85472a4ed19..ead29439839 100755 --- a/linux/scripts/upload-to-debian.sh +++ b/linux/scripts/upload-to-debian.sh @@ -92,14 +92,19 @@ function get_latest_stable_branch_name() { echo "${stable_branch##* }" } +# Push the changelog changes to GitHub and create a PR. Returns the PR# +# in the environment variable PR_NUMBER. function push_to_github_and_create_pr() { - local BRANCH=$1 - local BASE=$2 - local PR_TITLE=$3 - local PR_BODY=$4 + local BRANCH=$1 # `chore/linux/changelog` or `chore/linux/cherry-pick/changelog` + local BASE=$2 # stable branch, `beta` or `master` + local PR_TITLE=$3 # `Update debian changelog` + local PR_BODY=$4 # `@keymanapp-test-bot skip` if [[ -n "${PUSH}" ]]; then - ${NOOP} git push --force-with-lease origin "${BRANCH}" + # Push to origin. We force push to reset the branch the commit we just made. + # There shouldn't be any other commits on ${BRANCH} except the one we want to replace + # (if any). + ${NOOP} git push --force origin "${BRANCH}" PR_NUMBER=$(gh pr list --draft --search "${PR_TITLE}" --base "${BASE}" --json number --jq '.[].number') if [[ -n ${PR_NUMBER} ]]; then builder_echo "PR #${PR_NUMBER} already exists" diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 51cf7185509..483998a16e0 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 29BE9D872CA3C21900B67DE7 /* KMModifierMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 29BE9D862CA3C21900B67DE7 /* KMModifierMapping.m */; }; 29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = D861B03E2C5747F70003675E /* KMSettingsRepository.m */; }; 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; }; + 29DD5F442CFEF88000683388 /* SILAndikaV1RGB.png in Resources */ = {isa = PBXBuildFile; fileRef = 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */; }; 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37A245C02565DFA6000BBF92 /* Assets.xcassets */; }; 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */ = {isa = PBXBuildFile; fileRef = 37AE5C9C239A7B770086CC7C /* qrcode.min.js */; }; 37C2B0CB25FF2C350092E16A /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 37C2B0CA25FF2C340092E16A /* Help */; }; @@ -84,7 +85,6 @@ E213601E2142D7C000A043B7 /* keyman-88.png in Resources */ = {isa = PBXBuildFile; fileRef = E213601D2142D7BF00A043B7 /* keyman-88.png */; }; E21799051FC5B7BC00F2D66A /* KMInputMethodEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */; }; E21E645820C04EA8000D6274 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = E21E645720C04EA7000D6274 /* logo.png */; }; - E23380FB21407AA100B90591 /* SILInBlue76.png in Resources */ = {isa = PBXBuildFile; fileRef = E23380FA21407AA100B90591 /* SILInBlue76.png */; }; E240F599202DED740000067D /* KMPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = E240F598202DED740000067D /* KMPackage.m */; }; E2585A7F20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx in Resources */ = {isa = PBXBuildFile; fileRef = E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */; }; E2585A8120DD7CF100CBB994 /* TestAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E2585A8020DD7CF100CBB994 /* TestAppDelegate.m */; }; @@ -275,6 +275,7 @@ 29D470972C648D5200224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/KMKeyboardHelpWindowController.strings; sourceTree = ""; }; 29D470982C648D5200224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/MainMenu.strings; sourceTree = ""; }; 29D470992C648D7100224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; + 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = SILAndikaV1RGB.png; sourceTree = ""; }; 29DD8400276C49E20066A16E /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/KMAboutWindowController.strings; sourceTree = ""; }; 29DD8401276C49E20066A16E /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/preferences.strings; sourceTree = ""; }; 29DD8402276C49E30066A16E /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/KMInfoWindowController.strings; sourceTree = ""; }; @@ -379,7 +380,6 @@ E21799031FC5B74D00F2D66A /* KMInputMethodEventHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodEventHandler.h; sourceTree = ""; }; E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodEventHandler.m; sourceTree = ""; }; E21E645720C04EA7000D6274 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo.png; path = Keyman4MacIM/KMConfiguration/logo.png; sourceTree = SOURCE_ROOT; }; - E23380FA21407AA100B90591 /* SILInBlue76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = SILInBlue76.png; path = Keyman4MacIM/Images/SILInBlue76.png; sourceTree = SOURCE_ROOT; }; E240F597202DED300000067D /* KMPackage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMPackage.h; sourceTree = ""; }; E240F598202DED740000067D /* KMPackage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMPackage.m; sourceTree = ""; }; E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */ = {isa = PBXFileReference; lastKnownFileType = file; path = KMMethodEventHandlerTests.kmx; sourceTree = ""; }; @@ -639,7 +639,7 @@ 98BF92421BF01CBE0002126A /* KMAboutWindowController.m */, 293EA3E827140D8100545EED /* KMAboutWindowController.xib */, E213601D2142D7BF00A043B7 /* keyman-88.png */, - E23380FA21407AA100B90591 /* SILInBlue76.png */, + 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */, 98BF92461BF01D890002126A /* image.jpg */, 98BF92531BF040A50002126A /* title.png */, ); @@ -846,7 +846,6 @@ 989C9C131A7876DE00A20425 /* Images.xcassets in Resources */, 98E2CEA11A92C39C00AE2455 /* InfoPlist.strings in Resources */, E21E645820C04EA8000D6274 /* logo.png in Resources */, - E23380FB21407AA100B90591 /* SILInBlue76.png in Resources */, 9874C3211B536847000BB543 /* info.png in Resources */, 98E672A01B532F5E00DBDE2F /* KMDownloadKBWindowController.xib in Resources */, 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */, @@ -862,6 +861,7 @@ 293EA3E627140D8100545EED /* KMAboutWindowController.xib in Resources */, 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */, 293EA3EB27140DEC00545EED /* preferences.xib in Resources */, + 29DD5F442CFEF88000683388 /* SILAndikaV1RGB.png in Resources */, 9800EC5A1C02940300BF0FB5 /* keyman-for-mac-os-license.html in Resources */, 989C9C161A7876DE00A20425 /* MainMenu.xib in Resources */, 29B42A602728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib in Resources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Images/SILInBlue76.png b/mac/Keyman4MacIM/Keyman4MacIM/Images/SILInBlue76.png deleted file mode 100644 index 0275859531c..00000000000 Binary files a/mac/Keyman4MacIM/Keyman4MacIM/Images/SILInBlue76.png and /dev/null differ diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib index b6c018d1a56..fa15b43024f 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib @@ -1,8 +1,8 @@ - + - + @@ -20,7 +20,7 @@ - + @@ -42,8 +42,12 @@ - - + + + + + + @@ -100,29 +104,29 @@ -