From 34d6eb89fa4fabb39660a7d6f165c9d4bc946fcb Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 20 May 2026 15:51:25 +0200 Subject: [PATCH 01/72] fix(windows): Ensure default locale always appears in list of locales * Add `TKeymanPaths.KeymanLocalePath` for consistent access to locales folder, and update references. * Remove the `SKDefaultLanguageCode` string and hard code the default language in CustomisationMessages.pas. * Hide the test locale 'qqq' on release builds. Fixes: #15164 --- common/windows/delphi/general/KeymanPaths.pas | 15 + .../windows/delphi/general/KeymanVersion.pas | 11 + oem/firstvoices/windows/src/xml/strings.xml | 5 - windows/docs/help/advanced/locale_edit.md | 7 +- windows/src/desktop/branding/messages.txt | 8 - .../desktop/kmshell/locale/am-ET/strings.xml | 4 - .../desktop/kmshell/locale/az-AZ/strings.xml | 4 - .../desktop/kmshell/locale/bwr-NG/strings.xml | 4 - .../desktop/kmshell/locale/ckl-NG/strings.xml | 4 - .../desktop/kmshell/locale/cs-CZ/strings.xml | 4 - .../src/desktop/kmshell/locale/de/strings.xml | 4 - .../kmshell/locale/el-polyton/strings.xml | 4 - .../desktop/kmshell/locale/es-419/strings.xml | 4 - .../desktop/kmshell/locale/es-ES/strings.xml | 4 - .../desktop/kmshell/locale/ff-NG/strings.xml | 4 - .../desktop/kmshell/locale/ff-ZA/strings.xml | 4 - .../src/desktop/kmshell/locale/fr/strings.xml | 4 - .../desktop/kmshell/locale/ha-HG/strings.xml | 4 - .../desktop/kmshell/locale/hia-NG/strings.xml | 4 - .../desktop/kmshell/locale/id-ID/strings.xml | 4 - .../desktop/kmshell/locale/it-IT/strings.xml | 4 - .../desktop/kmshell/locale/km-KH/strings.xml | 4 - .../src/desktop/kmshell/locale/kn/strings.xml | 4 - .../desktop/kmshell/locale/kr-NG/strings.xml | 4 - .../desktop/kmshell/locale/mfi-NG/strings.xml | 4 - .../desktop/kmshell/locale/mnw-MM/strings.xml | 4 - .../desktop/kmshell/locale/mrt-NG/strings.xml | 4 - .../src/desktop/kmshell/locale/my/strings.xml | 1 - .../desktop/kmshell/locale/nl-NL/strings.xml | 4 - .../desktop/kmshell/locale/pl-PL/strings.xml | 4 - .../desktop/kmshell/locale/pt-BR/strings.xml | 4 - .../desktop/kmshell/locale/pt-PT/strings.xml | 6 +- .../src/desktop/kmshell/locale/qqq/locale.xml | 577 ------------------ .../desktop/kmshell/locale/qqq/strings.xml | 1 - .../desktop/kmshell/locale/ru-RU/strings.xml | 4 - .../kmshell/locale/shu-latn/strings.xml | 4 - .../desktop/kmshell/locale/sv-SE/strings.xml | 4 - .../src/desktop/kmshell/locale/ta/strings.xml | 3 - .../src/desktop/kmshell/locale/tr/strings.xml | 1 - .../desktop/kmshell/locale/uk-UA/strings.xml | 4 - .../desktop/kmshell/locale/vec/strings.xml | 1 - .../desktop/kmshell/locale/vi-VN/strings.xml | 4 - .../desktop/kmshell/locale/zh-CN/strings.xml | 4 - ...uration.System.KeymanUILanguageManager.pas | 1 - .../src/desktop/kmshell/util/UILanguages.pas | 2 +- windows/src/desktop/kmshell/xml/strings.xml | 5 - .../delphi/cust/CustomisationMessages.pas | 36 +- .../manual-tests/test_i2605/locale-tam.xml | 3 - 48 files changed, 62 insertions(+), 745 deletions(-) delete mode 100644 windows/src/desktop/kmshell/locale/qqq/locale.xml diff --git a/common/windows/delphi/general/KeymanPaths.pas b/common/windows/delphi/general/KeymanPaths.pas index a0cc07d5b4d..2e5bff0aab6 100644 --- a/common/windows/delphi/general/KeymanPaths.pas +++ b/common/windows/delphi/general/KeymanPaths.pas @@ -44,6 +44,7 @@ TKeymanPaths = class class function KeyboardsInstallDir: string; static; class function KeymanConfigStaticHttpFilesPath(const filename: string = ''): string; static; class function KeymanCustomisationPath: string; static; + class function KeymanLocalePath: string; static; class function KeymanCoreLibraryPath(const Filename: string): string; static; class function CEFPath: string; static; // Chromium Embedded Framework class function CEFDataPath(const mode: string): string; static; @@ -389,6 +390,20 @@ class function TKeymanPaths.KeymanConfigStaticHttpFilesPath(const filename: stri Result := Result + filename; end; +class function TKeymanPaths.KeymanLocalePath: string; +var + keyman_root: string; +begin + // Look up KEYMAN_ROOT development variable -- if found and executable + // within that path then use that as source path + if TKeymanPaths.RunningFromSource(keyman_root) then + begin + Exit(keyman_root + 'windows\src\desktop\kmshell\locale\'); + end; + + Result := TKeymanPaths.KeymanDesktopInstallPath('locale\'); +end; + class function TKeymanPaths.KeymanCustomisationPath: string; var keyman_root: string; diff --git a/common/windows/delphi/general/KeymanVersion.pas b/common/windows/delphi/general/KeymanVersion.pas index 524973e025e..59794854236 100644 --- a/common/windows/delphi/general/KeymanVersion.pas +++ b/common/windows/delphi/general/KeymanVersion.pas @@ -30,6 +30,17 @@ interface TIER_STABLE = 'stable'; const + ENVIRONMENT_TEST = 'test'; + ENVIRONMENT_LOCAL = 'local'; + ENVIRONMENT_ALPHA = 'alpha'; + ENVIRONMENT_BETA = 'beta'; + ENVIRONMENT_STABLE = 'stable'; + +const + SKeymanVersion190 = '19.0'; + SKeymanVersion180 = '18.0'; + SKeymanVersion170 = '17.0'; + SKeymanVersion160 = '16.0'; SKeymanVersion150 = '15.0'; SKeymanVersion140 = '14.0'; SKeymanVersion130 = '13.0'; diff --git a/oem/firstvoices/windows/src/xml/strings.xml b/oem/firstvoices/windows/src/xml/strings.xml index e69f624e0c7..e476f1daf89 100644 --- a/oem/firstvoices/windows/src/xml/strings.xml +++ b/oem/firstvoices/windows/src/xml/strings.xml @@ -833,11 +833,6 @@ keyboard that you use in Windows. Keyman for FirstVoices will adapt automatical en - - - - en - diff --git a/windows/docs/help/advanced/locale_edit.md b/windows/docs/help/advanced/locale_edit.md index 1db621bebff..d37c12d6ced 100644 --- a/windows/docs/help/advanced/locale_edit.md +++ b/windows/docs/help/advanced/locale_edit.md @@ -43,12 +43,7 @@ messages (these mostly start with SK) and with the Menu strings. First, edit the translation's language information - SKUILanguageName, SKUILanguageNameWithEnglish, and SKLanguageCode. The SKLanguageCode -should be the same as the language code you chose earlier. You will also -see a String with id SKDefaultLanguageCode. For Keyman for Windows, this -should remain "en" for all translations. When developing a custom -product using the Keyman Engine for Windows, you may change your -product's default language, and this would then entail changing -SKDefaultLanguageCode. +should be the same as the language code you chose earlier. **Note:** Any entries missing from the translation will be retrieved from the default file. diff --git a/windows/src/desktop/branding/messages.txt b/windows/src/desktop/branding/messages.txt index 9fa93cc6de3..ac1ce1a5c90 100644 --- a/windows/src/desktop/branding/messages.txt +++ b/windows/src/desktop/branding/messages.txt @@ -40,14 +40,6 @@ object Messages: TStockMessages Comment = 'The language code for the current translation' Parameters = <> end - item - Name = 'SKDefaultLanguageCode' - DefStr = 'en' - Comment = - 'The default language code for this product. This should be the ' + - 'language that the product is created in' - Parameters = <> - end item Name = 'SKButtonUILanguageDownload' DefStr = 'More Languages Online...' diff --git a/windows/src/desktop/kmshell/locale/am-ET/strings.xml b/windows/src/desktop/kmshell/locale/am-ET/strings.xml index 7f5f4f8ba78..2f238923439 100644 --- a/windows/src/desktop/kmshell/locale/am-ET/strings.xml +++ b/windows/src/desktop/kmshell/locale/am-ET/strings.xml @@ -647,10 +647,6 @@ am - - - - en diff --git a/windows/src/desktop/kmshell/locale/az-AZ/strings.xml b/windows/src/desktop/kmshell/locale/az-AZ/strings.xml index 404e51c51ac..7e3b7d5691a 100644 --- a/windows/src/desktop/kmshell/locale/az-AZ/strings.xml +++ b/windows/src/desktop/kmshell/locale/az-AZ/strings.xml @@ -626,10 +626,6 @@ az - - - - en diff --git a/windows/src/desktop/kmshell/locale/bwr-NG/strings.xml b/windows/src/desktop/kmshell/locale/bwr-NG/strings.xml index 28cdb065aa4..503b941fdea 100644 --- a/windows/src/desktop/kmshell/locale/bwr-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/bwr-NG/strings.xml @@ -627,10 +627,6 @@ Keyboard natǝ ga ana hara kǝthlǝr kari akwa Windows. Keyboardyeri ar Keyman a en - - - - en diff --git a/windows/src/desktop/kmshell/locale/ckl-NG/strings.xml b/windows/src/desktop/kmshell/locale/ckl-NG/strings.xml index dd994697df0..779b0e05316 100644 --- a/windows/src/desktop/kmshell/locale/ckl-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/ckl-NG/strings.xml @@ -647,10 +647,6 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to en - - - - en diff --git a/windows/src/desktop/kmshell/locale/cs-CZ/strings.xml b/windows/src/desktop/kmshell/locale/cs-CZ/strings.xml index 83adda0577e..6e217e3b1f8 100644 --- a/windows/src/desktop/kmshell/locale/cs-CZ/strings.xml +++ b/windows/src/desktop/kmshell/locale/cs-CZ/strings.xml @@ -714,10 +714,6 @@ klávesnici, kterou používáte v systému Windows. Klávesnice se automaticky cs-cs - - - - cs-cs diff --git a/windows/src/desktop/kmshell/locale/de/strings.xml b/windows/src/desktop/kmshell/locale/de/strings.xml index 9827426632f..2e2d3bffd87 100644 --- a/windows/src/desktop/kmshell/locale/de/strings.xml +++ b/windows/src/desktop/kmshell/locale/de/strings.xml @@ -713,10 +713,6 @@ de - - - - en diff --git a/windows/src/desktop/kmshell/locale/el-polyton/strings.xml b/windows/src/desktop/kmshell/locale/el-polyton/strings.xml index f9699f61de2..72d425903fd 100644 --- a/windows/src/desktop/kmshell/locale/el-polyton/strings.xml +++ b/windows/src/desktop/kmshell/locale/el-polyton/strings.xml @@ -693,10 +693,6 @@ el - - - - en diff --git a/windows/src/desktop/kmshell/locale/es-419/strings.xml b/windows/src/desktop/kmshell/locale/es-419/strings.xml index 61cf66cb46d..f09a4fd222f 100644 --- a/windows/src/desktop/kmshell/locale/es-419/strings.xml +++ b/windows/src/desktop/kmshell/locale/es-419/strings.xml @@ -713,10 +713,6 @@ es-419 - - - - en diff --git a/windows/src/desktop/kmshell/locale/es-ES/strings.xml b/windows/src/desktop/kmshell/locale/es-ES/strings.xml index 639debd6fd6..144777c6056 100644 --- a/windows/src/desktop/kmshell/locale/es-ES/strings.xml +++ b/windows/src/desktop/kmshell/locale/es-ES/strings.xml @@ -713,10 +713,6 @@ en - - - - en diff --git a/windows/src/desktop/kmshell/locale/ff-NG/strings.xml b/windows/src/desktop/kmshell/locale/ff-NG/strings.xml index ccb18cebfef..9c15b5bf159 100644 --- a/windows/src/desktop/kmshell/locale/ff-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/ff-NG/strings.xml @@ -647,10 +647,6 @@ Kibod ko a` naftirta dow na`ura. Kibod Keyman koosan bee hoore mum yanayu mawnug en - - - - en diff --git a/windows/src/desktop/kmshell/locale/ff-ZA/strings.xml b/windows/src/desktop/kmshell/locale/ff-ZA/strings.xml index 119a12f37b1..47b53ff10e3 100644 --- a/windows/src/desktop/kmshell/locale/ff-ZA/strings.xml +++ b/windows/src/desktop/kmshell/locale/ff-ZA/strings.xml @@ -651,10 +651,6 @@ Jokku\? ful - - - - en diff --git a/windows/src/desktop/kmshell/locale/fr/strings.xml b/windows/src/desktop/kmshell/locale/fr/strings.xml index 2ed3f704bd2..df1248f0332 100644 --- a/windows/src/desktop/kmshell/locale/fr/strings.xml +++ b/windows/src/desktop/kmshell/locale/fr/strings.xml @@ -646,10 +646,6 @@ fr - - - - en diff --git a/windows/src/desktop/kmshell/locale/ha-HG/strings.xml b/windows/src/desktop/kmshell/locale/ha-HG/strings.xml index f65e132d358..91a118a878b 100644 --- a/windows/src/desktop/kmshell/locale/ha-HG/strings.xml +++ b/windows/src/desktop/kmshell/locale/ha-HG/strings.xml @@ -626,10 +626,6 @@ en - - - - en diff --git a/windows/src/desktop/kmshell/locale/hia-NG/strings.xml b/windows/src/desktop/kmshell/locale/hia-NG/strings.xml index bb618801581..bb558c5b18b 100644 --- a/windows/src/desktop/kmshell/locale/hia-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/hia-NG/strings.xml @@ -627,10 +627,6 @@ keyboard unda da mogka thlina nda Windows. Keyman keyboards na da spat sosaya t en - - - - en diff --git a/windows/src/desktop/kmshell/locale/id-ID/strings.xml b/windows/src/desktop/kmshell/locale/id-ID/strings.xml index b99c489289e..bfc69558a31 100644 --- a/windows/src/desktop/kmshell/locale/id-ID/strings.xml +++ b/windows/src/desktop/kmshell/locale/id-ID/strings.xml @@ -626,10 +626,6 @@ id - - - - en diff --git a/windows/src/desktop/kmshell/locale/it-IT/strings.xml b/windows/src/desktop/kmshell/locale/it-IT/strings.xml index de66ee82e9f..a89759327e6 100644 --- a/windows/src/desktop/kmshell/locale/it-IT/strings.xml +++ b/windows/src/desktop/kmshell/locale/it-IT/strings.xml @@ -714,10 +714,6 @@ che usi in Windows. Le tastiere Keyman si adatteranno automaticamente al layout it - - - - it diff --git a/windows/src/desktop/kmshell/locale/km-KH/strings.xml b/windows/src/desktop/kmshell/locale/km-KH/strings.xml index 16deccb41df..4c32ffeb1bb 100644 --- a/windows/src/desktop/kmshell/locale/km-KH/strings.xml +++ b/windows/src/desktop/kmshell/locale/km-KH/strings.xml @@ -713,10 +713,6 @@ km - - - - en diff --git a/windows/src/desktop/kmshell/locale/kn/strings.xml b/windows/src/desktop/kmshell/locale/kn/strings.xml index 0425ab7a58b..4c985d98e0d 100644 --- a/windows/src/desktop/kmshell/locale/kn/strings.xml +++ b/windows/src/desktop/kmshell/locale/kn/strings.xml @@ -646,10 +646,6 @@ kn - - - - en diff --git a/windows/src/desktop/kmshell/locale/kr-NG/strings.xml b/windows/src/desktop/kmshell/locale/kr-NG/strings.xml index ce4aee3eddc..509f3e85441 100644 --- a/windows/src/desktop/kmshell/locale/kr-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/kr-NG/strings.xml @@ -627,10 +627,6 @@ keyboard do suro Windows ye dǝlan faidatǝmin ma. Keyman keyboards dǝye adai en - - - - en diff --git a/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml b/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml index 6f9c21529a1..7f3788a37cf 100644 --- a/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml @@ -627,10 +627,6 @@ keyboard na ká ga slera anikwa na am Windows. Keyman keyboards anuga maga sler en - - - - en diff --git a/windows/src/desktop/kmshell/locale/mnw-MM/strings.xml b/windows/src/desktop/kmshell/locale/mnw-MM/strings.xml index a4c4ef6e924..63d921d4a9c 100644 --- a/windows/src/desktop/kmshell/locale/mnw-MM/strings.xml +++ b/windows/src/desktop/kmshell/locale/mnw-MM/strings.xml @@ -693,10 +693,6 @@ mnw - - - - mnw diff --git a/windows/src/desktop/kmshell/locale/mrt-NG/strings.xml b/windows/src/desktop/kmshell/locale/mrt-NG/strings.xml index 72b2a4f362a..896f67ddcbe 100644 --- a/windows/src/desktop/kmshell/locale/mrt-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/mrt-NG/strings.xml @@ -626,10 +626,6 @@ en - - - - en diff --git a/windows/src/desktop/kmshell/locale/my/strings.xml b/windows/src/desktop/kmshell/locale/my/strings.xml index 7830d9473a4..0e8576c65b4 100644 --- a/windows/src/desktop/kmshell/locale/my/strings.xml +++ b/windows/src/desktop/kmshell/locale/my/strings.xml @@ -225,7 +225,6 @@ ကီးဘုတ်ို အောင်မြင်စွာ ပရင့် ထုတ်လို့မရပါ။ ဝက်ဘ်စာမျက်နှာမှာသိမ်းဆည်းပြီး ထုတ်ယူပါ <ကီးဘုတ်ထိခိုက်ခြင်း> Keyman debug information will be stored in a text file called keymanlog\system.log on your desktop. You should exit Keyman before reading or deleting this file. WARNING: This file can grow large very quickly. Enabling debugging will slow down your system and should only be done if advised by Tavultesoft support. PRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session, and delete the [desktop]\keymanlog\system.log file as soon as possible - en သုံးစွဲသူအတွက်ဘာသာစကားကို ရွေးချယ်ပါ အွန်လိုင်းဘာသာပြန်မှုအသစ်ကို ဖန်တီးပါ ဖိုင်ကိုဒေါင်းလုဒ်ဆွဲနေသည် diff --git a/windows/src/desktop/kmshell/locale/nl-NL/strings.xml b/windows/src/desktop/kmshell/locale/nl-NL/strings.xml index c6e48c924ed..4edb9a4e6ca 100644 --- a/windows/src/desktop/kmshell/locale/nl-NL/strings.xml +++ b/windows/src/desktop/kmshell/locale/nl-NL/strings.xml @@ -627,10 +627,6 @@ dat u in Windows gebruikt. Keyman toetsenborden worden automatisch aangepast aan nl - - - - en diff --git a/windows/src/desktop/kmshell/locale/pl-PL/strings.xml b/windows/src/desktop/kmshell/locale/pl-PL/strings.xml index d6a75be6e9d..1702d5b4d1d 100644 --- a/windows/src/desktop/kmshell/locale/pl-PL/strings.xml +++ b/windows/src/desktop/kmshell/locale/pl-PL/strings.xml @@ -627,10 +627,6 @@ której używasz w systemie Windows. Klawiatury Keyman automatycznie dostosują pl - - - - en diff --git a/windows/src/desktop/kmshell/locale/pt-BR/strings.xml b/windows/src/desktop/kmshell/locale/pt-BR/strings.xml index d3dd5437c8d..d798b1b43dd 100644 --- a/windows/src/desktop/kmshell/locale/pt-BR/strings.xml +++ b/windows/src/desktop/kmshell/locale/pt-BR/strings.xml @@ -647,10 +647,6 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to en - - - - en diff --git a/windows/src/desktop/kmshell/locale/pt-PT/strings.xml b/windows/src/desktop/kmshell/locale/pt-PT/strings.xml index 906b23c8589..6a1d0df5a48 100644 --- a/windows/src/desktop/kmshell/locale/pt-PT/strings.xml +++ b/windows/src/desktop/kmshell/locale/pt-PT/strings.xml @@ -542,7 +542,7 @@ - Selecione o teclado latino básico + Selecione o teclado latino básico que utiliza no Windows. O teclado Keyman irá se adaptar automaticamente ao seu layout preferido. @@ -714,10 +714,6 @@ por-PT - - - - por-PT diff --git a/windows/src/desktop/kmshell/locale/qqq/locale.xml b/windows/src/desktop/kmshell/locale/qqq/locale.xml deleted file mode 100644 index 0dfef8f9c79..00000000000 --- a/windows/src/desktop/kmshell/locale/qqq/locale.xml +++ /dev/null @@ -1,577 +0,0 @@ - - -]> - - - - -[!!Ķēɏmãŋ!!] - -Segoe UI -9 - - - - - - - -&Name; - - - - - -[!!Ÿĕŝ!!] -[!!Ňō!!] - -[!!&Ƴēš!!] -[!!&Ńŏ!!] - -[!!ƠĶ!!] -[!!Čãŋčȅļ!!] -[!!Ćļŏśȅ!!] -[!!Ãƥƥłƴ!!] -[!!Ʀȇmıŋď mĕ Ľàťēř!!] - -[!!ŐƘ!!] -[!!Ċāņċěł!!] - -[!!Ċłīċķ ţħĭš ĭċơŋ ťō şȇŀȅćţ ȁ ƙěɏƅŏáŗď!!] -&Name;[!! ıś ŝťıĺĺ ŕūʼnňīńġ. Ċŀĩćķ ţħıŝ įĉőň ťơ űśē ŷōūř ļāņğųãğě ƙȇƴƃŏǻřđ ąť āʼnɏ ŧĩmė!!] - - - - - -&Name;[!! Ćōʼnƒĩģųřȃţįőň!!] - - - 40 - 118 - 40 - - -[!!Ġĕţŧīńģ Ŝţářťěď!!] -[!!Ħēļƿ!!] - - - -[!!Ƙėƴɓŏäŗď Łǻŷơŭţš!!] [!!Ķ!!] -[!!Ōƿťĩőƞş!!] [!!Ŏ!!] -[!!Ĥōţƙȇȳş!!] [!!Ħ!!] -[!!Śűƥƥơřť!!] [!!Š!!] -[!!Ķēĕƥ īň Ťōŭċĥ!!] [!!Ŧ!!] - - - -[!!Ƒĭļėʼnȁmĕ:!!] - -[!!Ƥāčĸäġȅ vĕřšįơņ:!!] -[!!Ƙĕƴƅŏȃŕď vȇŕšįơņ:!!] -[!!Äųţĥōŗ:!!] -[!!Ŵēƀşīťę:!!] - -[!!Ƥáĉķąğȅ:!!] -[!!Ƒőƞţŝ:!!] -[!!Ƙěŷƀōäŗď Ĺāɏőūŧś:!!] - -[!!Ęńĉőďıʼnğś:!!] - -[!!Łáŷōũŧ Ŧŷƥȅ:!!] - [!!Ƒĭxěđ (Ƥőşıŧįơƞąŀ)!!] - [!!Mäƥƥȅď ŧơ Ŵıƞďőŵŝ ŀąƴőůŧ (Mƞēmŏʼnįč)!!] - -[!!Ɖȇŝĉřīƥťįơń:!!] - -[!!Įŋŝţăĺłȅđ ƒōŕ:!!] - [!!Āłĺ ŭšȇřş!!] - [!!Čūŕŗȅņţ űşęŗ!!] - -[!!Ĺǻƞĝůǻĝęŝ:!!] - [!!✕!!] - [!!Āđđ ļàʼnğũåĝē!!] - -[!!Őƞ Ŝĉŗȇȅň Ķȅƴƀŏȁřď:!!] - [!!Ĉŭŝťŏm!!] - [!!Įŋŝťăļļėđ!!] - [!!Ŋōŧ ĩņšťāłŀėđ!!] - -[!!Ďơĉũměŋťáťĩŏʼn:!!] - [!!İƞśţąļļĕď!!] - [!!Ɲőţ įńşťǻĺłȅđ!!] - -[!!Mȅşśäģę:!!] -[!!Čőƥȳŕīğĥť:!!] - - - -[!!Ƥąċƙäĝė ŏƿţīōʼnş!!] -[!!Őƥţĭơʼnş!!] -[!!Īňšŧąľľ ƙĕŷɓŏȃŕđ...!!] -[!!Ďŏŵƞļőȁđ ĸěƴɓōäŗď...!!] - - [!!Ƙēŷƀōăŕđ ōƿťĭőńš!!] -[!!Ūƞīŋšťāļŀ!!] -[!!Ŭňĭƞŝťąľĺ ƥåĉĸåĝĕ!!] -[!!Šħŏŵ ıʼnťŕơđūċŧŏŗŷ ĥȅłƥ!!] -[!!Čħąņģĕ ĥŏŧĸēƴ!!] -[!!Śęť ĥőťĸěɏ!!] -[!!Űńıňşťāľŀ Ơʼn Şčŗēȇń ƘȇƴƄơǻŕď!!] -[!!Īƞśŧăłŀ Ŏń Śċřęęŋ Ƙĕŷƀơáŗď...!!] - -[!!Ƴŏũ ďŏ ņōţ ħâvě àņƴ ķȅƴƀőāŗđś ĩņşŧăļłęď. Ćŀįĉķ ťĥē Ɗőŵńŀőȁď Ƙȅɏƀŏāřđ ƃŭţŧŏń ŧő ĭńŝţąĺĺ á ĸěƴƄŏąŗď ŀȃŷơŭŧ ƒŗōm ţħě Ƙěɏmăƞ ŵĕƀŝĩţȇ.!!] -[!!Ŷōų ĉáņ ŏƞľŷ ũʼnīňşŧȁĺŀ ŧħĕ ĸėɏƃōâŕđ '%0:s' ıƒ ŷőū ăŗę åŋ Âđmĩƞıśţřąţŏŗ!!] - - - -[!!Ğȅƞėřāĺ!!] -[!!Šťäřŧůƿ!!] -[!!Ŏń Śčŗȇĕň Ƙęŷƀơářď!!] -[!!Āďvăƞčĕď!!] - -[!!Ķĕɏɓơáŗď ħőţķȅŷş ŧōğģľė ƙēȳɓŏářđ áćŧīvǻŧĩōņ!!] -[!!Şīmũŀàťȇ ĄŀťĞř ŵīţĥ Ćţřľ+Āĺŧ!!] -[!!Ťŕėāţ ħȁŗđŵāřĕ ďěãđĸęŷş ǻş ƥľȃįŋ ĸēɏş!!] -[!!Ŝĥőŵ ĥĩʼnţ měššàĝȇś!!] -[!!Āűŧőmȁŧĩĉáĺłŷ ćħȅčķ ƒőŗ ūƿđȃťėŝ àńď đōŵʼnłőǻđ!!] - -[!!Šţářţ ŵĥēń Ŵīŋďơŵš ŝŧâřťš!!] -[!!Ŝĥơŵ ŝƥļąšħ šćŕęȅņ!!] -[!!Šħơŵ ŵěļĉōmė şĉŕĕěņ!!] -[!!Ţēśŧ ƒőŗ ćōńƒłĭćţıņġ ȃƥƿŀīčäťīōňş ŵĥēƞ !!]&Name;[!! şťǻřţš!!] - -[!!Ŕěŀęàšĕ Şĥıƒţ/Ĉţřŀ/Ȁľŧ ōň Őņ Ŝčŗėěń Ķēŷƃơâŕđ ȃƒţēŕ ćŀĭċĸīňģ å ĸȅȳ!!] -[!!Äľŵȁƴŝ ŝĥŏŵ Őƞ Šċŕėěń ĶĕƴƄōãřđ Ŵīƞďơŵ ŵĥėń Ķěɏmàƞ ķȇƴƃőáŗď ĩš śēļĕĉţěď!!] -[!!Šŵĭŧčĥ ŧơ āƥƿřőƥŕįáŧė Őŋ Šćŗěėń ƘȅƴƄőãŗđ/Ħȅĺƿ áųťơmăŧĭĉåłŀŷ ŵĥȅņ ȃ ķęȳƀōāŕđ ĭŝ śĕļȇćţȇđ!!] - -[!!ĎēƄŭģġįƞĝ!!] - -[!!Ůšēř ĩŋţĕŗƒāĉē łáņģůȃġė...!!] - -[!!Ɓâśȇ Ƙȇƴɓŏäŕđ...!!] - - - - - - - - - -[!!(ņōŋȅ)!!] -[!!Šŵĩťċĥ !!]&Name;[!! Ơƒƒ!!] -[!!Ōƥȇń Ķęƴɓőäŗď Měƞų!!] -[!!Śħōŵ Ŏƞ Ŝĉřęęƞ ĶėƴƂőäŗď Ƥâņȇ!!] -[!!Őƥȅņ Čŏƞƒıġŭřãŧĭőń!!] - -[!!Šĥōŵ Ƙȅɏƀŏâŗď Ũşāġė Ƥȁņě!!] -[!!Šħŏŵ Ƒơņŧ Ĥēľƿėř Ƥăňě!!] -[!!Šĥőŵ Ćħąŗâćţěŗ Mȁƥ Ƥȃņě!!] -[!!Ơƥĕʼn Ţěxţ Ęđĩţŏŕ!!] - -[!!Ŏƿĕʼn Łåƞġűãĝě Śŵīţċĥĕŕ!!] - -[!!Ğēņĕŕáŀ Ĥơťĸęŷŝ!!] -[!!Ƙȅŷƅŏȁřđ Łãƴơųţś!!] - - - -[!!Ĩňšťăłĺęđ Ŀāʼnĝŭāģė!!] -[!!Ŵıńďơŵš Ĺāɏőūŧ!!] -[!!Ƙěŷmąŋ ĶȅŷƄőāřď!!] - -[!!(ůšę Ŵīņđơŵş ĺáŷőůŧ)!!] - - - -[!!Ůƥģŗǻđȅ ňŏŵ!!!] -[!!Ŝēŋď Şūƥƿōŗŧ Ʀęǫűęšť!!] -[!!Ĉŏƞŧǻĉŧ Ķěɏmäń Şűƥƿŏřŧ!!] -[!!Ĭƒ ȳőŭ ĥȃvĕ ąŋɏ įśşųȅŝ űśĭňģ Ķėɏmāň, Ĵũšť ãšƙ ã ɋűȅšťıőƞ ŏņ ťħĕ Ķȇȳmȁń Ĉōmmūŋĭťŷ Ƒŏŗūm.!!] -[!!Ŏƿėŋ Ķȇȳmǻň Čőmmŭŋĭťɏ Ƒŏŕūm!!] -[!!Ĉōƿŷŕĭĝĥţ © ŜİĹ Īņţęřŋàťįơņåĺ. Ȃŀĺ Ŕĭğĥŧś Ŗěşĕŗvěď.!!] -[!!Vĕŕšĭōʼn!!] -[!!Ēƞģıňȅ Vęŗŝīőń!!] - -[!!Ũŝęƒųĺ Ľıŋķš!!] - -[!!Ơńŀĭŋě Ĥęĺƥ!!] -[!!Ɖĩȁģʼnőşťīċš!!] -[!!Čĥėĉķ ƒơŗ Ůƥđǻŧęŝ!!] -[!!Ƥŗŏxȳ Şęŧŧīʼnġŝ...!!] - -[!!Ďīāĝŋőşŧīĉş!!] -[!!Ĉħēĉĸ Ľȃŋĝūȃģȅ Śēţŧįŋġš!!] - -[!!Mīćřőŝơƒţ Ŵīňđőŵś ǻƥƿĕãŗś ťŏ Ƅĕ ćőŕŗēćţłŷ ċōŋƒįğűŗēđ ƒőř ɏőűř ľąŋğūȁģē(ś).!!] - - - - - - - 40 - - -[!!Đŏŵňľŏâď Ƙęŷƀőáŗđ Ƒŕŏm ƙėȳmâņ.čőm!!] -[!!Ɗōń'ŧ ĭʼnšŧȃļļ, Ĵūşť đōŵʼnłőǻđ!!] - - - - - - - - - -[!!Īňśťąľĺ Ķėȳƅőåřď/Ƥàčĸąĝė!!] - -[!!Ďȅťáıľś!!] -[!!Ŗěāďmē!!] - -[!!İňśťȁŀļ!!] -[!!Ĩŋšŧąĺŀ ƒŏŕ ãĺŀ űşęŗś!!] - - - - - - - -[!!Ƥřŏxɏ Śėŕvȅř Ĉőŋƒįğũŗåţįőň!!] - -[!!Šȇřvėŕ: !!] -[!!Ƥőŗŧ: !!] -[!!Űşēřńąmě: !!] -[!!Ƥàŝšŵōŗď: !!] - - - - - - - -[!!Šėţ ßǻśę Ķęȳɓơǻřđ!!] -[!!Ŝėłěċţ ťĥę Ƃãşȇ Ļǻťīʼn šċŗĭƥť -ƙėȳƄŏǻŗď ţħãţ ƴōű ůšę įŋ Ŵįŋďơŵŝ. Ķēŷmąň ĸēȳɓōȁřďś ŵĭŀļ ȁđǻƥť ǻųŧōmâŧĭĉäłļȳ ťō ȳőũŗ ƥŕĕƒėŗŕȅđ ŀàƴōũť.!!] - - - - - -[!!Ĉħåńğě Ħơťķėŷ!!] - -[!!&Ċłēǻŕ Ħŏťĸȅŷ!!] -[!!Šėľęćť ä šťȁňđáŕđ ħőŧƙȅƴ ơř ċĥơőşė 'Ćūşťōm' ăƞď ħŏļď Ćţŕŀ,Śĥįƒť åʼnđ/ōř Àļţ áƞđ ţŷƥė ŷőũŕ ďėšįŗěđ ĥőťĸēŷ ƒőŕ ĸęȳƄơȃŗď %0:s:!!] -[!!Ŝęļēċŧ à śŧáńďǻŗď ĥőţĸēɏ ōŕ ĉħőőšē 'Ćŭşţŏm' ǻňđ ħŏĺđ Ĉťŕŀ,Śħıƒŧ ȁʼnď/ŏŕ Àłţ åŋđ ťȳƿē ȳŏũŗ đĕŝĩŕęđ ĥōŧķęŷ ƒŏŗ ŀȃʼnğůăĝē %0:s:!!] -[!!Şēłēĉŧ ã šţàňđáŗď ħőţĸȇȳ ŏŗ ćħőőśȅ 'Ćűšťőm' ăńđ ĥōĺđ Čţŕł,Šħįƒť ăņđ/ơř Ȃŀŧ āʼnď ťȳƥȇ ŷơűŕ đęšĭŕěđ ħơţĸėɏ:!!] - -[!!Ţĥė ħŏţƙēȳ %0:s ŵįŀĺ čōńƒļĭċŧ ŵīŧħ ŋŏřmȁľ ĸęȳƅơǻřđ ŭşȇ. Ɏŏů śĥōųļđ ūşę áţ łėàŝţ Ĉţŕĺ ơŗ Áłť. Ɖő ɏőŭ ŵȁƞţ ţŏ čħäňğē ŧĥįş ńőŵ?!!] -[!!Ťĥē ħơťƙēɏ %0:s ċōŋƒŀĭćţş ŵĩŧĥ ťħę ĥőťĸěȳ şěłĕĉťěď ƒőŗ ƙȇɏƀōāŕď %1:s. Ĭƒ ŷōũ ĉőƞŧıʼnŭĕ, ťĥȇ ħŏŧƙęɏ ƒơŗ ƙȅƴƅōāŕď %1:s ŵīŀł ƃė ĉļēȁřěď. Čơʼnťįŋũę?!!] - - - - - -[!!Şȇłěčţ ƘęŷƂŏȁřďš ŧő Įňśţãļľ!!] -[!!Ÿőū ċȃƞņŏŧ įƞśťåłļ āŀŀ ŧħę ƙĕŷƂőǻŗďś įŋ ťħĩš ƥȁčķáĝē. Ƴŏũ ċãň īƞşťāĺľ à máxĩmūm őƒ %0:d àďđıŧıōʼnąļ ĸēɏƄōȃŕďš. Ƥļĕąšȇ šȅľĕċť ŧħē ĸȇƴƀơåŗđŝ ŷőū ŵįşħ ťő īʼnśŧăļĺ ƒŕŏm ťĥīś ƿàĉĸăģȅ.!!] - - - - - - - -[!!Ŭƥđăţĕď !!]&Name;[!! Ċơmƥơƞȅƞŧś Ãváīŀâɓļĕ!!] - -[!!Ůƥđäţęş ƒōŕ !!]&Name;[!! àŕė ŋơŵ ävàıłàƄľē!!] -[!!Ƥłęāşȅ šĕĺěčţ ŧħę űƥđáŧěš ȳőŭ ŵīŝħ ŧō ĭńšťąľŀ:!!] - -[!!Đō ȳőů ŵãʼnŧ ťŏ ďơŵńĺơăď ăńď ĭŋśţȁŀļ ťħīš ƿăŧċħ ƞōŵ?!!] -[!!Ŷŏů ċâƞ ďőŵŋłŏãđ ŧĥě ňȅŵ věŗşıŏƞ ƒŕŏm:!!] -[!!Đő ƴơų ŵǻňť ťŏ vīšįť ŧħę ŵȅƄŝįťĕ ʼnőŵ?!!] - -[!!Įʼnšŧąłļ Ɲōŵ!!] -[!!Čáńĉĕļ!!] - -[!!Ơļđ Vĕŗŝīőƞ!!] -[!!Ŭƿďãťěđ Čơmƥőŋȅňţ!!] -[!!Ŝĭźē!!] - -&Name;[!! %0:s!!] -[!!ĶȇɏƄơąŕď %0:s %1:s!!] - -[!!Ůńàƃŀĕ ŧő ćŏņŧāċŧ ƙēȳmāń.ċőm - ƿľěášē măķȇ śųŗȇ ŷōů ħåvė àƞ àćťįvę Ĭńťȅřʼnȇţ čőňŋęčťįơƞ àńď ţřŷ âĝàįń.!!] -[!!ŪŋȃƄŀĕ ţŏ ćŏņťȁćť ƙėƴmāʼn.ĉőm - ƿľěäśȇ mäķė šűŕě ȳōű ħåvȇ àƞ āĉťīvě Ĩƞŧēŕňȅŧ čŏņʼnĕćŧįŏʼn åʼnđ ţŗŷ āġàĭŋ. Ŧħȇ ĕŗřőř ŗėĉĕīvėđ ŵǻŝ: %0:s!!] - -&Name;[!! ųƿđåţȇş ăŕę âvăĩľáƂľė!!] -[!!Ċłıćķ ťħįŝ ĭĉơņ ţő đơŵʼnłōåđ åƞď ĭʼnŝťåĺľ űƿđàŧĕŝ!!] -[!!Vĭėŵ ȃʼnđ &ĩńŝţáĺľ !!]&Name;[!! ũƿďąţēş!!] -[!!Ė&xīť őŋľĩńē ŭƥđǻŧĕ ċħěĉƙ!!] - - - - - - - -&Name; -&Name; - -[!!Ŝŧăŕŧ Ƙęƴmâņ!!] -[!!Ĕxīţ!!] - -[!!Őţĥĕŗ ţäśķš!!] - [!!Čơʼnƒīĝůřȁťīőņ!!] - [!!Śĥőŵ ŧħıš šćŗęȇň ăť ŝţǻŗŧųƥ!!] - [!!Ĥĩďė ťħış ŝćŕēėň ąŧ śťǻŗŧŭƿ!!] - - - - - -[!!Đīŝƿľàƴ įʼn!!] -[!!Ƒĭņď őťĥēŗ ďīŝƿľãɏ łåƞğűåģęŝ ōƞłĭʼnė...!!] -[!!Ħȇłƿ ŧŗãňşļȁťĕ ţĥē ũśěŗ įńťēřƒàĉĕ...!!] -[!!Ěńġľĭŝħ!!] -[!!Ēŋġļıŝĥ!!] -[!!ȇƞ!!] -[!!ēŋ!!] - -&Name; - -[!!Šȅľęċţ Ūşěŕ Įƞţȇŕƒâċė Łâʼnĝũàğě!!] - -[!!Ĉĥőŏśė ťħȇ łåʼnġųăğę ţħäŧ ŷőū ŵâņţ ďĩşƿŀáƴȇď ĭŋ ţħĕ !!]&Name;[!! ĩņťěŕƒāčē.!!] -[!!&Mŏŕė łăňĝŭǻğęŝ őʼnľĭňē...!!] - -[!!Čŕėăŧē ā ŋĕŵ ŧŕăņŝłāŧīōń őƞľīʼnė!!] - - - - - - -[!!Ĺǻʼnģųâġē Ċőńƒığųŗȃťıŏƞ Ťāşƙś!!] - -[!!Ċōƞƒĩĝůŕę ƴơŭŗ ĉōmƿūŧȅŕ ťő ŵőŕĸ ŵĭŧĥ!!] - - -&Name;[!! ĥąś ďēŧēĉŧėď ťħąţ ŧħȅ ƒőľľőŵįŋģ čơńƒīģūřâţĭŏņ ťåśķś ƞęĕđ ţŏ Ƃę ĉŏmƿłēţěď įƞ ơŗďėŕ ƒőŗ!!] -[!!ŝĉŗĩƿť(š) ŧơ ŵŏŕĸ ċőŕřĕĉţĺƴ ơƞ ɏōűŕ ċơmƥųţȅŗ:!!] - -[!!Mĩĉŕőŝōƒţ Ŏƒƒįčě Ļǻŋĝűăĝě Ŝũƿƥơŕť!!] - -[!!Mōŗĕ Īƞƒōŗmàŧįőʼn...!!] - -[!!Ɖő ȳơů ŵăňţ !!]&Name;[!! ŧő čơmƿłȅŧę ŧĥēŝę ŧäşķş ņơŵ?!!] - - -[!!Ɖơń'ť åŝƙ mė ăƂơūţ ťħīś àģåĩƞ!!] - - - - - - - 40 - 40 - - -[!!Ŗěšěţ Ĥĭņťš!!] -[!!Áļļ ħĩńť mĕŝšăĝȇš ħävȇ Ƃȅēʼn ŕȇşĕţ ȃņď ŵĩĺļ Ƅė đışƥļȁȳēđ āĝáıŋ.!!] - -[!!Ȅxıŧ !!]&Name;[!!?!!] -[!!Ãŕę ƴŏū şŭŗē ɏōų ŵȁņť ŧō ēxīŧ !!]&Name;[!!? Ÿơũŗ Ƙȅŷmǻń ĸȅŷƄőǻŕďš ŵıĺļ ŝťıľľ ƀę ŀıŝŧȅđ įň ťħě Ŵĩŋđŏŵš ŀȃņĝŭāģēŝ, ƃŭť ŵĭĺĺ ņơţ ƀĕ ƒůƞčŧĩŏňȃĺ ũŋťĩł ȳőů řęšţäŕţ !!]&Name;[!!.!!] - -[!!Ŏņ Šċŗěȇʼn Ƙȅɏƀơâřď Ĉĺőŝęđ!!] -[!!Ÿơų ħāvę čļơśȇđ ťĥē Őň Ŝćřěēʼn Ķēɏƃơâřď. !!]&Name;[!! ış şťıłł ŗŭŋƞĭŋģ. Ƴŏų čãń ōƿēŋ ţĥė Ŏň Şćŕęēƞ Ƙěɏƀōȁŗď åţ åņȳ ťĭmė ƃƴ čĺĭĉĸĭņğ ōń ŧĥĕ !!]&Name;[!! Ĩĉōņ ȃňď şĕļȅćŧīŋĝ "Ōņ Śćŗĕȇʼn Ƙȇȳƃơäŕđ"!!] - -[!!Đōň'ţ ŝĥŏŵ ŧĥĩš ĥıņť âĝăĩƞ!!] - - - - - - -&Name;[!! Ĥęļƥ!!] -[!!Ġėŧ Şŧáŗţěđ!!] -[!!Ĥěłƿ Čơʼnťēƞŧš!!] - -[!!Ħėŀƥ ōņ "!!] -[!!" ƙęŷƃőáŕď!!] - -[!!Vīěŵ Őŋ Şčŗęȇņ Ƙȅƴƅŏȃřď!!] -[!!Vįĕŵ Ƙȇɏƅơãŗď Ŭśȁĝě!!] -[!!Vĩėŵ Ƒōňţ Ħēĺƥěŗ!!] -[!!Vīěŵ Ċħåřāčŧĕř Máƿ!!] -[!!Ŝŵĭŧčħ !!]&Name;[!! Ōƒƒ!!] -[!!Ơƥĕʼn !!]&Name;[!! Ċơƞƒĩġũřáţīőń!!] -[!!Ơƿĕň Ĥȅłƿ!!] -[!!Ćļŏŝȇ Ŏň Şĉŗȅēʼn Ķēƴƃōäŗď!!] - - - - - -[!!Şŵıţčħ !!]&Name;[!! &Ōƒƒ!!] -[!!Ōń Ŝćŗĕėň &ĶȇȳƂőářď!!] -[!!Ķȇƴƃơăřď &Ůšăĝė!!] -[!!&Ƒōńť Ħȇŀƥěŕ!!] -[!!Ĉĥãřāĉťęř &Mâƥ!!] -[!!&Ťēxť Ȇďĩťŏř...!!] -[!!&Ċơņƒığūŕåťĭơʼn...!!] - -[!!&Ħęłƥ...!!] -[!!Ĕ&xĩŧ!!] - -[!!Ňơ ƙěƴɓőȃřď ŀāƴőůťš āŕĕ ıŋşţāłłěď. -Ĉĥơơšȇ "Čőʼnƒįğűŗäťīŏƞ" ŧơ īňŝŧǻłľ á -ķȅɏƂőäŗđ ļąɏōůţ ƒőř ɏŏūŕ ĺáňġųáĝę.!!] - -[!!Ƒȁďȅ Ŵĥęń Ĭʼnàčţĭvē!!] -[!!Ŝĥŏŵ Ťōōĺƀåř!!] -[!!Śǻvĕ äš ŴěƄ Ƥąģę...!!] -[!!Ƥŕıňŧ...!!] - - - - - -[!!Ƙȅŷƅőǻřď Ħēłƥ!!] -[!!Őņ Ŝćŗėėń ĶȇɏƄōâŕď!!] -[!!Ģȅŧ Šťàŗťĕď!!] -&Name;[!! Ĥėŀƥ!!] -&Name;[!! Ċơŋƒĩġũřăţıơņ!!] -[!!Ťħĩŝ ƙȅŷƄőäŗđ ĺàŷŏũţ ĥáś ʼnō Ĝěťťīņģ Śťăŗţęď ơř Ũŝȁģē ďơċũměņťȁţĩŏƞ. Ƥľȅǻšě ĉħėċĸ ŧħē Šťàřţ Měńů őŗ ĉőŋţáĉť ŧħę ƙȇȳƂŏåŗď ďȇvēľőƥȅŗ ƒōŗ mőŗē ıňƒŏŗmȃŧĩőň ōƞ ĥơŵ ťō ųŝę ŧĥĭş ķěȳƅőářđ ŀâŷōųţ.!!] -[!!Čļīćƙ ťĥȅ ƅŭťŧŏňś ƃȅłơŵ ťơ vıęŵ åďďįŧĩōņāľ ďŏĉųmȅņŧāŧīơƞ ąʼnđ/őř Őń Şčřȇęʼn Ƙȇƴƃŏȁŗď.!!] -[!!Ňơ ƙęƴƃŏàŕđ ĺȁƴơųŧ ĭś ćũřŗęńţľȳ ŝȇľěćŧĕđ ĩņ !!]&Name;[!!. Ĉŀĩĉƙ őƞ àŋɏ őƒ ŧĥē ƙěŷƀőăřď ıćőņš ıƞ ŧħĕ ŧŏơŀƀąŗ åƄơvȇ ťơ ćħőŏşȅ ã ĸȅɏƀơáŕď ĺãŷōūţ.!!] -[!!Ŋō ķĕŷƄőàřđ ľãƴŏųŧŝ ąřė ċůŕŕěŋťĺŷ įƞšťàļľĕď ơŗ ŀơȃđȅđ. Ŭşē !!]&Name;[!! Čōʼnƒīĝűŗāťĩőň ŧơ ıƞšŧȃľļ à ķȅɏƂơâřď ĺãɏōũť.!!] - - - - - - - - - -&Name; -[!!Vĕŕŝĩŏņ %0:s!!] - -[!!Ĥėļƥ!!] -[!!&Ƥřıƞţ...!!] -[!!&Ɗŏŵʼnłōȁď...!!] - - - -[!!Ą ƿäĉĸâģĕ ŵįťħ ţĥę ňȃmȅ %0:s ĩš ǻłŕȅáďƴ īŋśŧåĺĺȅď. Ďơ ŷőũ ŵąņŧ ţō űņĩńŝţǻŀŀ ıŧ ǻňđ įńŝťåŀĺ ťħē ņēŵ ōʼnė?!!] -[!!Ă ĸėȳƀơáŗđ ŵįťħ ŧħė ƞàmę '%0:s' ĩş ȁļŕȇäđȳ ĭʼnšţǻĺłȇđ. ݃ ŷơű ĉơņţīŋűȇ, ĩŧ ŵįļļ ɓę ŭʼnīņŝŧāľŀęđ Ƅėƒơřȅ ťħę ʼnȅŵ ōņȇ īś ĭʼnşŧáłłęđ. Ċōƞťįƞųȇ?!!] -[!!Ŧħē ƙĕŷƄŏäŗď '%0:s' īŝ ƿǻŗť ōƒ ƥâĉĸàģě '%1:s'. Ɏōū mūśţ ũňĩňŝŧäĺĺ ŧĥę ėƞťįřė ƿąĉĸäğȇ. Čŏʼnŧĭņųĕ?!!] -[!!Ɏơũ ċâň ōʼnŀŷ ĭƞšŧâĺĺ ŏř ůŋīņŝťàļł ķȇɏƃőąřďş àś Ȃđmįŋıšťřâťŏŗ. Ůşě Ćōņŧřŏļ Ƥâŋęļ ţō ŝēļȇčŧ ŀáƞģůàĝęś ƒơŕ ųśȅ ãš ą ƞơŕmǻŀ ųšȅŗ.!!] -[!!Ĉħāņģĭńğ ťĥĕ śĕţŧĭŋğ ōƒ Šůŕŗőğȁŧĕś ŕęǫūĩřėŝ Ŵĭňďơŵś ţő ƀě ŗȇśŧȁŗŧěď. Đō ƴơŭ ŵàʼnŧ ŧő ŕȇşţąřŧ Ŵįņđőŵš ňŏŵ?!!] -[!!Ţĥė ƿàċƙȃĝě ơŕ ƙěŷƂơäřď '%0:s' ďơȇś ƞŏţ ĭńčłŭđė äƞƴ ĭƞŧřŏđůċţōŗȳ ħēľƥ.!!] -[!!Ţħĭş ōƥȇŕȃťīʼnĝ şȳšţȅm ıŝ ƞőť śűƥƥōŕŧēď.!!] -[!!Ÿōŭŗ ļįċęŋĉě ħǻş ɓėȇƞ śūċĉėşŝƒŭłľȳ àćţĩvåťȅđ.!!] -[!!ĶėƴmȁņĎēşƙŧōƿ.čĥm!!] -[!!Ţĥĕ ĥĕľƿ ƒįŀě $(ŞƘŎʼnľıŋěĤēļƥƑĭļę) čŏūļđ ƞōŧ ƅė ƒōűʼnď.!!] -[!!<Ďȃmàĝěď ķěɏƄőāřđ>!!] -[!!Ćōđȅƥăġė!!] -[!!Ūņıčŏďĕ!!] -[!!Ďŏŵʼnľŏąđįńġ Ƒĩłē!!] - - - - -[!!Ąřĕ ȳőŭ ŝŭŗȅ ɏŏů ŵåňţ ŧő ŗěmōvȅ ŧĥĕ ơƞ śćŕēęƞ ķēȳƀőȁŗď ĭņŝťäľļėď ƒŏŗ %0:s?!!] -[!!Ăŕȅ ƴőŭ şųŗē ƴơŭ ŵāʼnť ţő ųƞįňšţăļł ĸĕƴƄőȁŗď %0:s?!!] -[!!Åŕė ɏōų śűŕĕ ŷŏū ŵȁńť ŧő űňıňşŧāŀĺ ƿáĉƙåĝė %0:s? - -%1:s!!] -[!!Ţħė ƒơĺĺŏŵįƞĝ ƒōʼnŧś ŵīłĺ ãłšō ɓę ũʼnıƞśťâľŀȅď: %0:s.!!] -[!!Đŏ ƴŏũ ŵäńť ţơ àċţıvǻŧė $(ŞĶŞħőřŧĂƿƿľįĉāŧĭŏńŢıŧŀě) ŏńļīńė ńơŵ ŵĭţĥ ľıčėŋčě ŋŭmƄěŕ %0:s? ݃ ȳŏũ ĉŀĩčƙ Ņō, ȳōŭ ċȃň čħőōšĕ ƒřŏm ăĺŧęřƞāŧē ǻčŧĩváŧįơń mȇŧħơďŝ.!!] -[!!Ŧħė Ćĥàŕǻċťěŗ Mȁƿ ĥáş ȃ ďãťäƀáŝę ơƒ ćħǻŕāćţȇŗš ŧħáţ ňěĕďś ŧő ƅȅ ɓũĩŀť ƀȅƒőřȇ ĩŧ ĉâņ ƀȅ ųşȅđ. ʙůıĺď ĭť ńŏŵ?!!] - - - -[!!Ŧĥē Ůńĭĉŏďė Ċĥąŗáċţėř ďȁťăƅāśĕ ĉŏůļđ ńŏť ƀȅ đęłȅŧȅď ƒőŕ å řėƀųīĺđ. Ĕŗŕōŕ đėŧăĩĺŝ ȁŗȅ: %0:s!!] -[!!Ťħė Ūņīĉőđĕ Ćħáŗȁċťęŗ ďąťáɓȃśȇ ċŏŭĺď ŋơţ Ƅĕ ćřęãŧėđ ăŋđ ħàş Ƃĕĕň đĩŝàƂŀēđ. Ěŗŕōŗ đȅţāįŀŝ äřě: %0:s!!] -[!!Ťĥȇ Űňĭċŏďĕ ĉħäŗąčţȅř đȁţăƅåŝě đįđ ňōť łōăď šűććėŝšƒűŀłɏ (%0:s). Ʀȅƃūīĺď īŧ ƞőŵ?!!] - - -[!!Ċàńňŏŧ åĉťĩvâŧė ƀŕŏŵśĕř ŏř ěmáīľ ƥŕơģŗǻm ƒōŕ ţĥĭŝ ŪƦĽ.!!] -[!!Ŧħĕ ķȅȳƀŏäřđ ċơŭłď ƞŏť ƃě ƥŕĭʼnţȅđ šũććĕŝŝƒŭļļɏ. Ƥľĕáşē ŝâvė ťő ā ŵěƀ ƿåğȅ áńď ƿŕįňť mâņũąĺľɏ.!!] -&Name;[!! ƒȁīĺȅđ ťō śťȃŗŧ. Ƥĺȅǻšĕ ćĥȇčƙ ƴŏųŕ śěćũŕĭţƴ şĕťťıƞĝś ŧơ màķȇ şųŕę ŧħâŧ ţĥĕ ƿŕơġŕām ƙęɏmâƞ.ȅxė įś äľľŏŵęď ťŏ şţãřţ ƃęƒơŗĕ čōʼnŧĩńůıŋĝ. Ŧĥė ȇřřōŗ řěŧŭŗŋēđ ŵãŝ: - -"%0:s" - -Đơ ƴŏū ŵáņť ţő ţŗƴ ǻʼnď śţàŕŧ !!]&Name;[!! ǻĝãĭņ ňōŵ?!!] - - - -&Name;[!! đēƂůĝ ıńƒŏŕmàŧıőņ ŵıĺľ Ƃė ŝŧŏŗȅď ĭń ă ţȇxť ƒīłȇ ćãĺŀȇď ķėŷmåńŀőģ\śŷşťēm.łōģ ơņ ƴőũŕ ďėśƙťŏƥ. Ȳŏū šħơůľđ ėxıť !!]&Name;[!! ƅĕƒŏřȇ řȇåđīʼnĝ ơŕ ďĕŀėŧīŋĝ ťħıŝ ƒĩłě. - -ŴÃŖŊĪŅĢ: Ťħĭš ƒıŀȇ čãň ĝřŏŵ łářģȅ vėřȳ ǫųĩćƙŀɏ. ĒƞâƂļıņģ ďėƄŭġģīʼnğ ŵĭłļ ŝŀơŵ ďōŵň ŷōŭŕ šɏšťěm åňď śħőųļď ŏņĺŷ ɓę ďōƞė ıƒ äďvĭśȅď ɓŷ ŧēćĥʼnįċȃĺ šųƿƿơŗť. - -ƤŘĬVÅĊŸ ŴÁŔŃĨŅĞ: Ƥŀēâšȇ ňơŧē ţĥäť ţĥě đęƄũğ ŀőğƒĭĺȅ ŕĕčőŕďş āĺĺ ĸĕŷşťřőķěś ťĥäŧ ƴơů ţȳƿȇ. Ƴŏų śħơūļđ ơņĺŷ ťűŕń ơʼn ţħė ďėƃũġ ľōģ ƒōř ţĥē ďųřàţĭőń őƒ ȃ ďēƀŭğģīƞģ ŏŗ ďıăĝƞōşŧįĉ šėşŝīơƞ, âŋď đĕĺȇţē ŧħȅ [đȅŝķŧơƥ]\ƙȇȳmåńłŏģ\ŝȳşťȅm.ŀōģ ƒıĺě ãŝ šōōņ áš ƿơśśıɓļĕ.!!] - - - -[!!Äďđ %0:s ēđįŧīƞģ şųƿƥơŕť ťŏ %1:s!!] -[!!Mĭčŗơšōƒť Ōƒƒįčě 2000!!] -[!!Mįćřōśơƒť Ơƒƒįċĕ XƤ!!] -[!!Mıċŗơşőƒŧ Ōƒƒıćĕ 2003!!] -[!!Mįĉŗōšơƒŧ Ōƒƒįćē 2007!!] -[!!Mįćŗőşōƒŧ Ōƒƒīčě 2010!!] - - -[!!Ļįŋƙ ŀâʼnĝűąğę %0:s ŧō Ķȅŷmāň ƙęŷƄŏäřđ %1:s!!] -[!!Ĩňŝŧąŀĺ ļăʼnģųåğē %0:s ąʼnď łĩňƙ ţŏ Ƙėƴmàʼn ķĕɏƂōäřđ %1:s!!] - - - -[!!Ƥłęäśȇ ŵàįŧ ŵħĭĺę !!]&Name;[!! ƒıńďŝ ŧħě ƒōňţŝ ŧĥåţ ŵĩĺľ ŵŏřķ ŵĭţħ!!] -[!!ķȅɏɓơăŗď.!!] - - -[!!įŝ ňőţ á Ůʼnįčơďĕ ĸěȳƄőãŗđ. Ƒōņţš ćȁʼnņơť ƅȅ âŭťŏmãťĭćáļľɏ ĺōĉäţȇđ. Ƥłĕǻšė ćħēćķ ťħě ĸěȳƀőāŗď ďōćůmȇňŧàťĭŏŋ ƒơŗ ƒŏņť ĭƞƒơŕmâţīőń.!!] - -[!!Ťĥȇ ƒŏĺĺőŵıňģ ƒơŋŧś ŵơřĸ ŵıŧħ!!] -[!!ķĕɏƀŏāřđ:!!] - -[!!Ťħĕ ƒōŀŀōŵĭʼnğ ƒơņťš màŷ ãłşő ŵŏřƙ:!!] - -[!!Ŧħę ƒōľļơŵįʼnģ ƒơňŧś mǻƴ ŵŏŕķ ŵįŧĥ!!] -[!!ķęŷƅŏǻŕď:!!] - -[!!Ńő ƒŏŋŧş ŵĕřě ƒōũʼnđ ơņ ȳơŭŗ ŝƴśťȅm ťĥȃţ ŵōŕķ ŵīţĥ !!] -[!!. Ƥľȇāšē !!][!!čħēċƙ ťħė ďŏĉūmęņţǻţīŏņ!!][!! ţơ ƒıńď ƒơňţś ƒōŕ ťĥış ķȇɏƀōãřď.!!] - -[!!Ńŏ ĸęƴƅơāŗđ ŀåƴōůŧś äŕȅ ćųŗŕęņťĺȳ īņśťâłłēđ ōŕ łőäđĕď. Ŭŝě !!]&Name;[!! Ĉőŋƒįğűřąŧīŏņ ŧơ ıʼnŝťȃĺľ ä ƙĕɏƃōąŗđ łâƴơŭŧ.!!] -[!!Ƥľęǻśĕ ćħōŏšě å !!]&Name;[!! ķēƴƄŏȃřđ ţő ƒīƞď ţħė ƒőƞŧş ŧĥáŧ ŵįĺĺ ŵőŗĸ ŵıŧħ īŧ.!!] - - - -[!!Ħīʼnť:!!] -[!!Řěšıżȇ ŧħȅ Őň Şċŕĕĕň Ƙěŷƅơǻřđ ƃŷ ćŀĭćĸīňģ äńď đŕäĝġıńĝ ŏʼn ŧĥę ŵįʼnďơŵ ƅơŗďėŕ!!] -[!!Mővȅ ţĥȅ Ơň Şčřėěŋ ƘėŷƄơäřď Ƃɏ čŀīċĸĩʼnĝ ąʼnđ ďřâģğıŋĝ ōń ťĥȅ "!!]&Name;[!!" ŧīťłē!!] -[!!Ĩņśȅřť ąŋƴ Ůńĩćōđē ćħăŗǻċŧěř ĩņťō ƴơũŕ đōčŭměňŧ ŵıŧĥ ŧħȅ!!] -[!!Čĥǻŗąĉŧėř Mäƥ Ťŏŏŀ!!] -[!!Ƒĩńď őűť ŵĥıćĥ ƒơņţş ŵĭŀł ŵōřƙ ŵıţħ ŷōŭŕ ŀåņğūȃĝė ŵĭťĥ ťĥȇ!!] -[!!Ƒơʼnţ Ħęŀƥȅŕ Ťōōļ!!] -[!!Ğēţ mơŕē ħěŀƥ ōŋ ţħė ĸȅȳƄơäřđ Ƃȳ ċļįčķįŋğ ţħě!!] -[!!Ƙěɏƀơåřď Ůśâģȇ Ƅűŧţơņ!!] - - - - - -[!!Ţęxŧ Ęďĭŧŏŗ - !!]&Name; - - diff --git a/windows/src/desktop/kmshell/locale/qqq/strings.xml b/windows/src/desktop/kmshell/locale/qqq/strings.xml index 86fbece5842..23e31b333b7 100644 --- a/windows/src/desktop/kmshell/locale/qqq/strings.xml +++ b/windows/src/desktop/kmshell/locale/qqq/strings.xml @@ -234,7 +234,6 @@ Create a user interface translation for Keyman Desktop at http://www.tavultesoft [!!Ěńġľĭŝħ!!] [!!Ēŋġļıŝĥ!!] [!!ȇƞ!!] - [!!ēŋ!!] Keyman [!!Šȅľęċţ Ūşěŕ Įƞţȇŕƒâċė Łâʼnĝũàğě!!] [!!Ĉĥőŏśė ťħȇ łåʼnġųăğę ţħäŧ ŷőū ŵâņţ ďĩşƿŀáƴȇď ĭŋ ţħĕ !!]Keyman[!! ĩņťěŕƒāčē.!!] diff --git a/windows/src/desktop/kmshell/locale/ru-RU/strings.xml b/windows/src/desktop/kmshell/locale/ru-RU/strings.xml index 4fdf78818a3..b0729d8b54c 100644 --- a/windows/src/desktop/kmshell/locale/ru-RU/strings.xml +++ b/windows/src/desktop/kmshell/locale/ru-RU/strings.xml @@ -647,10 +647,6 @@ ru - - - - ru diff --git a/windows/src/desktop/kmshell/locale/shu-latn/strings.xml b/windows/src/desktop/kmshell/locale/shu-latn/strings.xml index 06c95b9f6de..ebf8995a12c 100644 --- a/windows/src/desktop/kmshell/locale/shu-latn/strings.xml +++ b/windows/src/desktop/kmshell/locale/shu-latn/strings.xml @@ -646,10 +646,6 @@ en - - - - en diff --git a/windows/src/desktop/kmshell/locale/sv-SE/strings.xml b/windows/src/desktop/kmshell/locale/sv-SE/strings.xml index bec64002587..7bba7833fe5 100644 --- a/windows/src/desktop/kmshell/locale/sv-SE/strings.xml +++ b/windows/src/desktop/kmshell/locale/sv-SE/strings.xml @@ -647,10 +647,6 @@ tangentbord som du använder i Windows. Keyman tangentbord kommer att anpassas a sv - - - - sv diff --git a/windows/src/desktop/kmshell/locale/ta/strings.xml b/windows/src/desktop/kmshell/locale/ta/strings.xml index 63214c2104f..38a91f66656 100644 --- a/windows/src/desktop/kmshell/locale/ta/strings.xml +++ b/windows/src/desktop/kmshell/locale/ta/strings.xml @@ -155,9 +155,6 @@ Entries not in this file will be sourced from the default locale.xml file in the Tamil - - '%0:s' தட்டசு பலகை ஏற்கனவே நிறுவப்பட்டுள்ளது. தொடர்ந்தால் ஏற்கனவே உள்ளதை நீக்கி விட்டு, புதியதை நிறுவவா? diff --git a/windows/src/desktop/kmshell/locale/tr/strings.xml b/windows/src/desktop/kmshell/locale/tr/strings.xml index 688bc45dcb3..e32f2a09b56 100644 --- a/windows/src/desktop/kmshell/locale/tr/strings.xml +++ b/windows/src/desktop/kmshell/locale/tr/strings.xml @@ -228,7 +228,6 @@ Klavye başarıyla yazdırılamadı. Lütfen bir web sayfasına kaydedin ve manuel olarak yazdırın. <Klavye zarar görmüş> Keyman hata ayıklama bilgileri masaüstünüzde keymanlog\system.log adlı metin dosyasında saklanır. Bu dosyayı okumadan ve silmeden önce Keyman çıkış yapmalısınız. UYARI: Bu dosya çok hızlı büyür. Hata ayıklamayı etkinleştirmek sisteminizi yavaşlatır ve yalnızca Tavultesoft destek birimi tarafından tavsiye edilirse yapılmalıdır. GİZLİLİK UYARISI: Hata ayıklama günlük dosyası yazdığınız tuş vuruşlarının tamamını kaydeder. Hata ayıklama günlüğünü sadece hata ayıklama veya tanılama oturumunun süresi için açmalısınız ve [masaüstü]\keymanlog\system.log dosyanızı en kısa sürede silmeniz gerekir. - en Kullanıcı Arabirimi Dili Seçin Çevrimiçi olarak yeni bir çeviri hazırla Dosya İndiriyor diff --git a/windows/src/desktop/kmshell/locale/uk-UA/strings.xml b/windows/src/desktop/kmshell/locale/uk-UA/strings.xml index 6e54690f54e..6d2e9e092b1 100644 --- a/windows/src/desktop/kmshell/locale/uk-UA/strings.xml +++ b/windows/src/desktop/kmshell/locale/uk-UA/strings.xml @@ -647,10 +647,6 @@ uk-UA - - - - uk-UA diff --git a/windows/src/desktop/kmshell/locale/vec/strings.xml b/windows/src/desktop/kmshell/locale/vec/strings.xml index 2b2dcc477df..47c1a96cc7f 100644 --- a/windows/src/desktop/kmshell/locale/vec/strings.xml +++ b/windows/src/desktop/kmshell/locale/vec/strings.xml @@ -161,7 +161,6 @@ No se pol stanpar ƚa tastièra. Prègo, salva inte na pàxena web e stanpa manualmente. <Tastièra danexada> Ƚa informasion de debug Keyman ƚa vegnarà salvada inte un file de tèsto de nòme keymanlog\system.log so 'l to desktop. Te gà da nar fora da Keyman inanso de lèxar o scanseƚar sto file. ATENSION: sto file el pol deventar grando pasto in poco tenpo. Ativando el debugging se ralentarà el to sistèma e se gà da farlo nòma che se consejà da 'l supòrto de Tavultesoft. VERTIMENTO DE PRIVACY: tien conto che el logfile de debug el rejistra tute ƚe batùe de tastièra che te fà. Te gà da inpisar el log de debug nòma che durante na sesion de debugging o diagnòstega, e scanseƚar el file \keymanlog\system.log da 'l desktop pì presto che se pol. - en Desernisi lengua de ƚa interfacia utente Descargo de 'l file No se pol ativar el navegador o el programa de email par sto URL. diff --git a/windows/src/desktop/kmshell/locale/vi-VN/strings.xml b/windows/src/desktop/kmshell/locale/vi-VN/strings.xml index 8e75db2fb0e..2cf66d5d5a3 100644 --- a/windows/src/desktop/kmshell/locale/vi-VN/strings.xml +++ b/windows/src/desktop/kmshell/locale/vi-VN/strings.xml @@ -713,10 +713,6 @@ vi - - - - vi diff --git a/windows/src/desktop/kmshell/locale/zh-CN/strings.xml b/windows/src/desktop/kmshell/locale/zh-CN/strings.xml index ae99513afb6..c9049cd2a97 100644 --- a/windows/src/desktop/kmshell/locale/zh-CN/strings.xml +++ b/windows/src/desktop/kmshell/locale/zh-CN/strings.xml @@ -647,10 +647,6 @@ 英语 - - - - 英语 diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.KeymanUILanguageManager.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.KeymanUILanguageManager.pas index 8a303747de7..b1b3c8c5d5a 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.KeymanUILanguageManager.pas +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.KeymanUILanguageManager.pas @@ -48,7 +48,6 @@ class function TKeymanUILanguageManager.GetKeymanUILanguages: TStrings; begin Result := TStringList.Create; Result.Text := LowerCase(KeymanCustomisation.CustMessages.GetAvailableLanguages); - Result.Insert(0, LowerCase(KeymanCustomisation.CustMessages.MessageFromID('SKDefaultLanguageCode'))); end; end. diff --git a/windows/src/desktop/kmshell/util/UILanguages.pas b/windows/src/desktop/kmshell/util/UILanguages.pas index 83519c23d08..2cbe5dfc833 100644 --- a/windows/src/desktop/kmshell/util/UILanguages.pas +++ b/windows/src/desktop/kmshell/util/UILanguages.pas @@ -108,7 +108,7 @@ procedure TUILanguages.Refresh; begin with kmint.KeymanCustomisation.CustMessages do begin - FLanguagesAvailableString := MsgFromId(SKDefaultLanguageCode)+#13#10+GetAvailableLanguages; + FLanguagesAvailableString := GetAvailableLanguages; FLanguages.Text := FLanguagesAvailableString; for i := 0 to FLanguages.Count - 1 do diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index b414fd891c2..49287614482 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -919,11 +919,6 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to en - - - - en - diff --git a/windows/src/global/delphi/cust/CustomisationMessages.pas b/windows/src/global/delphi/cust/CustomisationMessages.pas index d79e9b32640..b1b2608a4b8 100644 --- a/windows/src/global/delphi/cust/CustomisationMessages.pas +++ b/windows/src/global/delphi/cust/CustomisationMessages.pas @@ -79,11 +79,18 @@ implementation uses DebugPaths, Keyman.System.AndroidStringToKeymanLocaleString, + KeymanPaths, + KeymanVersion, KLog, ErrorControlledRegistry, MessageIdentifierConsts, RegistryKeys; +const + S_DefaultLocale = 'en'; + S_DefaultLocaleName = 'English'; + S_TestLocale = 'qqq'; + { TCustomisationMessageManager } constructor TCustomisationMessageManager.Create(ACustStorageFilename: string; ALoadLocale: Boolean = True); @@ -127,11 +134,29 @@ function TCustomisationMessageManager.GetAvailableLanguages: string; Result := True; end; + procedure AddDefaultLanguage; + var + locale: TCustomisationLocale; + begin + locale := TCustomisationLocale.Create; + locale.ID := S_DefaultLocale; + locale.Name := S_DefaultLocaleName; + locale.NameWithEnglish := S_DefaultLocaleName; + FLanguages.Add(locale.ID, locale); + end; + begin Result := ''; - FLocalePath := ExtractFilePath(FCustStorageFileName)+'locale\'; + FLocalePath := TKeymanPaths.KeymanLocalePath; FLanguages.Clear; + // Add default locale + + AddDefaultLanguage; + Result := Result + locale.ID + #13#10; + + // Load other defined locales + try FLocaleIndexDoc := CoDomDocument.Create; FLocaleIndexDoc.preserveWhiteSpace := True; @@ -152,6 +177,9 @@ function TCustomisationMessageManager.GetAvailableLanguages: string; locale := TCustomisationLocale.Create; if not LoadLocaleData(node, locale) then locale.Free + else if (locale.ID = S_TestLocale) and (CKeymanVersionInfo.Environment <> ENVIRONMENT_LOCAL) and (CKeymanVersionInfo.Environment <> ENVIRONMENT_TEST) then + // Exclude test UI except when running a test build + locale.Free else begin FLanguages.Add(locale.ID, locale); @@ -163,7 +191,9 @@ function TCustomisationMessageManager.GetAvailableLanguages: string; except // If any locale items are invalid, we will have only English // Currently, we cannot report errors to Sentry from kmcomapi - Result := ''; + FLanguages.Clear; + AddDefaultLanguage; + Result := S_DefaultLocale; end; end; @@ -200,7 +230,7 @@ function TCustomisationMessageManager.GetLocalePathForLocale( locale: TCustomisationLocale; begin if LocaleName = '' then - Result := ExtractFilePath(FCustStorageFileName)+'locale\' + Result := TKeymanPaths.KeymanLocalePath else if not FLanguages.TryGetValue(LocaleName, locale) then Result := '' else diff --git a/windows/src/test/manual-tests/test_i2605/locale-tam.xml b/windows/src/test/manual-tests/test_i2605/locale-tam.xml index 3154776c3c7..2bcf262d626 100644 --- a/windows/src/test/manual-tests/test_i2605/locale-tam.xml +++ b/windows/src/test/manual-tests/test_i2605/locale-tam.xml @@ -236,9 +236,6 @@ Entries not in this file will be sourced from the default locale.xml file in the Tamil - - '%0:s' தட்டசு பலகை ஏற்கனவே நிறுவப்பட்டுள்ளது. தொடர்ந்தால் ஏற்கனவே உள்ளதை நீக்கி விட்டு, புதியதை நிறுவவா? From 6b77c4fd917557b2012ce22b855655099de41119 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 21 May 2026 11:17:25 +0200 Subject: [PATCH 02/72] fix(windows): set default locale correctly --- windows/src/global/delphi/cust/CustomisationMessages.pas | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/windows/src/global/delphi/cust/CustomisationMessages.pas b/windows/src/global/delphi/cust/CustomisationMessages.pas index b1b2608a4b8..46c5e6fcd9c 100644 --- a/windows/src/global/delphi/cust/CustomisationMessages.pas +++ b/windows/src/global/delphi/cust/CustomisationMessages.pas @@ -146,14 +146,13 @@ function TCustomisationMessageManager.GetAvailableLanguages: string; end; begin - Result := ''; FLocalePath := TKeymanPaths.KeymanLocalePath; FLanguages.Clear; // Add default locale AddDefaultLanguage; - Result := Result + locale.ID + #13#10; + Result := S_DefaultLocale + #13#10; // Load other defined locales From 15e181903d21b60878f6bbf77b34454d85d9514f Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Thu, 21 May 2026 16:20:53 -0400 Subject: [PATCH 03/72] chore(ios): update first voices distribution certificate --- oem/firstvoices/ios/exportAppStore.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oem/firstvoices/ios/exportAppStore.plist b/oem/firstvoices/ios/exportAppStore.plist index 35155c59ea9..a981855b265 100644 --- a/oem/firstvoices/ios/exportAppStore.plist +++ b/oem/firstvoices/ios/exportAppStore.plist @@ -7,9 +7,9 @@ teamID D7TR486TEH signingCertificate - D7B7973A30C90C1A04666667F24B4D5C7E913923 + D2FEB39A439B44ACC1E5A710D20C17587EEE29E1 installerSigningCertificate - D7B7973A30C90C1A04666667F24B4D5C7E913923 + D2FEB39A439B44ACC1E5A710D20C17587EEE29E1 provisioningProfiles com.firstvoices.keyboards From 6fa4c1612c47fc7245ff878a0d3999705f5c5121 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 22 May 2026 13:01:47 -0500 Subject: [PATCH 04/72] auto: increment master version to 19.0.238 Test-bot: skip Build-bot: skip --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 2901fca3d6b..f8ebf8b0825 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.237 alpha 2026-05-22 + +* fix(windows): Ensure default locale always appears in list of locales (#15984) +* chore(ios): update First Voices distribution certificate (#15993) + ## 19.0.236 alpha 2026-05-21 * docs(developer): write up basic internal docs on kmc modules (#15982) diff --git a/VERSION.md b/VERSION.md index 128da2f9232..d063f9a28db 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.237 \ No newline at end of file +19.0.238 \ No newline at end of file From 56148632468ea19c55bc930f3108eb41c6ad1654 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 26 May 2026 09:21:50 +0200 Subject: [PATCH 05/72] maint(linux): Don't block merge if source verification check fails If the Linux source verification check fails we don't want to block merging a PR. It behaves then similar to the web/file-size check. Related-to: #16004 Build-bot: skip Test-bot: skip --- .github/workflows/pr-build-status.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index a3d82b31629..51571b33ef6 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -81,7 +81,9 @@ jobs: } else if(status.context == 'Ubuntu Packaging') { summary += addStatus(o, 'build', status.context, status.state); } else if(status.context == 'Linux source verification') { - summary += addStatus(o, 'build', status.context, status.state); + // Ignore Linux source verification -- we won't block automerge + // for this at this point + summary += addLog(`Skipping ${status.context}`); } else if(status.context == 'npm pack/publish') { summary += addStatus(o, 'build', status.context, status.state); } else if(status.context == 'Keyman Core - ARM64 test') { From f339ceb72235a089d64ae9e2ce6ad57b00972ffb Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 26 May 2026 15:44:44 +0200 Subject: [PATCH 06/72] maint(linux): fix Linux source tarball Previously we tried to include all necessary files in the Linux source tarball, so that a user would be able to successfully run the top-level `build.sh` file. However, that causes problems when new dependencies between sub-projects get introduced that would require adding additional sub-projects to the tarball. This change modifies the tarball to only include files that are necessary to run the `linux/build.sh` file, i.e. necessary to build Keyman for Linux. This is likely what users would expect when they download a source tarball for Linux. If users want to build other parts of Keyman that are buildable on Linux they can download the source tarball that the GitHub releases page provides, or clone the repo. In order to be able to still run the top-level `build.sh` script, this change replaces the top-level `build.sh` with a script that simply forwards the arguments to `linux/build.sh`. This change also reverts the recent introduction of a *.pkg.tar.xz file that contained all files necessary to build a Debian/Ubuntu package. There wouldn't be a difference anymore between the two .tar.xz files. Fixes: #16004 Fixes: #16005 Build-bot: skip build:linux Test-bot: skip --- linux/scripts/dist.sh | 224 ++++++++---------- linux/scripts/package-build.inc.sh | 2 +- linux/scripts/verify_source.sh | 18 +- linux/scripts/watch.in | 2 +- .../teamcity/linux/keyman-linux-release.sh | 7 +- 5 files changed, 108 insertions(+), 145 deletions(-) diff --git a/linux/scripts/dist.sh b/linux/scripts/dist.sh index b66615ad9dc..a09225e3ea3 100755 --- a/linux/scripts/dist.sh +++ b/linux/scripts/dist.sh @@ -15,6 +15,101 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/linux/scripts/package-build.inc.sh" +create_tarball() { + # Include these files and folders: + # shellcheck disable=2034 # to_include appears to be unused, even though + # it is used indirectly by generate_tar_ignore_list. + to_include=( + common/build.sh \ + common/cpp \ + common/include \ + common/linux \ + common/test/keyboards/baseline \ + core \ + linux \ + resources/build/*.sh \ + resources/build/meson \ + resources/standards-data \ + resources/*.sh \ + ./*.md \ + ./build.sh \ + ./*.json \ + ) + + # files and subfolders to exclude from paths included in 'to_include', + # i.e. the exceptions to 'to_include'. + # shellcheck disable=2034 # to_exclude appears to be unused, even though + # it is used indirectly by generate_tar_ignore_list. + to_exclude=( + build \ + common/test/keyboards/baseline/kmcomp-*.zip \ + linux/builddebs \ + linux/docs/help \ + linux/keyman-config/keyman_config/version.py \ + linux/keyman-config/buildtools/build-langtags.py \ + linux/upload \ + ) + + # array to store list of --tar-ignore parameters generated from to_include and to_exclude. + ignored_files=() + + generate_tar_ignore_list "./" to_include to_exclude ignored_files "$(basename "${KEYMAN_ROOT}")" + + # Note: explicitly specify the --tar-ignores here for files/folders that we always + # want to ignore regardless of their location. Having them here allows us to pass + # the wildcards to dpkg-source - whereas the wildcards in 'to_exclude' will be + # resolved and replaced with multiple --tar-ignore entries. + dpkg-source \ + --tar-ignore=*~ \ + --tar-ignore=.git \ + --tar-ignore=.gitattributes \ + --tar-ignore=.gitignore \ + --tar-ignore=experiments \ + --tar-ignore=debian \ + --tar-ignore=.github \ + --tar-ignore=.vscode \ + --tar-ignore=.configured \ + --tar-ignore=.devcontainer \ + --tar-ignore=.pc \ + --tar-ignore=__pycache__ \ + --tar-ignore=node_modules \ + --tar-ignore=keyman_1* \ + --tar-ignore=launchpad \ + --tar-ignore=dist \ + --tar-ignore=VERSION \ + \ + "${ignored_files[@]}" \ + \ + --compression=xz --build . + mv ../keyman_"${KEYMAN_VERSION}".tar.xz linux/dist/keyman-"${KEYMAN_VERSION}".tar.xz +} + +replace_toplevel_buildsh() { + # extract the tarball and replace top-level build.sh, then recreate tarball + builder_echo heading "Replacing top-level build.sh" + cd "${KEYMAN_ROOT}/linux/dist" + tar xfJ keyman-"${KEYMAN_VERSION}".tar.xz + cat > "keyman/build.sh" << EOF +#!/usr/bin/env bash +linux/build.sh "\$@" +EOF + chmod +x "keyman/build.sh" + tar cfJ "keyman-${KEYMAN_VERSION}.tar.xz" "keyman" + rm -rf "keyman" +} + +create_debian_origtarxz() { + builder_echo heading "Creating Debian orig.tar.xz" + cd "${KEYMAN_ROOT}/linux/dist" + pkgvers="keyman-${KEYMAN_VERSION}" + tar xfJ keyman-"${KEYMAN_VERSION}".tar.xz + mv -v keyman "${pkgvers}" 2>/dev/null || mv -v "$(find . -mindepth 1 -maxdepth 1 -type d)" "${pkgvers}" + tar cfJ "keyman_${KEYMAN_VERSION}.orig.tar.xz" "${pkgvers}" + rm "keyman-${KEYMAN_VERSION}.tar.xz" + rm -rf "${pkgvers}" +} + + BASEDIR=$(pwd) cd "${KEYMAN_ROOT}/linux" @@ -24,6 +119,8 @@ if [[ ! -z ${1+x} ]] && [[ "$1" == "origdist" ]]; then shift fi +builder_echo heading "Creating source tarball for Keyman ${KEYMAN_VERSION}" + rm -rf dist mkdir -p dist @@ -34,130 +131,13 @@ echo "3.0 (native)" > debian/source/format # shellcheck disable=SC2154 dch keyman --newversion "${KEYMAN_VERSION}" --force-bad-version --nomultimaint -# Create the tarball - -# We always include these files which are the minimum files and -# folder required for Ubuntu/Debian packaging -# shellcheck disable=2034 # to_include appears to be unused -to_include=( - common/build.sh \ - common/cpp \ - common/include \ - common/linux \ - common/test/keyboards/baseline \ - core \ - linux \ - resources/build/*.sh \ - resources/build/meson \ - resources/standards-data \ - resources/*.sh \ - ./*.md \ - ./build.sh \ - ./*.json \ -) - -# files and subfolders to exclude from paths included in 'to_include', -# i.e. the exceptions to 'to_include'. - -# shellcheck disable=2034 # to_exclude appears to be unused -to_exclude=( - build \ - common/test/keyboards/baseline/kmcomp-*.zip \ - linux/builddebs \ - linux/docs/help \ - linux/keyman-config/keyman_config/version.py \ - linux/keyman-config/buildtools/build-langtags.py \ - linux/upload \ -) - -if [[ -z "${create_origdist+x}" ]]; then - # If we build a full source tarball we include additional files - # so that it's possible to run `${KEYMAN_ROOT}/build.sh` on Linux - - # shellcheck disable=2034 # to_include appears to be unused - to_include+=( - common/schemas \ - common/tools/hextobin \ - common/web/keyman-version \ - common/web/langtags \ - common/web/types \ - common/windows/cpp \ - common/windows/include \ - developer/src/common/include \ - developer/src/common/web \ - developer/src/ext/json \ - developer/src/kmc \ - developer/src/kmc-analyze \ - developer/src/kmc-copy \ - developer/src/kmc-generate \ - developer/src/kmc-keyboard-info \ - developer/src/kmc-kmn \ - developer/src/kmc-ldml \ - developer/src/kmc-model \ - developer/src/kmc-model-info \ - developer/src/kmc-package \ - developer/src/kmcmplib \ - docs/minimum-versions.md.in - resources/build \ - resources/standards-data \ - ) - - # additional files and subfolders to exclude from paths included in 'to_include', - # i.e. the exceptions to 'to_include'. - # shellcheck disable=2034 # to_exclude appears to be unused - to_exclude+=( - *.exe \ - resources/build/history \ - resources/build/l10n \ - resources/build/mac \ - resources/build/win \ - resources/build/*.lua \ - ) -fi - -# array to store list of --tar-ignore parameters generated from to_include and to_exclude. -ignored_files=() - -generate_tar_ignore_list "./" to_include to_exclude ignored_files "$(basename "${KEYMAN_ROOT}")" - -# Note: explicitly specify the --tar-ignores here for files/folders that we always -# want to ignore regardless of their location. Having them here allows us to pass -# the wildcards to dpkg-source - whereas the wildcards in 'to_exclude' will be -# resolved and replaced with multiple --tar-ignore entries. -dpkg-source \ - --tar-ignore=*~ \ - --tar-ignore=.git \ - --tar-ignore=.gitattributes \ - --tar-ignore=.gitignore \ - --tar-ignore=experiments \ - --tar-ignore=debian \ - --tar-ignore=.github \ - --tar-ignore=.vscode \ - --tar-ignore=.configured \ - --tar-ignore=.devcontainer \ - --tar-ignore=.pc \ - --tar-ignore=__pycache__ \ - --tar-ignore=node_modules \ - --tar-ignore=keyman_1* \ - --tar-ignore=launchpad \ - --tar-ignore=dist \ - --tar-ignore=VERSION \ - \ - "${ignored_files[@]}" \ - \ - -Zxz -b . - -mv ../keyman_"${KEYMAN_VERSION}".tar.xz linux/dist/keyman-"${KEYMAN_VERSION}".tar.xz +create_tarball echo "3.0 (quilt)" > debian/source/format -cd "${BASEDIR}" +replace_toplevel_buildsh # create orig.tar.xz if [[ ! -z "${create_origdist+x}" ]]; then - cd "${KEYMAN_ROOT}/linux/dist" - pkgvers="keyman-${KEYMAN_VERSION}" - tar xfJ keyman-"${KEYMAN_VERSION}".tar.xz - mv -v keyman "${pkgvers}" 2>/dev/null || mv -v "$(find . -mindepth 1 -maxdepth 1 -type d)" "${pkgvers}" - tar cfJ "keyman_${KEYMAN_VERSION}.orig.tar.xz" "${pkgvers}" - rm "keyman-${KEYMAN_VERSION}.tar.xz" - rm -rf "${pkgvers}" + create_debian_origtarxz fi + +cd "${BASEDIR}" diff --git a/linux/scripts/package-build.inc.sh b/linux/scripts/package-build.inc.sh index ca58fb9d54d..f1d6f15c692 100644 --- a/linux/scripts/package-build.inc.sh +++ b/linux/scripts/package-build.inc.sh @@ -32,7 +32,7 @@ function downloadSource() { cd .. mv "keyman-${version}" "${KEYMAN_ROOT}/linux/${packageDir}" mv "keyman_${version}.orig.tar.xz" "${KEYMAN_ROOT}/linux/${packageDir}" - mv "keyman_${version}.pkg.tar.xz" "${KEYMAN_ROOT}/linux/${packageDir}" + mv "keyman-${version}.tar.xz" "${KEYMAN_ROOT}/linux/${packageDir}" mv "keyman"*.asc "${KEYMAN_ROOT}/linux/${packageDir}" rm "keyman"*.debian.tar.xz cd "${KEYMAN_ROOT}/linux/${packageDir}" || exit diff --git a/linux/scripts/verify_source.sh b/linux/scripts/verify_source.sh index bec62599f55..2ed00b9a6f8 100755 --- a/linux/scripts/verify_source.sh +++ b/linux/scripts/verify_source.sh @@ -36,10 +36,6 @@ create_source_tarball() { ./scripts/reconf.sh PKG_CONFIG_PATH="${KEYMAN_ROOT}/core/build/arch/release/meson-private" ./scripts/dist.sh mv "dist/keyman-${KEYMAN_VERSION}.tar.xz" "${target_dir}" - - builder_echo heading "Make source for packaging" - PKG_CONFIG_PATH="${KEYMAN_ROOT}/core/build/arch/release/meson-private" ./scripts/dist.sh origdist - mv "dist/keyman_${KEYMAN_VERSION}.orig.tar.xz" "${target_dir}/keyman_${KEYMAN_VERSION}.pkg.tar.xz" } extract_source_tarball() { @@ -51,15 +47,6 @@ extract_source_tarball() { tar -xvf "keyman-${KEYMAN_VERSION}.tar.xz" } -extract_packaging_source_tarball() { - local target_dir="$1" - - builder_echo heading "Extract packaging source tarball" - cd "${target_dir}" - rm -rf "keyman-${KEYMAN_VERSION}" - tar -xvf "keyman_${KEYMAN_VERSION}.pkg.tar.xz" -} - verify_can_build() { local target_dir="$1" builder_echo heading "Verifying build of tarball" @@ -81,7 +68,7 @@ create_source_package() { cd launchpad cp -r "../keyman-${KEYMAN_VERSION}" . cp -r "${KEYMAN_ROOT}/linux/debian" "keyman-${KEYMAN_VERSION}" - cp "${target_dir}/keyman_${KEYMAN_VERSION}.pkg.tar.xz" "keyman_${KEYMAN_VERSION}.orig.tar.xz" + cp "${target_dir}/keyman-${KEYMAN_VERSION}.tar.xz" "keyman_${KEYMAN_VERSION}.orig.tar.xz" "keyman-${KEYMAN_VERSION}/linux/scripts/launchpad.sh" --no-download \ --dist "$(lsb_release -c -s)" --outputdir "${target_dir}/launchpad" --no-lintian --no-sign } @@ -124,7 +111,8 @@ fi if ! builder_has_option --source-only; then builder_echo start lintian "Verifying Launchpad source package" - extract_packaging_source_tarball "${TARGET_DIR}" + extract_source_tarball "${TARGET_DIR}" + mv "${TARGET_DIR}/keyman" "${TARGET_DIR}/keyman-${KEYMAN_VERSION}" create_source_package "${TARGET_DIR}" verify_lintian rm -rf "${TARGET_DIR}/keyman-${KEYMAN_VERSION}" diff --git a/linux/scripts/watch.in b/linux/scripts/watch.in index 82a5ce6cde7..b4c0039a846 100644 --- a/linux/scripts/watch.in +++ b/linux/scripts/watch.in @@ -1,3 +1,3 @@ version=4 # Tier replaced by package-build.inc.sh script -opts=pgpsigurlmangle=s/$/.asc/ https://downloads.keyman.com/linux/$tier/@ANY_VERSION@/@PACKAGE@@ANY_VERSION@.pkg@ARCHIVE_EXT@ debian uupdate +opts=pgpsigurlmangle=s/$/.asc/ https://downloads.keyman.com/linux/$tier/@ANY_VERSION@/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@ debian uupdate diff --git a/resources/teamcity/linux/keyman-linux-release.sh b/resources/teamcity/linux/keyman-linux-release.sh index 9fb73ef91e9..9cc1b76a1b6 100755 --- a/resources/teamcity/linux/keyman-linux-release.sh +++ b/resources/teamcity/linux/keyman-linux-release.sh @@ -61,15 +61,11 @@ function _make_release_source_tarball() { ./scripts/reconf.sh PKG_CONFIG_PATH="${KEYMAN_ROOT}/core/build/arch/release/meson-private" ./scripts/dist.sh mv dist/*.tar.xz "upload/${KEYMAN_VERSION}/" - builder_echo heading "Make source for packaging" - PKG_CONFIG_PATH="${KEYMAN_ROOT}/core/build/arch/release/meson-private" ./scripts/dist.sh origdist - mv "dist/keyman_${KEYMAN_VERSION}.orig.tar.xz" "dist/keyman_${KEYMAN_VERSION}.pkg.tar.xz" - mv dist/*.tar.xz "upload/${KEYMAN_VERSION}/" ( cd "upload/${KEYMAN_VERSION}" sha256sum ./*.tar.xz > SHA256SUMS - builder_echo end "make source tarball" success "Make source tarball" ) + builder_echo end "make source tarball" success "Make source tarball" } function _sign_source_tarball() { @@ -100,7 +96,6 @@ function _publish_to_downloads() { chmod a+r "${UPLOAD_DIR}"/* write_download_info "${UPLOAD_DIR}" "keyman-${KEYMAN_VERSION}.tar.xz" "Keyman for Linux source tarball" tar.xz linux - write_download_info "${UPLOAD_DIR}" "keyman_${KEYMAN_VERSION}.pkg.tar.xz" "Keyman for Linux source for packaging" tar.xz linux tc_rsync_upload "${UPLOAD_DIR}" "linux/${KEYMAN_TIER}" builder_echo end "publish to downloads" success "Publish to downloads.keyman.com" From 4fd5e59a8543ea9f4096bd004a8a02e36f34bf33 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 26 May 2026 13:01:44 -0500 Subject: [PATCH 07/72] auto: increment master version to 19.0.239 Test-bot: skip Build-bot: skip --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index f8ebf8b0825..4d2638e6730 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.238 alpha 2026-05-26 + +* maint(linux): Don't block merge if source verification check fails (#16010) +* maint(linux): fix Linux source tarball (#16011) + ## 19.0.237 alpha 2026-05-22 * fix(windows): Ensure default locale always appears in list of locales (#15984) diff --git a/VERSION.md b/VERSION.md index d063f9a28db..a8d1a233ce1 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.238 \ No newline at end of file +19.0.239 \ No newline at end of file From 3c11eae9644b9427efa0029c59f4f605a9e4cb5b Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 27 May 2026 11:55:06 +0200 Subject: [PATCH 08/72] chore(web): remove obsolete file This change removes a file that is no longer needed. The link is redirected to keyman.com, so it's kind of pointless to keep it around. Build-bot: skip Test-bot: skip --- web/NOTICE | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 web/NOTICE diff --git a/web/NOTICE b/web/NOTICE deleted file mode 100644 index 116bef67679..00000000000 --- a/web/NOTICE +++ /dev/null @@ -1,5 +0,0 @@ - Tavultesoft KeymanWeb 2.0 - Copyright 2005-2014 Tavultesoft Pty Ltd - - This product includes software developed at - Tavultesoft (http://www.tavultesoft.com/). From 39fba5e29c70bf2593a48b293a6a86ff9c4d4d92 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 27 May 2026 15:17:48 +0200 Subject: [PATCH 09/72] chore(core): fixup const cast --- core/src/km_core_processevent_api.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/km_core_processevent_api.cpp b/core/src/km_core_processevent_api.cpp index b7f34c14e00..d37ca45518c 100644 --- a/core/src/km_core_processevent_api.cpp +++ b/core/src/km_core_processevent_api.cpp @@ -37,8 +37,8 @@ km_core_event( return KM_CORE_STATUS_INVALID_ARGUMENT; } - km_core_status status = (state)->processor().external_event((state), event, data); - state->apply_actions_and_merge_app_context(); + km_core_status status = const_cast(state)->processor().external_event(const_cast(state), event, data); + const_cast(state)->apply_actions_and_merge_app_context(); return status; } From 748ad4aa01455fc56c49d7f15d5ca149c77d0c48 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 27 May 2026 17:17:23 +0200 Subject: [PATCH 10/72] chore(web): add filename to link text of manual web tests This change appends the filename to the description of the tests on the index page of the manual web tests. This makes it easier to find the test on the rendered index page when having the directory name of the test available. Build-bot: skip Test-bot: skip --- web/src/test/manual/web/index.html | 101 ++++++++++++++--------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/web/src/test/manual/web/index.html b/web/src/test/manual/web/index.html index 6d608b08d33..fa254254d08 100644 --- a/web/src/test/manual/web/index.html +++ b/web/src/test/manual/web/index.html @@ -20,64 +20,63 @@ -

KeymanWeb 17 Testing

-

Test unminified Keymanweb

-

Test unminified Keymanweb in manual-attachment mode.

-

Prediction - robust testing

-

Desktop UI module testing

-

Tests keyboard documentation rendering.

-

Stand-alone gesture recognition

+

KeymanWeb 19 Testing

+

Test unminified Keymanweb (unminified)

+

Test unminified Keymanweb in manual-attachment mode (unminified - manual)

+

Prediction - robust testing (prediction-mtnt)

+

Desktop UI module testing (desktop-ui)

+

Tests keyboard documentation rendering (build-visual-keyboard)

+

Stand-alone gesture recognition (gesture-recognizer)

Miscellaneous tests for smaller features

-

Chirality testing/bootstrapping

-

Tests option/variable store functionality

-

Platform testing

-

Prediction UI testing

-

Promise API testing

-

Tests Sentry error-reporting functionality

-

Test page for osk setRect() and restorePosition() interaction

-

Integration with CKEditor

-

Tests toggling & use of desktop OSK config & help buttons

+

Chirality testing/bootstrapping (chirality)

+

Tests option/variable store functionality (options-with-save)

+

Platform testing (platform)

+

Prediction UI testing (prediction-ui)

+

Promise API testing (promise-api)

+

Tests Sentry error-reporting functionality (sentry-integration)

+

Test page for osk setRect() and restorePosition() interaction (osk-movement)

+

Integration with CKEditor (ckeditor)

+

Tests toggling & use of desktop OSK config & help buttons (osk-event-buttons)

Page integration (attachment) tests

-

Tests the Attachment/Enablement API functionality

-

Test desktop MutationObserver functionality

-

Light test for touch-based MutationObserver functionality

-

Test/stress-test touch-based MutationObserver functionality

+

Tests the Attachment/Enablement API functionality (attachment-api)

+

Test desktop MutationObserver functionality (issue29)

+

Light test for touch-based MutationObserver functionality (issue62)

+

Test/stress-test touch-based MutationObserver functionality (issue63)

Issue Testing

-

Tests OSK handling of empty rows

-

Tests keyboard error-handling functionality

-

'Test case' for proper implementation of the removeKeyboards API function.

-

Tests layering transitions for keyboards with variable layer sizes.

-

Tests keyboard loading with repeated requests

-

Long-press on numeric and symbolic layer testing

-

Next-layer processing testing

-

Uxxxx code testing

-

Test KMW beep

-

Test page for interactions between setActiveKeyboard and the toolbar UI.

-

Test keyboard loading with upper case TTF font

-

Test page for IFrame connectivity and class prototype management

-

Test page for mnemonic basic support on US English

-

Test page for evaluating issues with mobile device rotation events

-

Test page for automatic key-cap scaling

-

Test page for fat-finger interaction with beep feedback

-

Test page for context() and notany() interaction

-

Test SpacebarText APIs (#949)

-

Test inline OSK (#5665)

-

Test variable stores and predictive text (#2924)

-

Test Caps Lock Layer (#3620)

-

Test Start of Sentence (#3621)

-

Test start of sentence keyboard rules (#5963)

-

Tests predictive text & other handling of rule matching when the final rule group does not match (#6005)

-

Tests handling of new default-subkey feature (#9430)

-

Test special characters rendering with keymanweb-osk.ttf (#9469)

-

Test text selection (#9073)

-

Test key-cap scaling / font load interactions (#10506)

-

Test page interaction + engine-initialization race condition handling (#10743)

-

Test OSK loading with early add-keyboard calls (#11785)

+

Tests OSK handling of empty rows (empty-row)

+

Tests keyboard error-handling functionality (keyboard-errors)

+

'Test case' for proper implementation of the removeKeyboards API function. (issue005)

+

Tests layering transitions for keyboards with variable layer sizes. (issue53)

+

Tests keyboard loading with repeated requests (issue103)

+

Long-press on numeric and symbolic layer testing (issue115)

+

Next-layer processing testing (issue116)

+

Uxxxx code testing (issue160)

+

Test KMW beep (issue266)

+

Test page for interactions between setActiveKeyboard and the toolbar UI (issue271)

+

Test keyboard loading with upper case TTF font (issue1332)

+

Test page for IFrame connectivity and class prototype management (basic-iframe)

+

Test page for mnemonic basic support on US English (mnemonic)

+

Test page for evaluating issues with mobile device rotation events (rotation-events)

+

Test page for automatic key-cap scaling (issue382)

+

Test page for fat-finger interaction with beep feedback (issue3701)

+

Test page for context() and notany() interaction (issue917-context-and-notany)

+

Test SpacebarText APIs (#949) (spacebar-text)

+

Test inline OSK (#5665) (inline-osk)

+

Test variable stores and predictive text (#2924) (issue2924)

+

Test Caps Lock Layer (#3620) (caps-lock-layer-3620)

+

Test start of sentence keyboard rules (#3621/#5963) (start-of-sentence-3621)

+

Tests predictive text & other handling of rule matching when the final rule group does not match (#6005) (issue6005)

+

Tests handling of new default-subkey feature (#9430) (default-subkey)

+

Test special characters rendering with keymanweb-osk.ttf (#9469) (issue9469)

+

Test text selection (#9073) (text_selection_tests_9073)

+

Test key-cap scaling / font load interactions (#10506) (pr10506)

+

Test page interaction + engine-initialization race condition handling (#10743) (init-race-10743)

+

Test OSK loading with early add-keyboard calls (#11785) (issue11785)

Other

-

Keystroke processing regression test engine.

+

Keystroke processing regression test engine (regression-tests)


Return to main index From 78ea935fe520a4014345fa11bf17d4349ca90542 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 27 May 2026 19:33:07 +0200 Subject: [PATCH 11/72] docs(web): reformat and small fixes - rewrap lines - change http:// to https:// Build-bot: skip Test-bot: skip --- web/docs/engine/reference/core/addKeyboards.md | 15 +++++++++------ .../reference/core/addKeyboardsForLanguage.md | 17 ++++++++++++----- .../src/keyboards/keyboardProperties.ts | 4 ++-- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/web/docs/engine/reference/core/addKeyboards.md b/web/docs/engine/reference/core/addKeyboards.md index 4a2dc77f5db..0f312bee4ce 100644 --- a/web/docs/engine/reference/core/addKeyboards.md +++ b/web/docs/engine/reference/core/addKeyboards.md @@ -22,11 +22,14 @@ keyman.addKeyboards(spec[, spec...]) ### Return Value -`Promise`: A [JavaScript Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +`Promise`: A [JavaScript +Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) fulfilled upon adding keyboards. The promise is an array containing a combination of the following: -* successfully registered keyboard objects which define some or all of these [properties](../keyboard_properties) + +* successfully registered keyboard objects which define some or all of these + [properties](../keyboard_properties) * [ErrorStub](../keyboard_registration_errors) objects for keyboards that failed to register ## Description @@ -35,7 +38,8 @@ The keyboard spec can be a string or an object. Multiple keyboard specs can be specified in a single call, which can reduce the round-trip cost of multiple calls to Keyman Cloud servers (when using Keyman Cloud). -For general information and example uses of this method, please see the [Adding Keyboards](../../guide/adding-keyboards) page from the guide section. +For general information and example uses of this method, please see the [Adding +Keyboards](../../guide/adding-keyboards) page from the guide section. ### Using a `string` @@ -48,7 +52,7 @@ The string format is one of the following: * `'keyboardID@languageID'`: Loads a specific keyboard + language combination The keyboard catalogue is online at -[http://keyman.com/developer/keymanweb/keyboards](http://keyman.com/developer/keymanweb/keyboards). +[https://keyman.com/developer/keymanweb/keyboards](https://keyman.com/developer/keymanweb/keyboards). ### Using an `object` @@ -59,7 +63,6 @@ known as KeymanWeb Server Data API): The `spec` object contains the following members: - `name` : `string` @@ -143,7 +146,7 @@ The `spec.languages` object contains the following members: : `array|object` optional - An array of Font objects (see definition below) or single object describing + An array of Font objects (see `font` definition below) or single object describing fonts for input fields and the OSK (if `oskFont` is not present.) `oskFont` diff --git a/web/docs/engine/reference/core/addKeyboardsForLanguage.md b/web/docs/engine/reference/core/addKeyboardsForLanguage.md index d06ce2ede22..5e8ed92c9e0 100644 --- a/web/docs/engine/reference/core/addKeyboardsForLanguage.md +++ b/web/docs/engine/reference/core/addKeyboardsForLanguage.md @@ -18,15 +18,19 @@ keyman.addKeyboardsForLanguage(spec[, spec...]) : Type: `string` - Language name string. Appending `$` to the language name will cause all available keyboards for that language to be loaded rather than the default keyboard. + Language name string. Appending `$` to the language name will cause all available keyboards for + that language to be loaded rather than only the default keyboard. ### Return Value -`Promise`: A [JavaScript Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +`Promise`: A [JavaScript +Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) fulfilled upon adding keyboards. The promise is an array containing the following: -* successfully registered keyboard objects which define some or all of these [properties](../keyboard_properties) + +* successfully registered keyboard objects which define some or all of these + [properties](../keyboard_properties) ## Description @@ -34,9 +38,12 @@ The language spec is a string. Multiple language specs can also be specified in a single call, which can reduce the round-trip cost of multiple calls to Keyman Cloud servers (when using Keyman Cloud). -The first call to `addKeyboardsForLanguage()` makes an additional call to the Keyman API to load the current list of keyboard/language associations. This determines the default keyboards that are added for the language. +The first call to `addKeyboardsForLanguage()` makes an additional call to the Keyman API to load +the current list of keyboard/language associations. This determines the default keyboards that +are added for the language. -For general information and example uses of this method, please see the [Adding Keyboards](../../guide/adding-keyboards) page from the guide section. +For general information and example uses of this method, please see the [Adding +Keyboards](../../guide/adding-keyboards) page from the guide section. ### Using a `string` diff --git a/web/src/engine/keyboard/src/keyboards/keyboardProperties.ts b/web/src/engine/keyboard/src/keyboards/keyboardProperties.ts index 7f1d43282e3..0fafc150fbd 100644 --- a/web/src/engine/keyboard/src/keyboards/keyboardProperties.ts +++ b/web/src/engine/keyboard/src/keyboards/keyboardProperties.ts @@ -75,7 +75,7 @@ export type LanguageAPIPropertySpec = { * Corresponds to the documented API for the Web engine's `addKeyboards` function * when a single language object is specified - not an array. * - * See https://help.keyman.com/developer/engine/web/15.0/reference/core/addKeyboards, + * See https://help.keyman.com/developer/engine/web/current-version/reference/core/addKeyboards, * "Using an `object`". */ export type KeyboardAPIPropertySpec = { @@ -93,7 +93,7 @@ export type KeyboardAPIPropertySpec = { * Corresponds to the documented API for the Web engine's `addKeyboards` function * when a language array is specified for the object. * - * See https://help.keyman.com/developer/engine/web/15.0/reference/core/addKeyboards, + * See https://help.keyman.com/developer/engine/web/current-version/reference/core/addKeyboards, * "Using an `object`". */ export type KeyboardAPIPropertyMultilangSpec = { From 2b10964d8572d8ef5f2b2dd2813798f9065462e5 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 27 May 2026 13:01:34 -0500 Subject: [PATCH 12/72] auto: increment master version to 19.0.240 Test-bot: skip Build-bot: skip --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 4d2638e6730..d5eee7acb71 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.239 alpha 2026-05-27 + +* chore(web): remove obsolete file (#16013) + ## 19.0.238 alpha 2026-05-26 * maint(linux): Don't block merge if source verification check fails (#16010) diff --git a/VERSION.md b/VERSION.md index a8d1a233ce1..cdc8b4ee8e7 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.239 \ No newline at end of file +19.0.240 \ No newline at end of file From f61ad57544c49042601475d9c398280cd3d963d1 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 28 May 2026 10:28:40 +0200 Subject: [PATCH 13/72] refactor(web): don't wrap in the middle of links Addresses code review comment. --- web/docs/engine/reference/core/addKeyboards.md | 7 +++---- web/docs/engine/reference/core/addKeyboardsForLanguage.md | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/web/docs/engine/reference/core/addKeyboards.md b/web/docs/engine/reference/core/addKeyboards.md index 0f312bee4ce..fc5e3ce07bf 100644 --- a/web/docs/engine/reference/core/addKeyboards.md +++ b/web/docs/engine/reference/core/addKeyboards.md @@ -22,8 +22,7 @@ keyman.addKeyboards(spec[, spec...]) ### Return Value -`Promise`: A [JavaScript -Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +`Promise`: A [JavaScript Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) fulfilled upon adding keyboards. The promise is an array containing a combination of the following: @@ -38,8 +37,8 @@ The keyboard spec can be a string or an object. Multiple keyboard specs can be specified in a single call, which can reduce the round-trip cost of multiple calls to Keyman Cloud servers (when using Keyman Cloud). -For general information and example uses of this method, please see the [Adding -Keyboards](../../guide/adding-keyboards) page from the guide section. +For general information and example uses of this method, please see the +[Adding Keyboards](../../guide/adding-keyboards) page from the guide section. ### Using a `string` diff --git a/web/docs/engine/reference/core/addKeyboardsForLanguage.md b/web/docs/engine/reference/core/addKeyboardsForLanguage.md index 5e8ed92c9e0..bf97a06b8d7 100644 --- a/web/docs/engine/reference/core/addKeyboardsForLanguage.md +++ b/web/docs/engine/reference/core/addKeyboardsForLanguage.md @@ -23,8 +23,7 @@ keyman.addKeyboardsForLanguage(spec[, spec...]) ### Return Value -`Promise`: A [JavaScript -Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) +`Promise`: A [JavaScript Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) fulfilled upon adding keyboards. The promise is an array containing the following: @@ -42,8 +41,8 @@ The first call to `addKeyboardsForLanguage()` makes an additional call to the Ke the current list of keyboard/language associations. This determines the default keyboards that are added for the language. -For general information and example uses of this method, please see the [Adding -Keyboards](../../guide/adding-keyboards) page from the guide section. +For general information and example uses of this method, please see the +[Adding Keyboards](../../guide/adding-keyboards) page from the guide section. ### Using a `string` From 729c71d64db8011a5fda62a2cfcf3a36886bf776 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 28 May 2026 14:16:19 +0200 Subject: [PATCH 14/72] chore: minor cleanup prior to merge of web-core preflight --- common/web/sentry-manager/build.sh | 4 +- core/src/layout.hpp | 185 ----------------------------- core/src/meson.build | 2 +- 3 files changed, 3 insertions(+), 188 deletions(-) delete mode 100644 core/src/layout.hpp diff --git a/common/web/sentry-manager/build.sh b/common/web/sentry-manager/build.sh index 4e725290e49..b2bcea8d94f 100755 --- a/common/web/sentry-manager/build.sh +++ b/common/web/sentry-manager/build.sh @@ -11,7 +11,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe "Sentry error reporting module for embedded Keyman Engine for Web" \ "@/common/tools/es-bundling" \ "@/common/web/keyman-version" \ - clean configure build + clean configure build test builder_describe_outputs \ configure /node_modules \ @@ -22,7 +22,7 @@ builder_parse "$@" # --------------------------------------------------------------------------------- function do_build() { - tsc --build "./tsconfig.json" + tsc --build node_es_bundle "${KEYMAN_ROOT}/common/web/sentry-manager/build/obj/src/index.js" \ --out "${KEYMAN_ROOT}/common/web/sentry-manager/build/lib/index.js" \ diff --git a/core/src/layout.hpp b/core/src/layout.hpp deleted file mode 100644 index 2e3d2833b4a..00000000000 --- a/core/src/layout.hpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Keyman is copyright (C) SIL International. MIT License. - * - * Keyman Keyboard Processor API - On-Screen Keyboard Layout Interfaces - */ - -#pragma once - -#include -#include -#include - -#include "keyman_core_api.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -/** - * Possible directions of a flick - */ -enum keyboard_layout_flick_direction { - /** flick up (north) */ - n = 0, - /** flick down (south) */ - s = 1, - /** flick right (east) */ - e = 2, - /** flick left (west) */ - w = 3, - /** flick up-right (north-east) */ - ne = 4, - /** flick up-left (north-west) */ - nw = 5, - /** flick down-right (south-east) */ - se = 6, - /** flick down-left (south-west) */ - sw = 7 -}; - -/** - * key type like regular key, framekeys, deadkeys, blank, etc. - */ -enum keyboard_layout_key_type { - /** regular key */ - normal = 0, - /** A 'frame' key, such as Shift or Enter */ - special = 1, - /** A 'frame' key, such as Shift or Enter, which is is active, such as - * the shift key on a shift layer */ - specialActive = 2, - /** **KeymanWeb runtime private use:** a variant of `special` with the - * keyboard font rather than 'KeymanwebOsk' font */ - customSpecial = 3, - /** **KeymanWeb runtime private use:** a variant of `specialActive` with the - * keyboard font rather than 'KeymanwebOsk' font. */ - customSpecialActive = 4, - /** A deadkey */ - deadkey = 8, - /** A key which is rendered as a blank keycap, should block any interaction */ - blank = 9, - /** Renders the key only as a gap or spacer, should block any interaction */ - spacer = 10 -}; - -/** - * A key on a touch layout/on-screen keyboard - */ -struct keyboard_layout_key { - /** key id */ - std::u16string id; // TODO-WEB-CORE: perhaps necessary for special keys, Enter, etc? or can we get that from virtualKey? - /** the virtual key code */ - int virtualKey; // TODO-WEB-CORE: do we need this? both id and virtualKey? Or just one of them? - /** text to display on key cap */ - std::u16string display; - /** hint e.g. for longpress */ - std::u16string hint; - /** the type of key */ - keyboard_layout_key_type type; - - /** - * the modifier combination (not layer) that should be used in key events, - * for this key, overriding the layer that the key is a part of. - */ - int modifiersOverride; - /** the next layer to switch to after this key is pressed */ - std::u16string nextLayerId; - - // touch layouts only - - /** padding - space to the left of key (in what units?) */ - int gap; - /** width of the key (in what units?) */ - int width; - - /** longpress keys, also known as subkeys */ - std::vector longpresses; - /** multitaps */ - std::vector multiTaps; - /** flicks */ - std::map flicks; -}; - -/** - * a row of keys on a touch layout/on-screen keyboard - */ -struct keyboard_layout_row { - /** row id */ - int id; // TODO-WEB-CORE: do we need this? Web has it (`TouchLayoutRow`) - /** keys in this row */ - std::vector keys; -}; - -/** - * a layer with rows of keys on a touch layout/on-screen keyboard - */ -struct keyboard_layout_layer { - /** layer id */ - std::u16string id; - /** layer modifiers */ - // TODO-WEB-CORE: we added this during our discussion, but Web doesn't have it. - // Should be an enum if it's needed. - int modifiers; //? 0 = default, n = shift, etc. -1 = unspecified? - /** rows in this layer */ - std::vector rows; -}; - -/** - * layout specification for a specific platform like desktop, phone or tablet - */ -struct keyboard_layout_platform { - /** platform form factor, e.g. 'iso', 'touch', 'ansi', ... (see ldml spec) */ - std::u16string form; - /** width of screen for touch layout */ - int minWidthMm; // we don't have mobile vs tablet, instead use this - /** layers for this platform */ - std::vector layers; - - // TODO-WEB-CORE: Do we need these: - // Web additionally has: - // - font (should be in CSS; we have it in `keyboard_layout`) - // - fontsize (should be in CSS; we have it in `keyboard_layout`) - // - displayUnderlying - // - defaultHint ("none"|"dot"|"longpress"|"multitap"|"flick"|"flick-n"|"flick-ne"| - // "flick-e"|"flick-se"|"flick-s"|"flick-sw"|"flick-w"|"flick-nw") -}; - -/** - * On screen keyboard description consisting of specific layouts for different - * form factors. - */ -struct keyboard_layout { - /** layouts for different form factors */ - std::vector platforms; - /** font face name to use for key caps*/ - std::string fontFacename; - /** font size to use for key caps */ - int fontSizeEm; // TODO-WEB-CORE: em? px? something else? -}; - - -/** - * Get the on-screen keyboard layout for the specified keyboard. - * - * @param keyboard [in] The keyboard to get the layout for. - * @param layout [out] The on-screen keyboard layout. - * @return km_core_status `KM_CORE_STATUS_OK`: On success. - * `KM_CORE_STATUS_INVALID_ARGUMENT`: If `keyboard` is not a valid keyboard or `layout` is null. - */ -km_core_status -keyboard_get_layout( - km_core_keyboard const* keyboard, - keyboard_layout** layout -); - - -/** - * Dispose the on-screen keyboard layout. - */ -void -keyboard_layout_dispose(keyboard_layout* layout); - -#if defined(__cplusplus) -} -#endif diff --git a/core/src/meson.build b/core/src/meson.build index 128d72b7f76..560a66fc32f 100644 --- a/core/src/meson.build +++ b/core/src/meson.build @@ -209,7 +209,7 @@ if cpp_compiler.get_id() == 'emscripten' name_suffix: 'mjs') if get_option('buildtype') == 'release' - # TODO: #12888 + # TODO-WEB-CORE # Split debug symbols into separate wasm file for release builds only # as the release symbols will be uploaded to sentry # custom_target('core.wasm', From f44e3914f350a53e378b0bde70d0f2051ea34dae Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 28 May 2026 13:01:50 -0500 Subject: [PATCH 15/72] auto: increment master version to 19.0.241 Test-bot: skip Build-bot: skip --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index d5eee7acb71..462bb9a48df 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.240 alpha 2026-05-28 + +* chore(web): add filename to link text of manual web tests (#16019) +* docs(web): reformat and small fixes (#16021) + ## 19.0.239 alpha 2026-05-27 * chore(web): remove obsolete file (#16013) diff --git a/VERSION.md b/VERSION.md index cdc8b4ee8e7..e14cb86f3e7 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.240 \ No newline at end of file +19.0.241 \ No newline at end of file From b45cee7703d7fa2f05eecbcd9b45243976f0c2a4 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 29 May 2026 09:34:04 +0200 Subject: [PATCH 16/72] docs: add note on how to use composer on dockerized websites Relates-to: keymanapp/keyman.com#758 Test-bot: skip Build-bot: skip --- docs/websites/README.md | 51 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/websites/README.md b/docs/websites/README.md index 9813fcc2aec..165fb6a718a 100644 --- a/docs/websites/README.md +++ b/docs/websites/README.md @@ -71,7 +71,7 @@ After this, you can access the website at the following ports: | status.keyman.com* | http://localhost:8060 | http://status-backend.keyman.com.localhost | status-keyman-website | | | http://localhost:8061 | http://status.keyman.com.localhost | status-keyman-public | -\* Note that status.keyman.com runs in two containers in development to allow for live reload and separation of logs for backend and frontend. +\* Note that status.keyman.com runs in two containers in development to allow for live reload and separation of logs for backend and frontend. In production, only port 8060 is used. #### Remove the Docker container and image @@ -95,6 +95,55 @@ docker logs -f {Docker Container Name} Refer to **Port lookup table** above for Docker container names +--------- + +## How to run composer updates + +The Docker containers are setup with multi-stage images, so you will need to +target the composer-builder stage if you want to execute composer commands such +as `composer update` or `composer audit`. The following scripts show how to do +this; here the same id is used for the image tag and the container name for simplicity: + +On macOS, Linux: + +```bash +COMPOSER_ID=composer-temp +# setup +docker build -f Dockerfile --target composer-builder --tag $COMPOSER_ID . +docker run -v "$(pwd):/var/www/html/" --name $COMPOSER_ID --user root --rm -d $COMPOSER_ID +# run commands with `docker exec`, e.g.: +# docker exec $COMPOSER_ID composer audit +# docker exec $COMPOSER_ID composer update +# docker exec $COMPOSER_ID composer update --lock +# copy modified files to mounted volume: +docker exec $COMPOSER_ID cp composer.lock /var/www/html/ +docker exec $COMPOSER_ID cp composer.json /var/www/html/ +# cleanup +docker stop $COMPOSER_ID +docker rmi $COMPOSER_ID +``` + +On git bash for Windows, paths must start with `//`, e.g. `//$(pwd)`, `//var/www/html/`: + +```bash +COMPOSER_ID=composer-temp +# setup +docker build -f Dockerfile --target composer-builder --tag $COMPOSER_ID . +docker run -v "//$(pwd):/var/www/html/" --name $COMPOSER_ID --user root --rm -d $COMPOSER_ID +# run commands with `docker exec`, e.g.: +# docker exec $COMPOSER_ID composer audit +# docker exec $COMPOSER_ID composer update +# docker exec $COMPOSER_ID composer update --lock +# copy modified files to mounted volume: +docker exec $COMPOSER_ID cp composer.lock //var/www/html/ +docker exec $COMPOSER_ID cp composer.json //var/www/html/ +# cleanup +docker stop $COMPOSER_ID +docker rmi $COMPOSER_ID +``` + + + --------- ## Website Dependencies From 73a0dd53181d8a52a77430eaf88af00e2f0d72be Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 20 May 2026 18:10:04 +0200 Subject: [PATCH 17/72] maint(web): fix Playwright TC reporter Previously the loop at the beginning of `end` had an infinite loop because the child was never removed from `childrenToVisit`. This change refactors and fixes the loop. Build-bot: skip build:web Test-bot: skip --- .../test/resources/playwright-TC-reporter.ts | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/common/test/resources/playwright-TC-reporter.ts b/common/test/resources/playwright-TC-reporter.ts index 84f3eede0d0..c80e73e1806 100644 --- a/common/test/resources/playwright-TC-reporter.ts +++ b/common/test/resources/playwright-TC-reporter.ts @@ -14,7 +14,7 @@ class TestNode { private id: string; private suiteOrTest: Suite | TestCase; private flowId: number; - private parent: TestNode; + private parent: TestNode | null; private childrenToVisit: TestNode[] = []; public constructor(suiteOrTest: Suite | TestCase) { @@ -89,12 +89,20 @@ class TestNode { private end(result: TestResult, force: boolean = false): void { if (this.childrenToVisit.length > 0 && force) { - while (this.childrenToVisit.length > 0) { - const child = this.childrenToVisit[0]; - child.end(result, force); + for (const child of [...this.childrenToVisit]) { + child.end(result, true); } + this.childrenToVisit.length = 0; } + // Only run this block on a normal (non-forced) end. Forced ends happen during endAll() + // teardown for nodes that didn't complete normally, where we have no meaningful result to + // report and the node's start/finish pairing may be incomplete. In that case we also skip the + // cleanup below because endAll()'s safety-net loop drains OpenNodes / Nodes globally, and our + // parent is already iterating its own children and clearing childrenToVisit itself — + // re-running removeFromOpenNodes / Nodes.delete / removeChild here would double-remove and + // log spurious errors, or trigger a re-entrant parent.end(null, false) cascade in the middle + // of the ongoing iteration. if (!force) { if (this.suiteOrTest.title !== '') { if (this.isTest) { @@ -103,10 +111,10 @@ class TestNode { } else { console.log(`##teamcity[testSuiteFinished name='${this.suiteOrTest.title}']`); } - console.log(`##teamcity[flowFinished flowId = '${this.flowId}']`); + console.log(`##teamcity[flowFinished flowId='${this.flowId}']`); } - this.removeFromOpenNodes(); + this.removeFromOpenNodes(); this.parent?.removeChild(this); TestNode.Nodes.delete(this.id); } @@ -122,6 +130,10 @@ class TestNode { } private removeChild(child: TestNode) { + if (this.childrenToVisit.length == 0) { + // already cleared all children in a end(result, true) call, nothing to do + return; + } const childIndex = this.childrenToVisit.indexOf(child); if (childIndex < 0) { console.error(`Can't find child '${child.id}' in parent '${this.id}'`); @@ -166,6 +178,12 @@ class TestNode { const id = TestNode.OpenNodes[TestNode.OpenNodes.length - 1]; console.log(`Closing '${id}'`); const node = TestNode.Nodes.get(id); + if (!node) { + console.error(`Node for '${id}' not found in Nodes map, removing from OpenNodes`); + TestNode.OpenNodes.pop(); + continue; + } + node.end(null); } } @@ -179,7 +197,7 @@ class TestNode { } export default class PlaywrightTeamcityReporter implements Reporter { - private root: TestNode = null; + private root!: TestNode; public constructor(options: { parentFlow?: string } = {}) { TestNode.RootFlow = options.parentFlow ?? 'unit_tests'; From 5dab671f2226ff2f4b9fff763b04879d4b1df7d1 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 21 May 2026 17:38:41 +0200 Subject: [PATCH 18/72] fix(web): address code review comments Co-authored-by: Marc Durdin --- common/test/resources/playwright-TC-reporter.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/common/test/resources/playwright-TC-reporter.ts b/common/test/resources/playwright-TC-reporter.ts index c80e73e1806..a6bfeb96d33 100644 --- a/common/test/resources/playwright-TC-reporter.ts +++ b/common/test/resources/playwright-TC-reporter.ts @@ -16,6 +16,7 @@ class TestNode { private flowId: number; private parent: TestNode | null; private childrenToVisit: TestNode[] = []; + private childrenCleared: boolean = false; public constructor(suiteOrTest: Suite | TestCase) { this.suiteOrTest = suiteOrTest; @@ -88,11 +89,13 @@ class TestNode { } private end(result: TestResult, force: boolean = false): void { - if (this.childrenToVisit.length > 0 && force) { + if (force) { + // use a copy of the array because child.end will mutate the array for (const child of [...this.childrenToVisit]) { child.end(result, true); } - this.childrenToVisit.length = 0; + this.childrenToVisit = []; + this.childrenCleared = true; } // Only run this block on a normal (non-forced) end. Forced ends happen during endAll() @@ -130,7 +133,7 @@ class TestNode { } private removeChild(child: TestNode) { - if (this.childrenToVisit.length == 0) { + if (this.childrenCleared) { // already cleared all children in a end(result, true) call, nothing to do return; } From 697e6972eee368cbb252ec3610ac2805038642d7 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 21 May 2026 18:00:16 +0200 Subject: [PATCH 19/72] fix(web): further simplification Turns out `end(result, true)` didn't actually do anything except calling `end(result, true)` on each child, which resulted basically in a no-op. Therefore this change removes the `force` parameter from `end()`. --- .../test/resources/playwright-TC-reporter.ts | 49 +++++-------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/common/test/resources/playwright-TC-reporter.ts b/common/test/resources/playwright-TC-reporter.ts index a6bfeb96d33..ad082dedf48 100644 --- a/common/test/resources/playwright-TC-reporter.ts +++ b/common/test/resources/playwright-TC-reporter.ts @@ -16,7 +16,6 @@ class TestNode { private flowId: number; private parent: TestNode | null; private childrenToVisit: TestNode[] = []; - private childrenCleared: boolean = false; public constructor(suiteOrTest: Suite | TestCase) { this.suiteOrTest = suiteOrTest; @@ -88,39 +87,20 @@ class TestNode { } } - private end(result: TestResult, force: boolean = false): void { - if (force) { - // use a copy of the array because child.end will mutate the array - for (const child of [...this.childrenToVisit]) { - child.end(result, true); + private end(result: TestResult): void { + if (this.suiteOrTest.title !== '') { + if (this.isTest) { + const { msgTitle, details } = this.getTestResult(result) ?? { msgTitle: 'testFinished', details: '' }; + console.log(`##teamcity[${msgTitle} name='${this.suiteOrTest.title}' ${details}]`); + } else { + console.log(`##teamcity[testSuiteFinished name='${this.suiteOrTest.title}']`); } - this.childrenToVisit = []; - this.childrenCleared = true; + console.log(`##teamcity[flowFinished flowId='${this.flowId}']`); } - // Only run this block on a normal (non-forced) end. Forced ends happen during endAll() - // teardown for nodes that didn't complete normally, where we have no meaningful result to - // report and the node's start/finish pairing may be incomplete. In that case we also skip the - // cleanup below because endAll()'s safety-net loop drains OpenNodes / Nodes globally, and our - // parent is already iterating its own children and clearing childrenToVisit itself — - // re-running removeFromOpenNodes / Nodes.delete / removeChild here would double-remove and - // log spurious errors, or trigger a re-entrant parent.end(null, false) cascade in the middle - // of the ongoing iteration. - if (!force) { - if (this.suiteOrTest.title !== '') { - if (this.isTest) { - const { msgTitle, details } = this.getTestResult(result) ?? { msgTitle: 'testFinished', details: '' }; - console.log(`##teamcity[${msgTitle} name='${this.suiteOrTest.title}' ${details}]`); - } else { - console.log(`##teamcity[testSuiteFinished name='${this.suiteOrTest.title}']`); - } - console.log(`##teamcity[flowFinished flowId='${this.flowId}']`); - } - - this.removeFromOpenNodes(); - this.parent?.removeChild(this); - TestNode.Nodes.delete(this.id); - } + this.removeFromOpenNodes(); + this.parent?.removeChild(this); + TestNode.Nodes.delete(this.id); } private removeFromOpenNodes(): void { @@ -133,10 +113,6 @@ class TestNode { } private removeChild(child: TestNode) { - if (this.childrenCleared) { - // already cleared all children in a end(result, true) call, nothing to do - return; - } const childIndex = this.childrenToVisit.indexOf(child); if (childIndex < 0) { console.error(`Can't find child '${child.id}' in parent '${this.id}'`); @@ -149,7 +125,7 @@ class TestNode { } // No more children, so close this node - this.end(null, false); + this.end(null); } public static startTest(test: TestCase): void { @@ -174,7 +150,6 @@ class TestNode { if (this.childrenToVisit.length > 0) { console.error(`Root node still has ${this.childrenToVisit.length} open children`); } - this.end(null, true); if (TestNode.OpenNodes.length > 0) { console.error(`Still have ${TestNode.OpenNodes.length} open nodes`); while (TestNode.OpenNodes.length > 0) { From 81bae574b9187343e2a59cfd7bc9d45074ab5abe Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 12 May 2026 18:14:46 +0200 Subject: [PATCH 20/72] maint(web): output name of TC reporter This change outputs the name of the TC reporter used for a test. This will help in troubleshooting. Build-bot: skip build:web Test-bot: skip --- common/test/resources/mocha-teamcity-reporter/teamcity.cjs | 2 ++ common/test/resources/playwright-TC-reporter.ts | 1 + common/test/resources/test-runner-TC-reporter.mjs | 6 ++++-- web/build.sh | 3 +++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/common/test/resources/mocha-teamcity-reporter/teamcity.cjs b/common/test/resources/mocha-teamcity-reporter/teamcity.cjs index 24c76a5f2b8..fd901faaa1b 100644 --- a/common/test/resources/mocha-teamcity-reporter/teamcity.cjs +++ b/common/test/resources/mocha-teamcity-reporter/teamcity.cjs @@ -135,6 +135,8 @@ function Teamcity(runner, options) { const ignoredTests = {}; const testState = { pending: 0 }; + log('Initializing Mocha TeamCity Reporter'); + runner.on(EVENT_SUITE_BEGIN, function (suite) { handleFlow(true, hasParentFlowId); if (suite.root) { diff --git a/common/test/resources/playwright-TC-reporter.ts b/common/test/resources/playwright-TC-reporter.ts index ad082dedf48..20d6ac39d49 100644 --- a/common/test/resources/playwright-TC-reporter.ts +++ b/common/test/resources/playwright-TC-reporter.ts @@ -178,6 +178,7 @@ export default class PlaywrightTeamcityReporter implements Reporter { private root!: TestNode; public constructor(options: { parentFlow?: string } = {}) { + console.log('Initializing Playwright TeamCity Reporter'); TestNode.RootFlow = options.parentFlow ?? 'unit_tests'; } diff --git a/common/test/resources/test-runner-TC-reporter.mjs b/common/test/resources/test-runner-TC-reporter.mjs index 8a6aeaccf21..33198592afa 100644 --- a/common/test/resources/test-runner-TC-reporter.mjs +++ b/common/test/resources/test-runner-TC-reporter.mjs @@ -94,14 +94,16 @@ export default function teamcityReporter({ name="Web Test Runner JavaScript test /** @type {import('@web/test-runner').Reporter} */ const reporter = { - start({config, sessions}) { + start({ config, sessions }) { + logger = config.logger; + logger.log('Initializing Web Test Runner TeamCity Reporter'); + rootDir = config.rootDir; for(const session of sessions.all()) { testDefMap.set(buildSessionName(session), new Map()); } - logger = config.logger; logger.log(`##teamcity[blockOpened name='${e(name)}']`); }, stop(args) { diff --git a/web/build.sh b/web/build.sh index d047b7dece0..ca253ab82c7 100755 --- a/web/build.sh +++ b/web/build.sh @@ -205,12 +205,15 @@ function do_browser_tests() { pushd "${KEYMAN_ROOT}" if is_test_included dom; then + builder_echo "Running browser-based tests..." web-test-runner --config "web/src/test/auto/dom/web-test-runner${WTR_CONFIG}.config.mjs" ${WTR_INSPECT} fi if is_test_included integrated; then + builder_echo "Running integration tests..." web-test-runner --config "web/src/test/auto/integrated/web-test-runner${WTR_CONFIG}.config.mjs" ${WTR_INSPECT} fi if is_test_included e2e; then + builder_echo "Running end-to-end tests..." npx playwright test --config "web/src/test/auto/e2e/playwright.config.ts" fi popd From 6af638b9a8b94b5d9af89795fe2b027d90ec14d0 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 13 May 2026 11:33:49 +0200 Subject: [PATCH 21/72] maint(web): fix starting local dev server for e2e tests Starting the local dev server before running the e2e tests with `web/build.sh start` didn't work on Windows. This change now directly calls `node` to start the server which seems to work cross-platform. Fixes: #15895 Build-bot: skip build:web Test-bot: skip --- web/src/test/auto/e2e/playwright.config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/src/test/auto/e2e/playwright.config.ts b/web/src/test/auto/e2e/playwright.config.ts index ad20aa9c157..ff888b54344 100644 --- a/web/src/test/auto/e2e/playwright.config.ts +++ b/web/src/test/auto/e2e/playwright.config.ts @@ -76,7 +76,10 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { - command: `"${KEYMAN_ROOT}/node_modules/.bin/gosh" "../../../../build.sh start"`, + // Normally we'd run `web/build.sh start` or `gosh web/build.sh start` + // here, but that's causing problems on Windows. + command: 'node web/src/tools/testing/test-server/index.cjs', + cwd: KEYMAN_ROOT, url: 'http://localhost:3000', reuseExistingServer: !process.env.KEYMAN_IS_CI_BUILD, }, From 0d266cf873aa9855028da8e98396a0e59c21af5b Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 20 Apr 2026 14:04:03 +0200 Subject: [PATCH 22/72] chore(web): fix red squiggles in .ts test files in vscode VSCode shows red squiggles under things like `describe` or `it` in .ts test files for web. This change adds the types to the `tsconfig.json` file which fixes this problem. Build-bot: skip build:web Test-bot: skip --- web/src/test/auto/tsconfig.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/src/test/auto/tsconfig.json b/web/src/test/auto/tsconfig.json index ef780359c2e..3c4f46fb4c3 100644 --- a/web/src/test/auto/tsconfig.json +++ b/web/src/test/auto/tsconfig.json @@ -5,6 +5,11 @@ "outDir": "../../../build/test", "tsBuildInfoFile": "../../../build/test/tsconfig.tsbuildinfo", "rootDir": ".", + "types": [ + "mocha", + "chai", + "node" + ], }, "include": [ From bc8a4b6f43f9fd2550032cae30571df1213fc982 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 17 Apr 2026 19:11:18 +0200 Subject: [PATCH 23/72] refactor(web): cleanup return types in KeymanEngine This change explicitly specifies the return types and access for the functions in KeymanEngine. Also introduce `KeyboardDetails` instead of `ReturnType<>`. Test-bot: skip --- web/src/app/browser/src/keyboardDetails.ts | 66 +++++++++++++++++++ web/src/app/browser/src/keymanEngine.ts | 75 +++++++++++----------- 2 files changed, 102 insertions(+), 39 deletions(-) create mode 100644 web/src/app/browser/src/keyboardDetails.ts diff --git a/web/src/app/browser/src/keyboardDetails.ts b/web/src/app/browser/src/keyboardDetails.ts new file mode 100644 index 00000000000..9ced50ee466 --- /dev/null +++ b/web/src/app/browser/src/keyboardDetails.ts @@ -0,0 +1,66 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ + +import { InternalKeyboardFont } from 'keyman/engine/keyboard'; + +/** + * Defines keyboard metadata + */ +export interface KeyboardDetails { + /** + * true if the keyboard has been loaded, false otherwise + */ + HasLoaded: boolean, + /** + * User-friendly name of the keyboard. + */ + Name: string, + /** + * Internal name of the keyboard. + */ + InternalName: string, + /** + * User-friendly name of the language actively tied to the keyboard. + */ + LanguageName: string, + /** + * The three-letter code used to internally represent the language. + */ + LanguageCode: string, + /** + * The user-friendly name of the region of the world within which + * the language is predominantly found. + */ + RegionName: string, + /** + * The three-letter code representing the region. + */ + RegionCode: string, + /** + * The user-friendly name of the country in which the language is spoken. (Optional) + */ + CountryName: string | null, + /** + * A three-letter code corresponding to the country. (Optional) + */ + CountryCode: string| null, + /** + * Deprecated. A unique identifier for the keyboard. (Use 'InternalName' instead.) + */ + KeyboardID: string | null, + /** + * The font packaged with the keyboard to support its use. (Optional) + */ + Font: InternalKeyboardFont | null, + /** + * The font packaged with the keyboard to properly display specialized + * OSK characters. (Optional) + */ + OskFont: InternalKeyboardFont | null, + /** + * Indicates whether the keyboard is designed for right-to-left scripts, + * null if the keyboard hasn't been loaded yet (and thus the value is unknown). + */ + IsRTL: boolean | null +}; diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index b9663ca3747..0d81bbdeaa3 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -29,6 +29,7 @@ import { HotkeyManager } from './hotkeyManager.js'; import { BeepHandler } from './beepHandler.js'; import { KeyboardInterface } from './keyboardInterface.js'; import { WorkerFactory } from '@keymanapp/lexical-model-layer/web'; +import { KeyboardDetails } from './keyboardDetails.js'; export class KeymanEngine extends KeymanEngineBase { touchLanguageMenu?: LanguageMenu; @@ -43,8 +44,8 @@ export class KeymanEngine extends KeymanEngineBase number = null; - getOskWidth?: () => number = null; + public getOskHeight?: () => number = null; + public getOskWidth?: () => number = null; /** * Provides a quick link to the base help page for Keyman keyboards. @@ -53,7 +54,7 @@ export class KeymanEngine extends KeymanEngineBase { + public keyEventRefocus = () => { this.contextManager.restoreLastActiveTextStore(); } @@ -87,7 +88,7 @@ export class KeymanEngine extends KeymanEngineBase) { + public async init(options: Required): Promise { const deviceDetector = new DeviceDetector(); const device = deviceDetector.detect(); @@ -269,7 +270,7 @@ export class KeymanEngine extends KeymanEngineBase (and document!) - private _GetKeyboardDetail = function(Lstub: KeyboardStub, Lkbd: JSKeyboard) { // I2078 - Full keyboard detail - const Lr = { + private _GetKeyboardDetail(Lstub: KeyboardStub, Lkbd: Keyboard): KeyboardDetails { // I2078 - Full keyboard detail + return { Name: Lstub.KN, InternalName: Lstub.KI, LanguageName: Lstub.KL, // I1300 - Add support for language names @@ -419,8 +418,6 @@ export class KeymanEngine extends KeymanEngineBase /* [b/c Toolbar UI]*/) { + public isCJK(k0?: KeyboardObject | KeyboardDetails /* [b/c Toolbar UI]*/) { let kbd: Keyboard; if(k0) { - const kbdDetail = k0 as ReturnType; + const kbdDetail = k0 as KeyboardDetails; if(kbdDetail.KeyboardID){ kbd = this.keyboardRequisitioner.cache.getKeyboard(kbdDetail.KeyboardID); } else { @@ -460,7 +457,7 @@ export class KeymanEngine extends KeymanEngineBase[] { - const Lr: ReturnType[] = []; + public getKeyboards(): KeyboardDetails[] { + const Lr: KeyboardDetails[] = []; const cache = this.keyboardRequisitioner.cache; const keyboardStubs = cache.getStubList() @@ -511,7 +508,7 @@ export class KeymanEngine extends KeymanEngineBase void) { + public addHotKey(keyCode: number, shiftState: number, handler: () => void): void { this.hotkeyManager.addHotKey(keyCode, shiftState, handler); } @@ -619,7 +616,7 @@ export class KeymanEngine extends KeymanEngineBase Date: Tue, 21 Apr 2026 12:11:11 +0200 Subject: [PATCH 24/72] chore(web): address code review comments --- web/src/app/browser/src/keyboardDetails.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/app/browser/src/keyboardDetails.ts b/web/src/app/browser/src/keyboardDetails.ts index 9ced50ee466..a410800fbd8 100644 --- a/web/src/app/browser/src/keyboardDetails.ts +++ b/web/src/app/browser/src/keyboardDetails.ts @@ -40,24 +40,24 @@ export interface KeyboardDetails { /** * The user-friendly name of the country in which the language is spoken. (Optional) */ - CountryName: string | null, + CountryName?: string, /** * A three-letter code corresponding to the country. (Optional) */ - CountryCode: string| null, + CountryCode?: string, /** * Deprecated. A unique identifier for the keyboard. (Use 'InternalName' instead.) */ - KeyboardID: string | null, + KeyboardID?: string, /** * The font packaged with the keyboard to support its use. (Optional) */ - Font: InternalKeyboardFont | null, + Font?: InternalKeyboardFont, /** * The font packaged with the keyboard to properly display specialized * OSK characters. (Optional) */ - OskFont: InternalKeyboardFont | null, + OskFont?: InternalKeyboardFont, /** * Indicates whether the keyboard is designed for right-to-left scripts, * null if the keyboard hasn't been loaded yet (and thus the value is unknown). From 689f664ea0f4d3584b2bdcedfb2cf49b085891fe Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 24 Apr 2026 15:48:30 +0200 Subject: [PATCH 25/72] =?UTF-8?q?refactor(web):=20rename=20variables=20and?= =?UTF-8?q?=20parameter=20names=20in=20KeymanEngine=20=F0=9F=A7=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build-bot: skip build:web Test-bot: skip --- web/src/app/browser/src/keymanEngine.ts | 41 ++++++++++--------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 0d81bbdeaa3..70033b6dc3b 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -6,7 +6,7 @@ import { TwoStateActivator, VisualKeyboard } from 'keyman/engine/osk'; -import { ErrorStub, KeyboardStub, CloudQueryResult, toPrefixedKeyboardId as prefixed } from 'keyman/engine/keyboard-storage'; +import { ErrorStub, KeyboardStub, CloudQueryResult, toPrefixedKeyboardId } from 'keyman/engine/keyboard-storage'; import { DeviceSpec } from 'keyman/common/web-utils'; import { JSKeyboard, Keyboard, KMXKeyboard } from "keyman/engine/keyboard"; import KeyboardObject = KeymanWebKeyboard.KeyboardObject; @@ -451,14 +451,14 @@ export class KeymanEngine extends KeymanEngineBase Date: Tue, 28 Apr 2026 16:52:22 +0200 Subject: [PATCH 26/72] refactor(web): address code review comment Co-authored-by: Marc Durdin --- web/src/app/browser/src/keymanEngine.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 70033b6dc3b..072742e02ce 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -481,8 +481,7 @@ export class KeymanEngine extends KeymanEngineBase Date: Mon, 27 Apr 2026 16:21:56 +0200 Subject: [PATCH 27/72] =?UTF-8?q?refactor(web):=20use=20`KeyboardDetails`?= =?UTF-8?q?=20type=20=F0=9F=A7=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #15856 introduced the `KeyboardDetails` type but overlooked the use in one file. Follow-up-of: #15856 Follows: #15887 Build-bot: skip build:web Test-bot: skip --- web/src/app/browser/src/test-index.ts | 1 + web/src/app/ui/kmwuitoolbar.ts | 18 ++++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web/src/app/browser/src/test-index.ts b/web/src/app/browser/src/test-index.ts index 6f010f494e5..4c67c5cac8c 100644 --- a/web/src/app/browser/src/test-index.ts +++ b/web/src/app/browser/src/test-index.ts @@ -1,6 +1,7 @@ export { BrowserConfiguration, BrowserInitOptionSpec } from './configuration.js'; export { ContextManager, KeyboardCookie } from "./contextManager.js"; export { preprocessKeyboardEvent, HardwareEventKeyboard } from './hardwareEventKeyboard.js'; +export { KeyboardDetails } from './keyboardDetails.js'; export { KeymanEngine } from './keymanEngine.js'; export { KeyboardInterface } from './keyboardInterface.js'; diff --git a/web/src/app/ui/kmwuitoolbar.ts b/web/src/app/ui/kmwuitoolbar.ts index 08bcd00a2c8..cf736777f91 100644 --- a/web/src/app/ui/kmwuitoolbar.ts +++ b/web/src/app/ui/kmwuitoolbar.ts @@ -3,7 +3,7 @@ Copyright 2019-2023 SIL International ***/ -import type { KeymanEngine, KeyboardCookie, UIModule } from 'keyman/app/browser'; +import type { KeymanEngine, KeyboardCookie, KeyboardDetails, UIModule } from 'keyman/app/browser'; declare global { interface Window { @@ -31,8 +31,6 @@ type ToolbarCookie = { maxrecent: number; } & Record<`recent${number}`, string>; -type KeyboardDetail = ReturnType; - type LanguageEntry = { /** Language id */ id: string; @@ -43,13 +41,13 @@ type LanguageEntry = { /** * A rich list of keyboard metadata for keyboards matching this language's language code (`id`). */ - keyboards: KeyboardDetail[]; + keyboards: KeyboardDetails[]; } interface ListedKeyboard { priority: number; lang: LanguageEntry, - keyboard: KeyboardDetail; + keyboard: KeyboardDetails; buttonNode: HTMLDivElement; aNode: HTMLAnchorElement; } @@ -128,7 +126,7 @@ if(!keymanweb) { */ maxListedKeyboards = 1; lastActiveControl: HTMLElement = null; - selectedKeyboard: KeyboardDetail = null; + selectedKeyboard: KeyboardDetails = null; selectedLanguage = ''; helpOffsetX = 0; helpOffsetY = 0; @@ -563,7 +561,7 @@ if(!keymanweb) { * @param {Object} b * @return {number} **/ - readonly sortKeyboards = function(a: KeyboardDetail, b: KeyboardDetail) { + readonly sortKeyboards = function(a: KeyboardDetails, b: KeyboardDetails) { if(a.RegionCode < b.RegionCode) { return -2; } @@ -611,7 +609,7 @@ if(!keymanweb) { * @param {Object} lang * @param {Object} kbd **/ - addKeyboardToList(lang: LanguageEntry, kbd: KeyboardDetail) { + addKeyboardToList(lang: LanguageEntry, kbd: KeyboardDetails) { const found = this.findListedKeyboard(lang); if(found == null) { // Add the button @@ -795,7 +793,7 @@ if(!keymanweb) { **/ selectLanguage(event: Event, lang: LanguageEntry) { const found = this.findListedKeyboard(lang); - let kbd: KeyboardDetail = null; + let kbd: KeyboardDetails = null; if(found == null) { kbd = lang.keyboards[0]; @@ -820,7 +818,7 @@ if(!keymanweb) { * @param {boolean} updateKeyman * @return {boolean} **/ - private async selectKeyboard(event: Event, lang: LanguageEntry, kbd: KeyboardDetail, updateKeyman: boolean): Promise { + private async selectKeyboard(event: Event, lang: LanguageEntry, kbd: KeyboardDetails, updateKeyman: boolean): Promise { keymanweb.activatingUI(true); if(this.selectedLanguage) { From 9423ee504f673e3102974b6989f165ec046c76ec Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 30 Apr 2026 17:46:55 +0200 Subject: [PATCH 28/72] chore(web): cleanup `web/src/app/browser/src/test-index.ts` exports The export of `test-index.ts` were intended to be used by tests only, but over time they contained also exports that were consumed else where, e.g. in UI classes like `kmwuibutton.ts`. This change removes unused exports, moves test-only classes to a `unitTestEndPoints` container and marks test-only types with `@internal`. Build-bot: skip build:web Test-bot: skip --- web/package.json | 6 ++--- web/src/app/browser/build.sh | 2 +- web/src/app/browser/src/index.ts | 22 +++++++++++++++++++ web/src/app/browser/src/test-index.ts | 8 ------- .../hardware-event-processing.tests.ts | 3 ++- 5 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 web/src/app/browser/src/index.ts delete mode 100644 web/src/app/browser/src/test-index.ts diff --git a/web/package.json b/web/package.json index 712a8558a2e..47ebacab73f 100644 --- a/web/package.json +++ b/web/package.json @@ -3,9 +3,9 @@ "description": "Facilitates text input in any language.", "exports": { "./app/browser": { - "es6-bundling": "./src/app/browser/src/test-index.ts", - "types": "./build/app/browser/obj/test-index.d.js", - "import": "./build/app/browser/obj/test-index.js" + "es6-bundling": "./src/app/browser/src/index.ts", + "types": "./build/app/browser/obj/index.d.js", + "import": "./build/app/browser/obj/index.js" }, "./common/web-utils": { "es6-bundling": "./src/common/web-utils/src/index.ts", diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index d23b67987af..5396b9baa12 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -63,7 +63,7 @@ compile_and_copy() { --minify \ --target "es6" - node_es_bundle "${BUILD_ROOT}/obj/test-index.js" \ + node_es_bundle "${BUILD_ROOT}/obj/index.js" \ --out "${BUILD_ROOT}/lib/index.mjs" \ --charset "utf8" \ --sourceRoot "@keymanapp/keyman/web/build/app/browser/lib" \ diff --git a/web/src/app/browser/src/index.ts b/web/src/app/browser/src/index.ts new file mode 100644 index 00000000000..786f004de5a --- /dev/null +++ b/web/src/app/browser/src/index.ts @@ -0,0 +1,22 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ + +export { ContextManager, KeyboardCookie } from "./contextManager.js"; +export { KeyboardDetails } from './keyboardDetails.js'; +export { KeymanEngine } from './keymanEngine.js'; +export { UIModule } from './uiModuleInterface.js'; + +//---------------------------------------------------------------------- +// Exports for unit testing only + +/** @internal */ +export { type BrowserInitOptionSpec } from './configuration.js'; +/** @internal */ +export { type KeyboardInterface } from './keyboardInterface.js'; + +import { preprocessKeyboardEvent } from './hardwareEventKeyboard.js'; +/** @internal */ +export const unitTestEndPoints = { + preprocessKeyboardEvent +} diff --git a/web/src/app/browser/src/test-index.ts b/web/src/app/browser/src/test-index.ts deleted file mode 100644 index 4c67c5cac8c..00000000000 --- a/web/src/app/browser/src/test-index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { BrowserConfiguration, BrowserInitOptionSpec } from './configuration.js'; -export { ContextManager, KeyboardCookie } from "./contextManager.js"; -export { preprocessKeyboardEvent, HardwareEventKeyboard } from './hardwareEventKeyboard.js'; -export { KeyboardDetails } from './keyboardDetails.js'; - -export { KeymanEngine } from './keymanEngine.js'; -export { KeyboardInterface } from './keyboardInterface.js'; -export { UIModule } from './uiModuleInterface.js'; diff --git a/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts b/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts index 0da4588a085..6593b4e1155 100644 --- a/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts +++ b/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { preprocessKeyboardEvent } from 'keyman/app/browser'; +import { unitTestEndPoints } from 'keyman/app/browser'; import { processForMnemonicsAndLegacy } from 'keyman/engine/main'; import { PhysicalInputEventSpec } from '@keymanapp/recorder-core'; import { DeviceSpec } from 'keyman/common/web-utils'; @@ -8,6 +8,7 @@ import { Codes, JSKeyboard, KeyEvent } from 'keyman/engine/keyboard'; const ModifierCodes = Codes.modifierCodes; const KeyCodes = Codes.keyCodes; +const preprocessKeyboardEvent = unitTestEndPoints.preprocessKeyboardEvent; const DUMMY_DEVICE = new DeviceSpec('chrome', 'desktop', 'windows', false); From 79fdadc87fdc6cfee7199ae0c8bdac2e5a29a806 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 30 Apr 2026 17:58:19 +0200 Subject: [PATCH 29/72] chore(web): use .d.ts instead of .d.js for types The convention is to use the `.d.ts` extension for typescript types, and that's what is used elsewhere in the `package.json` file. This change fixes the two instances that had `.d.js`. --- web/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/package.json b/web/package.json index 47ebacab73f..0ce1a7d9935 100644 --- a/web/package.json +++ b/web/package.json @@ -4,12 +4,12 @@ "exports": { "./app/browser": { "es6-bundling": "./src/app/browser/src/index.ts", - "types": "./build/app/browser/obj/index.d.js", + "types": "./build/app/browser/obj/index.d.ts", "import": "./build/app/browser/obj/index.js" }, "./common/web-utils": { "es6-bundling": "./src/common/web-utils/src/index.ts", - "types": "./build/common/web-utils/obj/index.d.js", + "types": "./build/common/web-utils/obj/index.d.ts", "import": "./build/common/web-utils/obj/index.js" }, "./engine/attachment": { From fc82f28b2d9bd6f734714fa0c1f752c6981d668c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 1 Jun 2026 14:44:45 +0200 Subject: [PATCH 30/72] chore(web): web-core preflight - strip core references In order to maintain roughly the current build artifact size, strip out the unfinished core integration from what we merge with preflight. Test-bot: skip Build-bot: release:android,ios,developer,web --- common/web/build.sh | 1 + web/package.json | 10 - web/src/app/browser/build.sh | 4 - web/src/app/browser/src/keymanEngine.ts | 6 +- web/src/app/ui/build.sh | 4 - web/src/app/webview/build.sh | 4 - web/src/engine/build.sh | 10 - web/src/engine/src/core-adapter/.gitignore | 1 - web/src/engine/src/core-adapter/KM_Core.ts | 76 --- .../core-adapter/import/core/keymancore.d.ts | 157 +++++ web/src/engine/src/core-adapter/index.ts | 3 - .../core-processor/coreKeyboardInterface.ts | 47 -- .../core-processor/coreKeyboardProcessor.ts | 496 -------------- web/src/engine/src/core-processor/index.ts | 1 - .../keyboard-storage/stubAndKeyboardCache.ts | 13 +- web/src/engine/src/keyboard/index.ts | 1 - .../keyboard/keyboards/keyboardLoaderBase.ts | 15 +- .../src/keyboard/keyboards/kmxKeyboard.ts | 179 ----- .../src/main/headless/inputProcessor.ts | 13 +- web/src/engine/tsconfig.json | 1 + .../engine/core-adapter/core-adapter.tests.ts | 46 -- .../coreKeyboardProcessor.tests.ts | 610 ------------------ .../engine/keyboard/kmxkeyboard.tests.ts | 23 - .../headless/engine/loadKeyboardHelper.ts | 3 - 24 files changed, 169 insertions(+), 1555 deletions(-) delete mode 100644 web/src/engine/src/core-adapter/.gitignore delete mode 100644 web/src/engine/src/core-adapter/KM_Core.ts create mode 100644 web/src/engine/src/core-adapter/import/core/keymancore.d.ts delete mode 100644 web/src/engine/src/core-adapter/index.ts delete mode 100644 web/src/engine/src/core-processor/coreKeyboardInterface.ts delete mode 100644 web/src/engine/src/core-processor/coreKeyboardProcessor.ts delete mode 100644 web/src/engine/src/core-processor/index.ts delete mode 100644 web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts delete mode 100644 web/src/test/auto/headless/engine/core-adapter/core-adapter.tests.ts delete mode 100644 web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts delete mode 100644 web/src/test/auto/headless/engine/keyboard/kmxkeyboard.tests.ts diff --git a/common/web/build.sh b/common/web/build.sh index 182de85d624..052061946fb 100755 --- a/common/web/build.sh +++ b/common/web/build.sh @@ -11,6 +11,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe "Keyman common web modules" \ :keyman-version \ :langtags \ + :sentry-manager \ :types \ clean \ configure \ diff --git a/web/package.json b/web/package.json index 712a8558a2e..e7bd81e1fab 100644 --- a/web/package.json +++ b/web/package.json @@ -17,16 +17,6 @@ "types": "./build/engine/obj/attachment/index.d.ts", "import": "./build/engine/obj/attachment/index.js" }, - "./engine/core-adapter": { - "es6-bundling": "./src/engine/src/core-adapter/index.ts", - "types": "./build/engine/obj/core-adapter/index.d.ts", - "import": "./build/engine/obj/core-adapter/index.js" - }, - "./engine/core-processor": { - "es6-bundling": "./src/engine/src/core-processor/index.ts", - "types": "./build/engine/obj/core-processor/index.d.ts", - "import": "./build/engine/obj/core-processor/index.js" - }, "./engine/dom-utils": { "es6-bundling": "./src/engine/src/dom-utils/index.ts", "types": "./build/engine/obj/dom-utils/index.d.ts", diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index d23b67987af..760d0fea7ec 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -72,10 +72,6 @@ compile_and_copy() { mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk" cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/" - # Copy Keyman Core build artifacts for local reference - cp "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/browser/debug/" - cp "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/browser/release/" - # Update the build/publish copy of our build artifacts prepare diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index b9663ca3747..9120c7c07f9 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -8,7 +8,7 @@ import { } from 'keyman/engine/osk'; import { ErrorStub, KeyboardStub, CloudQueryResult, toPrefixedKeyboardId as prefixed } from 'keyman/engine/keyboard-storage'; import { DeviceSpec } from 'keyman/common/web-utils'; -import { JSKeyboard, Keyboard, KMXKeyboard } from "keyman/engine/keyboard"; +import { JSKeyboard, Keyboard } from "keyman/engine/keyboard"; import KeyboardObject = KeymanWebKeyboard.KeyboardObject; import * as views from './viewsAnchorpoint.js'; @@ -691,10 +691,6 @@ export class KeymanEngine extends KeymanEngineBase { - const coreModuleName = this.isNode() ? 'km-core-node.mjs' : 'km-core.js'; - const module = await import(`${baseurl}/${coreModuleName}`); - const createCoreProcessor = module.default; - const km_core = createCoreProcessor({ - locateFile: function (path: string, scriptDirectory: string) { - return baseurl + '/' + path; - } - }); - km_core.then((core: KMXCoreModule) => { - this.km_core = core; - }); - return km_core; - } -} \ No newline at end of file diff --git a/web/src/engine/src/core-adapter/import/core/keymancore.d.ts b/web/src/engine/src/core-adapter/import/core/keymancore.d.ts new file mode 100644 index 00000000000..4955b403070 --- /dev/null +++ b/web/src/engine/src/core-adapter/import/core/keymancore.d.ts @@ -0,0 +1,157 @@ +// TypeScript bindings for emscripten-generated code. Automatically generated at compile time. +declare namespace RuntimeExports { + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ + function UTF8ToString(ptr: number, maxBytesToRead?: number): string; + function stringToNewUTF8(str: any): any; + let wasmExports: any; + let HEAPF32: any; + let HEAPF64: any; + let HEAP_DATA_VIEW: any; + let HEAP8: any; + let HEAPU8: any; + let HEAP16: any; + let HEAPU16: any; + let HEAP32: any; + let HEAPU32: any; + let HEAP64: any; + let HEAPU64: any; +} +interface WasmModule { +} + +type EmbindString = ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string; +export interface km_core_keyboard { + delete(): void; +} + +export interface CoreKeyboardReturn { + readonly object: km_core_keyboard; + readonly status: number; + delete(): void; +} + +export interface CoreKeyboardAttrsReturn { + readonly object: km_core_keyboard_attrs; + readonly status: number; + delete(): void; +} + +export interface CoreContextReturn { + readonly object: km_core_context_items; + readonly status: number; + delete(): void; +} + +export interface km_core_keyboard_attrs { + version_string: string; + id: string; + default_options: any; + delete(): void; +} + +export interface km_core_actions { + do_alert: boolean; + emit_keystroke: boolean; + new_caps_lock_state: number; + code_points_to_delete: number; + output: string; + deleted_context: string; + persist_options: any; + toString(): string; + delete(): void; +} + +export interface km_core_state { + delete(): void; +} + +export interface CoreStateReturn { + readonly object: km_core_state; + readonly status: number; + delete(): void; +} + +export interface km_core_context { + delete(): void; +} + +export interface km_core_context_items { + data(): km_core_context_item; + push_back(item: km_core_context_item): void; + resize(size: number): void; + size(): number; + get(index: number): km_core_context_item; + set(index: number, item: km_core_context_item): void; + toString(): string; + delete(): void; +} + +export interface km_core_context_item { + readonly type: number; + character: number; + marker: number; + toString(): string; + delete(): void; +} + +export type km_core_attr = { + max_context: number, + current: number, + revision: number, + age: number, + technology: number +}; + +export type km_core_option_item = { + key: string, + value: string, + scope: number +}; + +interface EmbindModule { + km_core_keyboard: {}; + CoreKeyboardReturn: {}; + CoreKeyboardAttrsReturn: {}; + CoreContextReturn: {}; + km_core_keyboard_attrs: {}; + km_core_actions: {}; + km_core_state: {}; + CoreStateReturn: {}; + km_core_context: {}; + km_core_context_items: {new(): km_core_context_items}; + km_core_context_item: {new(): km_core_context_item}; + create_end_context(): km_core_context_item; + keyboard_get_attrs(keyboard: km_core_keyboard): CoreKeyboardAttrsReturn; + state_clone(state: km_core_state): CoreStateReturn; + state_context(state: km_core_state): km_core_context; + state_get_actions(state: km_core_state): km_core_actions; + context_get(context: km_core_context): CoreContextReturn; + keyboard_dispose(keyboard: km_core_keyboard): void; + state_dispose(state: km_core_state): void; + process_event(state: km_core_state, vk: number, modifier_state: number, is_key_down: boolean, event_flags: number): number; + state_context_clear(state: km_core_state): number; + context_set(context: km_core_context, context_items: km_core_context_items): number; + tmp_wasm_attributes(): km_core_attr; + state_context_set_if_needed(state: km_core_state, application_context: string): number; + state_context_debug(state: km_core_state, context_type: number): string; + keyboard_load_from_blob(kb_name: EmbindString, blob: any): CoreKeyboardReturn; + state_create(keyboard: km_core_keyboard, env: any): CoreStateReturn; + state_options_update(state: km_core_state, new_options: any): number; +} + +export type MainModule = WasmModule & typeof RuntimeExports & EmbindModule; +export default function MainModuleFactory (options?: unknown): Promise; diff --git a/web/src/engine/src/core-adapter/index.ts b/web/src/engine/src/core-adapter/index.ts deleted file mode 100644 index f5a4ff2c785..00000000000 --- a/web/src/engine/src/core-adapter/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { KM_Core, KM_CORE_STATUS, KM_CORE_OPTION_SCOPE, KM_CORE_KMX_ENV, KM_CORE_CT } from './KM_Core.js'; -import { type km_core_keyboard, type km_core_state, type km_core_context, type km_core_context_item, type km_core_context_items, type km_core_option_item } from './import/core/keymancore.js'; -export { km_core_keyboard, km_core_state, km_core_context, km_core_context_item, km_core_context_items, km_core_option_item }; diff --git a/web/src/engine/src/core-processor/coreKeyboardInterface.ts b/web/src/engine/src/core-processor/coreKeyboardInterface.ts deleted file mode 100644 index fbee9956e55..00000000000 --- a/web/src/engine/src/core-processor/coreKeyboardInterface.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ -import { KM_Core, km_core_option_item } from 'keyman/engine/core-adapter'; -import { KeyboardMinimalInterface, Keyboard, VariableStoreSerializer, KMXKeyboard } from 'keyman/engine/keyboard'; - -export class CoreKeyboardInterface implements KeyboardMinimalInterface { - private _activeKeyboard: Keyboard; - - public constructor(public variableStoreSerializer: VariableStoreSerializer) { - } - - public get activeKeyboard(): Keyboard { - return this._activeKeyboard; - } - public set activeKeyboard(keyboard: Keyboard) { - this._activeKeyboard = keyboard; - const options = this.loadSerializedOptions(keyboard); - - if (options.length > 0) { - const kmxKeyboard = this._activeKeyboard as KMXKeyboard; - KM_Core.instance.state_options_update(kmxKeyboard.state, options); - } - } - - - private loadSerializedOptions(keyboard: Keyboard): km_core_option_item[] { - // TODO-WEB-CORE: use km_core_keyboard_get_attrs to get list of all variable - // store names and then iterate through those rather than reading from - // cookie props - const options: km_core_option_item[] = []; - /*const stores = this.variableStoreSerializer.findStores(toPrefixedKeyboardId(keyboard.id)); - for (const store of stores) { - for (const key in store) { - if (store.hasOwnProperty(key)) { - const item: km_core_option_item = { - key: key, - value: store[key], - scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD - }; - options.push(item); - } - } - }*/ - return options; - } -} diff --git a/web/src/engine/src/core-processor/coreKeyboardProcessor.ts b/web/src/engine/src/core-processor/coreKeyboardProcessor.ts deleted file mode 100644 index 429a83f5ed3..00000000000 --- a/web/src/engine/src/core-processor/coreKeyboardProcessor.ts +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ - -import { EventEmitter } from 'eventemitter3'; -import { KM_Core, KM_CORE_STATUS, KM_CORE_CT, km_core_context, km_core_context_items, km_core_option_item } from 'keyman/engine/core-adapter'; -import { - BeepHandler, - EventMap, Keyboard, KeyboardMinimalInterface, KeyboardProcessor, - KeyEvent, KMXKeyboard, SyntheticTextStore, MutableSystemStore, TextStore, ProcessorAction, - StateKeyMap, - Deadkey, - Codes, - VariableStoreSerializer -} from "keyman/engine/keyboard"; -import { KM_CORE_EVENT_FLAG, KM_CORE_OPTION_SCOPE } from '../core-adapter/KM_Core.js'; -import { ModifierKeyConstants } from '@keymanapp/common-types'; -import { DeviceSpec } from 'keyman/common/web-utils'; -import { CoreKeyboardInterface } from './coreKeyboardInterface.js'; -import { toPrefixedKeyboardId } from 'keyman/engine/keyboard-storage'; - -const lockNames = ['CAPS', 'NUM_LOCK', 'SCROLL_LOCK'] as const; -const lockKeys = ['K_CAPS', 'K_NUMLOCK', 'K_SCROLL'] as const; -const lockModifiers = [ModifierKeyConstants.CAPITALFLAG, ModifierKeyConstants.NUMLOCKFLAG, ModifierKeyConstants.SCROLLFLAG] as const; -const noLockModifers = [ModifierKeyConstants.NOTCAPITALFLAG, ModifierKeyConstants.NOTNUMLOCKFLAG, ModifierKeyConstants.NOTSCROLLFLAG] as const; - -/** - * Implements the core keyboard processing engine that interacts with the - * shared Keyman Core component which handles .kmx keyboards. - */ -export class CoreKeyboardProcessor extends EventEmitter implements KeyboardProcessor { - private _newLayerStore: MutableSystemStore = new MutableSystemStore(0, 'default'); - private _oldLayerStore: MutableSystemStore = new MutableSystemStore(0, 'default'); - private _layerStore: MutableSystemStore = new MutableSystemStore(0, 'default'); - private _keyboardInterface: CoreKeyboardInterface; - - /** - * Initialize the core processor with the provided base path. - * Sets up the necessary environment for processing keyboard events. - * - * @param {string} basePath The path for the core processor - * resources, i.e. where the - * km-core.js file is located. - * @param {VariableStoreSerializer} storeSerializer Optional serializer for variable stores. - * @returns {Promise} A promise that resolves when initialization is complete. - */ - public async init(basePath: string, storeSerializer: VariableStoreSerializer): Promise { - await KM_Core.createCoreProcessor(basePath); - this._keyboardInterface = new CoreKeyboardInterface(storeSerializer); - } - - /** - * Tracks the simulated value for supported state keys, allowing the OSK to - * mirror a physical keyboard for them. Uses the exact keyCode name from the - * Codes definitions to enable certain optimizations elsewhere in the code. - * - * @type {StateKeyMap} - */ - public stateKeys: StateKeyMap = { - "K_CAPS": false, - "K_NUMLOCK": false, - "K_SCROLL": false - } - - /** - * Indicates the device (platform) to be used for non-keystroke events. - * Used for events such as those sent to `begin postkeystroke` and - * `begin newcontext` entry points. - * - * @type {DeviceSpec} - */ - public contextDevice: DeviceSpec; - - /** - * Optional handler for beep events triggered by the processor. - * Allows custom handling of beep feedback, such as for alerts or errors. - * - * @type {BeepHandler} - */ - public beepHandler?: BeepHandler; - - /** - * Bitfield representing the most recent modifier state (Alt, Ctrl, Shift, etc.) - * as observed or simulated by the processor. Used to quickly detect changes in - * modifier state not otherwise captured by the hosting page (important for AltGr). - * - * @type {number} - */ - public modStateFlags: number = 0; - - /** - * Stores the identifier for the base keyboard layout in use. - * Used to determine the default layout for key mapping and processing. - * - * @type {string} - */ - public baseLayout: string; - - /** - * The currently active keyboard. - * - * @type {Keyboard} - */ - public get activeKeyboard(): Keyboard { - return this.keyboardInterface.activeKeyboard; - } - public set activeKeyboard(keyboard: Keyboard) { - this.keyboardInterface.activeKeyboard = keyboard; - } - - /** - * The keyboard interface used by the processor. - * Provides access to the keyboard interface implementation. - * - * @type {KeyboardMinimalInterface} - */ - get keyboardInterface(): KeyboardMinimalInterface { - return this._keyboardInterface; - } - - /** - * The store representing the currently active keyboard layer. - * - * @type {MutableSystemStore} - */ - public get layerStore(): MutableSystemStore { - // TODO-web-core: link to .kmx layer store (#15284) - return this._layerStore; - } - - /** - * A writable store used when transitioning to a new layer - * - * @type {MutableSystemStore} - */ - public get newLayerStore(): MutableSystemStore { - // TODO-web-core: link to .kmx new-layer store (#15284) - return this._newLayerStore; - } - - /** - * A store representing the previously active layer - * - * @type {MutableSystemStore} - */ - public get oldLayerStore(): MutableSystemStore { - // TODO-web-core: link to .kmx old-layer store (#15284) - return this._oldLayerStore; - } - - /** - * Identifier of the currently active layer. - * - * @type {string} - */ - public get layerId(): string { - return this._layerStore.value; - } - public set layerId(value: string) { - this._layerStore.set(value); - } - - private getLayerId(modifier: number): string { - // TODO-web-core: implement - // return Layouts.getLayerId(modifier); - return 'default'; // TODO-web-core: put into LayerNames enum - } - - /** - * Retrieve context including deadkeys from TextStore and apply to Core's context - * - * @param context Context from Keyman Core - * @param textStore Web TextStore - */ - private applyContextFromTextStore(context: km_core_context, textStore: TextStore): void { - // Unlike the desktop Engines, we still track markers (deadkeys) in Engine - // for Web at this time. This is for two reasons: - // 1. We still have the legacy JSKeyboard code paths which manage deadkey - // state - // 2. SyntheticTextStores which are used for rewinding and replaying key - // events in predictive text and multitap need to also replay deadkeys - // - // TODO: Once we make CoreKeyboardProcessor the primary keyboard processor - // and fully deprecate JSKeyboardProcessor, we should consider moving the - // ownership of context back into opaque Core objects within - // SyntheticTextStore, so ownership of context and marker state can be - // managed entirely within Core, KeymanWeb does not need to have knowledge - // of markers, and then we better align with the desktop Engines. - - const caretPosition = textStore.getCaret(); - const text = textStore.getText().substring(0, caretPosition); - const deadkeys = textStore.deadkeys().dks.sort((a, b) => a.p != b.p ? a.p - b.p : a.o - b.o); - const contextItems = new KM_Core.instance.km_core_context_items(); - - const deadkeyIterator = deadkeys.values(); - let deadkey = deadkeyIterator.next(); - let textIndex = 0; - while (!deadkey.done || textIndex < text.length) { - // flush out invalid deadkeys - while (!deadkey.done && (deadkey.value.p < textIndex || deadkey.value.p > text.length)) { - // this should never happen -- it would mean that a deadkey position was < 0, in the - // middle of a surrogate pair, or after the caret. - console.warn(`invalid deadkey '${deadkey.value.d}' position ${deadkey.value.p}`); - deadkey = deadkeyIterator.next(); - } - - // insert 0 or more deadkeys at current index - while (!deadkey.done && deadkey.value.p == textIndex) { - const contextItem = new KM_Core.instance.km_core_context_item(); - contextItem.marker = deadkey.value.d; - contextItems.push_back(contextItem); - deadkey = deadkeyIterator.next(); - } - - // insert next character - if (textIndex < text.length) { - const contextItem = new KM_Core.instance.km_core_context_item(); - contextItem.character = text.codePointAt(textIndex); - contextItems.push_back(contextItem); - textIndex++; - if (contextItem.character > 0xFFFF) { - // we have a surrogate pair, skip other half of surrogate, codePointAt() - // already handled that for us - textIndex++; - } - } - } - - // Add end element - contextItems.push_back(KM_Core.instance.create_end_context()); - - KM_Core.instance.context_set(context, contextItems); - } - - /** - * Saves marker entries from Core's context into a TextStore's deadkey list. - * - * @param context Context from Keyman Core - * @param textStore Web TextStore - */ - private saveMarkersToTextStore(context: km_core_context, textStore: TextStore): void { - const { status, object } = KM_Core.instance.context_get(context); - if (status != KM_CORE_STATUS.OK) { - console.error('KeymanWeb: km_core_context_get failed with status: ' + status); - return; - } - textStore.deadkeys().clear(); - let textIndex = 0; - const contextItems: km_core_context_items = object; - for (let i = 0; i < contextItems.size(); i++) { - const contextItem = contextItems.get(i); - if (contextItem.type !== KM_CORE_CT.MARKER) { - textIndex++; - if (contextItem.character > 0xFFFF) { - // character will be a surrogate pair in the text store - textIndex++; - } - continue; - } - textStore.deadkeys().add(new Deadkey(textIndex, contextItem.marker)); - } - } - - /** - * Processes a keystroke event and updates the text store accordingly. - * Handles the main logic for interpreting and applying keyboard input. - * - * @param {KeyEvent} keyEvent The key event to process. - * @param {TextStore} textStore The current text store context. - * - * @returns {ProcessorAction} The resulting processor action. - */ - public processKeystroke(keyEvent: KeyEvent, textStore: TextStore): ProcessorAction { - - const preInput = SyntheticTextStore.from(textStore, true); - const activeKeyboard = this.activeKeyboard as KMXKeyboard; - const coreContext = KM_Core.instance.state_context(activeKeyboard.state); - - this.applyContextFromTextStore(coreContext, textStore); - - const status = KM_Core.instance.process_event(activeKeyboard.state, keyEvent.Lcode, keyEvent.Lmodifiers, keyEvent.source?.type === 'keydown', KM_CORE_EVENT_FLAG.DEFAULT); - // TODO-web-core: properly set flags (#15283) - if (status != KM_CORE_STATUS.OK) { - console.error('KeymanWeb: km_core_process_event failed with status: ' + status); - return null; - } - const processorAction = new ProcessorAction(); - const core_actions = KM_Core.instance.state_get_actions(activeKeyboard.state); - - textStore.deleteCharsBeforeCaret(core_actions.code_points_to_delete); - textStore.insertTextBeforeCaret(core_actions.output); - this.saveMarkersToTextStore(coreContext, textStore); - - processorAction.beep = core_actions.do_alert; - if (this.beepHandler && processorAction.beep) { - this.beepHandler(textStore); - } - - processorAction.triggerKeyDefault = core_actions.emit_keystroke; - - this.process_persist_action(core_actions.persist_options); - - // TODO-web-core: do we have to do anything with the new_caps_lock_state? (#15285) - // process_capslock_action(actions->new_caps_lock_state); - - processorAction.transcription = textStore.buildTranscriptionFrom(preInput, keyEvent, false); - - return processorAction; - } - - private process_persist_action(options: km_core_option_item[]): void { - if (this.keyboardInterface.variableStoreSerializer) { - for (const option of options) { - if (option.scope !== KM_CORE_OPTION_SCOPE.OPT_KEYBOARD) { - console.error(`Unsupported option scope: ${option.scope}`); - continue; - } - this.keyboardInterface.variableStoreSerializer.saveStore(toPrefixedKeyboardId(this.activeKeyboard.id), option.key, option.value); - } - } - } - - /** - * Processes post-keystroke actions for the given device and text store. - * Handles any actions that should occur after a keystroke is processed - * or after applying suggestions. - * - * @param {DeviceSpec} device The device specification. - * @param {TextStore} textStore The current text store context. - * - * @returns {ProcessorAction} The resulting processor action, or null if not applicable. - */ - public processPostKeystroke(device: DeviceSpec, textStore: TextStore): ProcessorAction { - // TODO-embed-osk-in-kmx: Implement this method (#15286) - // This gets called after processing a keystroke to process the PostKeystroke group - // (irrelevant for web-core since that is handled in Core), but also after - // applying a suggestion, which we do need to handle. - return null; - } - - // TODO-web-core: this could be shared with JsKeyboardProcessor - /** - * Determines if the given key event is a modifier key press. - * Returns true if the event corresponds to a modifier key, otherwise false. - * - * @param {KeyEvent} keyEvent The key event to evaluate. - * @param {TextStore} textStore The current text store context. - * @param {boolean} isKeyDown Indicates if the key event is a key down event. - * - * @returns {boolean} True if the event is a modifier key press, false otherwise. - */ - public doModifierPress(keyEvent: KeyEvent, textStore: TextStore, isKeyDown: boolean): boolean { - if(!this.activeKeyboard) { - return false; - } - - if(keyEvent.isModifier) { - this.activeKeyboard.notify(keyEvent.Lcode, textStore, isKeyDown ? 1 : 0); - // For eventual integration - we bypass an OSK update for physical keystrokes when in touch mode. - if(!keyEvent.device.touchable) { - return this._UpdateVKShift(keyEvent); // I2187 - } else { - return true; - } - } - - if(keyEvent.LmodifierChange) { - this.activeKeyboard.notify(0, textStore, 1); - if(!keyEvent.device.touchable) { - this._UpdateVKShift(keyEvent); - } - } - - // No modifier keypresses detected. - return false; - } - - - // TODO-web-core: this could be shared with JsKeyboardProcessor - /** - * Updates the virtual keyboard shift state based on the provided key event. - * Handles modifier key simulation, state key updates, and layer selection for the OSK. - * - * @param {KeyEvent} e - The key event used to update the shift state. - * - * @returns {boolean} True if the update was processed, otherwise true if no active keyboard. - */ - private _UpdateVKShift(e: KeyEvent): boolean { - let keyShiftState=0; - - if(!this.activeKeyboard) { - return true; - } - - if(e) { - // read shift states from event - keyShiftState = e.Lmodifiers; - - // Are we simulating AltGr? If it's a simulation and not real, time to un-simulate for the OSK. - if(this.activeKeyboard.isChiral && this.activeKeyboard.emulatesAltGr && - (this.modStateFlags & Codes.modifierBitmasks['ALT_GR_SIM']) == Codes.modifierBitmasks['ALT_GR_SIM']) { - keyShiftState |= Codes.modifierBitmasks['ALT_GR_SIM']; - keyShiftState &= ~ModifierKeyConstants.RALTFLAG; - } - - // Set stateKeys where corresponding value is passed in e.Lstates - let stateMutation = false; - for(let i=0; i < lockNames.length; i++) { - if((e.Lstates & Codes.stateBitmasks[lockNames[i]]) != 0) { - this.stateKeys[lockKeys[i]] = ((e.Lstates & lockModifiers[i]) != 0); - stateMutation = true; - } - } - - if(stateMutation) { - this.emit('statekeychange', this.stateKeys); - } - } - - this.updateStates(); - - if (this.activeKeyboard.isMnemonic && this.stateKeys['K_CAPS'] && (!e || !e.isModifier)) { - // Modifier keypresses don't trigger mnemonic manipulation of modifier state. - // Only an output key does; active use of Caps will also flip the SHIFT flag. - // Mnemonic keystrokes manipulate the SHIFT property based on CAPS state. - // We need to unflip them when tracking the OSK layer. - keyShiftState ^= ModifierKeyConstants.K_SHIFTFLAG; - } - - this.layerId = this.getLayerId(keyShiftState); - return true; - } - - // TODO-web-core: this could be shared with JsKeyboardProcessor - private updateStates(): void { - for (let i = 0; i < lockKeys.length; i++) { - const key = lockKeys[i]; - const flag = this.stateKeys[key]; - - // Ensures that the current mod-state info properly matches the currently-simulated - // state key states. - if (flag) { - this.modStateFlags |= lockModifiers[i]; - this.modStateFlags &= ~noLockModifers[i]; - } else { - this.modStateFlags &= ~lockModifiers[i]; - this.modStateFlags |= noLockModifers[i]; - } - } - } - - /** - * Resets the keyboard context, optionally using the provided text store. - * Clears or reinitializes the context for subsequent keyboard processing. - * - * @param {TextStore} [textStore] - The optional text store to use for resetting context. - */ - public resetContext(textStore?: TextStore): void {} - - /** - * Finalizes the processor action and applies any final changes to the text store. - * Ensures that all necessary updates are completed after processing a key event. - * - * @param {ProcessorAction} data The processor action to finalize. - * @param {TextStore} textStore The text store to update. - */ - public finalizeProcessorAction(data: ProcessorAction, textStore: TextStore): void { } - - /** - * Selects the next keyboard layer based on the provided key event. - * Determines and applies the appropriate layer switch for the OSK. - * - * @param {KeyEvent} keyEvent The key event used to determine the next layer. - * - * @returns {boolean} True if the keyboard layer changed, false otherwise. - */ - public selectLayer(keyEvent: KeyEvent): boolean { - // TODO-embed-osk-in-kmx: Implement this method (#15284) - return false; - } - - /** - * Sets the numeric layer for the given device. - * Switches the keyboard to a numeric input layer if supported. - * - * @param {DeviceSpec} device - The device for which to set the numeric layer. - */ - public setNumericLayer(device: DeviceSpec): void {} - - - /** @internal */ - public unitTestEndPoints = { - saveMarkersToTextStore: this.saveMarkersToTextStore.bind(this), - applyContextFromTextStore: this.applyContextFromTextStore.bind(this), - }; -} diff --git a/web/src/engine/src/core-processor/index.ts b/web/src/engine/src/core-processor/index.ts deleted file mode 100644 index 3376653ad86..00000000000 --- a/web/src/engine/src/core-processor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CoreKeyboardProcessor } from './coreKeyboardProcessor.js'; diff --git a/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts b/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts index 1c9117a4334..8564e80ef4f 100644 --- a/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts +++ b/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts @@ -1,4 +1,4 @@ -import { Keyboard, JSKeyboard, KeyboardLoaderBase as KeyboardLoader, KMXKeyboard } from "keyman/engine/keyboard"; +import { Keyboard, JSKeyboard, KeyboardLoaderBase as KeyboardLoader } from "keyman/engine/keyboard"; import { EventEmitter } from "eventemitter3"; import { KeyboardStub } from "./keyboardStub.js"; @@ -49,13 +49,6 @@ export class StubAndKeyboardCache extends EventEmitter { } shutdown(): void { - for (const kbdId in this.keyboardTable) { - const kbd = this.keyboardTable[kbdId]; - if (kbd instanceof KMXKeyboard) { - (kbd as KMXKeyboard).shutdown(); - } - // TODO-web-core: do we have to do something if instanceof Promise? - } } getKeyboardForStub(stub: KeyboardStub): Keyboard { @@ -203,7 +196,7 @@ export class StubAndKeyboardCache extends EventEmitter { let keyboardID: string; const languageID = arg1 || '---'; - if (arg0 instanceof JSKeyboard || arg0 instanceof KMXKeyboard) { + if (arg0 instanceof JSKeyboard) { keyboardID = arg0.id; } else { keyboardID = arg0 as string; @@ -235,7 +228,7 @@ export class StubAndKeyboardCache extends EventEmitter { * If `false`, only forgets the metadata (stubs). */ forgetKeyboard(keyboard: string | Keyboard, purge: boolean = false) { - const id: string = (keyboard instanceof JSKeyboard || keyboard instanceof KMXKeyboard) ? keyboard.id : toPrefixedKeyboardId(keyboard as string); + const id: string = (keyboard instanceof JSKeyboard) ? keyboard.id : toPrefixedKeyboardId(keyboard as string); if(this.stubSetTable[id]) { delete this.stubSetTable[id]; diff --git a/web/src/engine/src/keyboard/index.ts b/web/src/engine/src/keyboard/index.ts index 9a0c29ec767..40fd493a059 100644 --- a/web/src/engine/src/keyboard/index.ts +++ b/web/src/engine/src/keyboard/index.ts @@ -3,7 +3,6 @@ export { ButtonClass, ButtonClasses, LayoutLayer, LayoutFormFactor, LayoutRow, L export { JSKeyboard, LayoutState } from "./keyboards/jsKeyboard.js"; export { Keyboard } from './keyboards/keyboard.js'; export { KeyboardMinimalInterface } from './keyboards/keyboardMinimalInterface.js'; -export { KMXKeyboard } from './keyboards/kmxKeyboard.js'; export { KeyboardHarness, KeyboardKeymanGlobal, MinimalCodesInterface, MinimalKeymanGlobal } from "./keyboards/keyboardHarness.js"; export { NotifyEventCode, KeyboardLoaderBase } from "./keyboards/keyboardLoaderBase.js"; export { KeyboardLoadErrorBuilder, KeyboardMissingError, KeyboardScriptError, KeyboardDownloadError, InvalidKeyboardError } from './keyboards/keyboardLoadError.js' diff --git a/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts b/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts index 2236d696535..ee112130127 100644 --- a/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts +++ b/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts @@ -1,5 +1,3 @@ -import { KM_Core, KM_CORE_STATUS } from 'keyman/engine/core-adapter'; -import { KMXKeyboard } from './kmxKeyboard.js'; import { KeyboardHarness } from "./keyboardHarness.js"; import { KeyboardProperties } from "./keyboardProperties.js"; import { KeyboardLoadErrorBuilder, StubBasedErrorBuilder, UriBasedErrorBuilder } from './keyboardLoadError.js'; @@ -59,12 +57,8 @@ export abstract class KeyboardLoaderBase { if (this.isKMXKeyboard(byteArray)) { // KMX or LDML (KMX+) keyboard - const name = this.extractIdFromUrl(uri); - const result = KM_Core.instance.keyboard_load_from_blob(name, byteArray); - if (result.status == KM_CORE_STATUS.OK) { - return new KMXKeyboard(result.object); - } - throw errorBuilder.invalidKeyboard(new Error(`Loading KMX keyboard from ${uri} failed with status ${result.status}`)); + // For version 19, disable loading of .kmx keyboards + throw new Error("TODO: .kmx files are not currently supported"); } let script: string; @@ -78,11 +72,6 @@ export abstract class KeyboardLoaderBase { return await this.loadKeyboardFromScript(script, errorBuilder); } - private extractIdFromUrl(uri: string): string { - // Extract filename without extension from the URL - return uri.split('/').pop().replace(/\.[^/.]+$/, ''); - } - protected abstract loadKeyboardBlob(uri: string, errorBuilder: KeyboardLoadErrorBuilder): Promise; protected abstract loadKeyboardFromScript(scriptSrc: string, errorBuilder: KeyboardLoadErrorBuilder): Promise; diff --git a/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts b/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts deleted file mode 100644 index dfce5943a7d..00000000000 --- a/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { DeviceSpec } from 'keyman/common/web-utils'; -import { - KM_Core, km_core_keyboard, KM_CORE_KMX_ENV, - KM_CORE_OPTION_SCOPE, km_core_state, KM_CORE_STATUS -} from 'keyman/engine/core-adapter'; -import { ActiveKey, ActiveLayout, ActiveSubKey } from './activeLayout.js'; -import { StateKeyMap } from './stateKeyMap.js'; -import { KeyEvent } from '../keyEvent.js'; -import { TextStore } from '../textStore.js'; -import { NotifyEventCode } from './keyboardLoaderBase.js'; -import { Keyboard } from './keyboard.js'; - -/** - * Acts as a wrapper class for KMX(+) Keyman keyboards - */ -export class KMXKeyboard implements Keyboard { - private _state: km_core_state | null = null; - - public constructor(private _keyboard: km_core_keyboard) { - const environment_opts = - [ - { - scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT, - key: KM_CORE_KMX_ENV.PLATFORM, - // TODO-web-core: Turn touch off for non-touch targets; read proper platform string from web (#15289) - value: "web iphone ipad androidphone androidtablet mobile touch hardware android phone" - }, { - scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT, - key: KM_CORE_KMX_ENV.BASELAYOUT, - value: "kbdus.dll" // TODO: Base layout assumed to be US in v19 - }, { - scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT, - key: KM_CORE_KMX_ENV.BASELAYOUTALT, - value: "us", // TODO: Base layout assumed to be US in v19 - },{ - scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT, - key: KM_CORE_KMX_ENV.SIMULATEALTGR, - value: "0" // TODO: We won't support simulating AltGr option in v19 - see also emulatesAltGr - } - ] - const result = KM_Core.instance.state_create(_keyboard, environment_opts); - if (result.status == KM_CORE_STATUS.OK) { - this._state = result.object; - } - } - - public shutdown() { - if (this._state) { - this._state.delete(); - this._state = null; - } - if (this._keyboard) { - KM_Core.instance.keyboard_dispose(this._keyboard); - this._keyboard = null; - } - } - - public constructKeyEvent(key: ActiveKey | ActiveSubKey, device: DeviceSpec, stateKeys: StateKeyMap): KeyEvent { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - return null; - } - - public get isMnemonic(): boolean { - return false; - } - - public get version(): string { - const attrs = KM_Core.instance.keyboard_get_attrs(this._keyboard); - const version = attrs.object.version_string; - attrs.delete(); - return version; - } - - public get id(): string { - const attrs = KM_Core.instance.keyboard_get_attrs(this._keyboard); - const id = attrs.object.id; - attrs.delete(); - return id; - } - - public get name(): string { - // TODO-WEB-CORE: get keyboard name from Core API (to be implemented) - return this.id; - } - - public get keyboard(): km_core_keyboard { - return this._keyboard; - } - - public get state(): km_core_state { - return this._state; - } - - public get isChiral(): boolean { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - // Only relevant for OSK - return false; - } - - /** - * Signifies whether or not a layout or OSK should include AltGr / Right-alt emulation for this keyboard. - * @return {boolean} - */ - public get emulatesAltGr(): boolean { - // TODO: We won't support simulating AltGr option in v19 - see also c'tor - return false; - } - - /** - * Notifies keyboard of keystroke or other event - * - * @param {NotifyEventCode} eventCode key code (16-18: Shift, Control or Alt), - * or 0 for focus - * @param {TextStore} textStore textStore - * @param {number} data 1 for KeyDown or FocusReceived, - * 0 for KeyUp or FocusLost - */ - public notify(eventCode: NotifyEventCode, textStore: TextStore, data: number): void { - // TODO: implement for IMX (cf #2239 and #7928) - } - - /** - * Indicates whether the keyboard layout is designed for right-to-left scripts. - * This can be used by consumers to adjust UI layout and text direction for RTL keyboards. - * - * @returns {boolean} True if the keyboard is right-to-left, otherwise false. - */ - public get isRTL(): boolean { - // TODO-embed-osk-in-kmx: Hook up with Core API (#15482) - return false; - } - - /** - * Returns always false because (legacy) pick lists (Chinese, Japanese, Korean, etc.) - * are not supported when using KMX keyboards. - */ - public get isCJK(): boolean { - // always return false - return false; - } - - /** - * Returns an ActiveLayout object representing the keyboard's layout for this - * form factor. May return null if a custom desktop "help" OSK is defined, - * as with sil_euro_latin. - * - * In such cases, please use either `helpText` or `insertHelpHTML` instead. - * @param formFactor {string} The desired form factor for the layout. - */ - public layout(formFactor: DeviceSpec.FormFactor): ActiveLayout { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - return null; - } - - /** - * Indicates whether the keyboard's desktop layout should be used for the - * specified device. - * - * @param device {DeviceSpec} The device specification to check - * @returns {boolean} True if the desktop layout should be used for - * this device, otherwise false. - */ - public usesDesktopLayoutOnDevice(device: DeviceSpec): boolean { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - return true; - } - - /** - * CSS styling for the on-screen keyboard. - * - * @returns {string} CSS style string for the OSK - */ - public get oskStyling(): string { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - return ''; - } - - -} diff --git a/web/src/engine/src/main/headless/inputProcessor.ts b/web/src/engine/src/main/headless/inputProcessor.ts index 983dc1bdcf0..633f121835f 100644 --- a/web/src/engine/src/main/headless/inputProcessor.ts +++ b/web/src/engine/src/main/headless/inputProcessor.ts @@ -5,8 +5,6 @@ import { LanguageProcessor } from "./languageProcessor.js"; import type { ModelSpec, PathConfiguration } from "keyman/engine/interfaces"; import { globalObject, DeviceSpec, isEmptyTransform } from "keyman/common/web-utils"; -import { CoreKeyboardProcessor } from 'keyman/engine/core-processor'; - import { Codes, JSKeyboard, // required to be able to distinguish between JS and Core kbd processor @@ -38,7 +36,6 @@ export class InputProcessor { */ private contextDevice: DeviceSpec; private jsKbdProcessor: JSKeyboardProcessor; - private coreKbdProcessor: CoreKeyboardProcessor; private _keyboardProcessor: KeyboardProcessor; private _languageProcessor: LanguageProcessor; @@ -53,12 +50,10 @@ export class InputProcessor { this.contextDevice = device; this.jsKbdProcessor = new JSKeyboardProcessor(device, options); this._keyboardProcessor = this.jsKbdProcessor; - this.coreKbdProcessor = new CoreKeyboardProcessor(); this._languageProcessor = new LanguageProcessor(predictiveWorkerFactory, this.contextCache); } public async init(paths: PathConfiguration, storeSerializer: VariableStoreSerializer): Promise { - await this.coreKbdProcessor.init(paths.basePath, storeSerializer); } public get languageProcessor(): LanguageProcessor { @@ -79,12 +74,12 @@ export class InputProcessor { public set activeKeyboard(keyboard: Keyboard) { - if (keyboard instanceof JSKeyboard || keyboard == null) { + // TODO-web-core if (keyboard instanceof JSKeyboard || keyboard == null) { // TODO-web-core: consider keyboard==null scenario; which keyboardProcessor should be active? this._keyboardProcessor = this.jsKbdProcessor; - } else { - this._keyboardProcessor = this.coreKbdProcessor; - } + // TODO-web-core } else { + // TODO-web-core this._keyboardProcessor = this.coreKbdProcessor; + // TODO-web-core } this.keyboardInterface.activeKeyboard = keyboard; diff --git a/web/src/engine/tsconfig.json b/web/src/engine/tsconfig.json index bf7f732dcb9..3698afeb044 100644 --- a/web/src/engine/tsconfig.json +++ b/web/src/engine/tsconfig.json @@ -5,6 +5,7 @@ "outDir": "../../build/engine/obj/", "tsBuildInfoFile": "../../build/engine/obj/tsconfig.tsbuildinfo", "rootDir": "./src", + "strictNullChecks": false }, "include": [ "src/**/*.ts", "src/core-processor/import/*.js" ], diff --git a/web/src/test/auto/headless/engine/core-adapter/core-adapter.tests.ts b/web/src/test/auto/headless/engine/core-adapter/core-adapter.tests.ts deleted file mode 100644 index c688214d259..00000000000 --- a/web/src/test/auto/headless/engine/core-adapter/core-adapter.tests.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ - -import { assert } from 'chai'; -import { KM_Core, KM_CORE_STATUS } from 'keyman/engine/core-adapter'; -import { coreurl, loadKeyboardBlob } from '../loadKeyboardHelper.js'; - -// Test the KM_Core interface. -describe('KM_Core', function () { - this.timeout(5000); // increased timeout for async loading - - it('can initialize without errors', async function () { - const km_core = await KM_Core.createCoreProcessor(coreurl); - assert.isNotNull(km_core); - }); - - it('can call temp function', async function () { - const km_core = await KM_Core.createCoreProcessor(coreurl); - const a = km_core.tmp_wasm_attributes(); - assert.isOk(a); - assert.isNumber(a.max_context); - console.dir(a); - }); - - it('can load a keyboard from blob', async function () { - const km_core = await KM_Core.createCoreProcessor(coreurl); - const blob = await loadKeyboardBlob('/common/test/resources/keyboards/test_8568_deadkeys.kmx') - const result = km_core.keyboard_load_from_blob('test', blob); - assert.equal(result.status, KM_CORE_STATUS.OK); - assert.isOk(result.object); - result.delete(); - }); - - it('can get version from keyboard', async function () { - const km_core = await KM_Core.createCoreProcessor(coreurl); - const blob = await loadKeyboardBlob('/common/test/resources/keyboards/test_8568_deadkeys.kmx') - const keyboard = km_core.keyboard_load_from_blob('test', blob); - const result = km_core.keyboard_get_attrs(keyboard.object); - - assert.equal(result.status, KM_CORE_STATUS.OK); - assert.isOk(result.object); - assert.equal(result.object.version_string, '0.0'); - result.delete(); - }); -}); diff --git a/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts b/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts deleted file mode 100644 index 7c4e3690fe5..00000000000 --- a/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ - -import { assert } from 'chai'; -import sinon from 'sinon'; -import { KM_Core, km_core_context, km_core_keyboard, km_core_state, KM_CORE_CT, KM_CORE_STATUS, km_core_context_items } from 'keyman/engine/core-adapter'; -import { coreurl, loadKeyboardBlob } from '../loadKeyboardHelper.js'; -import { DeviceSpec } from 'keyman/common/web-utils'; -import { Codes, Deadkey, KeyEvent, KMXKeyboard, SyntheticTextStore } from 'keyman/engine/keyboard'; -import { CoreKeyboardProcessor } from 'keyman/engine/core-processor'; -import { VariableStoreCookieSerializer } from 'keyman/engine/main'; - -describe('CoreKeyboardProcessor', function () { - const loadKeyboard = function (name: string): km_core_keyboard { - const blob = loadKeyboardBlob(name); - const result = KM_Core.instance.keyboard_load_from_blob(name, blob); - assert.equal(result.status, 0); - assert.isOk(result.object); - return result.object; - }; - - const createState = function (keyboardName: string): km_core_state { - const keyboard = loadKeyboard(keyboardName); - const state = KM_Core.instance.state_create(keyboard, []); - assert.equal(state.status, 0); - assert.isOk(state.object); - return state.object; - }; - - const addContextItem = function (contextItems: km_core_context_items, c: string | number, isMarker: boolean) { - const item = new KM_Core.instance.km_core_context_item(); - if (isMarker) { - item.marker = c as number; - } else if (typeof c == 'number') { - item.character = c; - } else { - item.character = c.codePointAt(0); - } - contextItems.push_back(item); - }; - - let coreProcessor: CoreKeyboardProcessor; - let state: km_core_state; - let context: km_core_context; - let textStore: SyntheticTextStore; - let sandbox: sinon.SinonSandbox; - - describe('saveMarkersToTextStore', function () { - beforeEach(async function () { - coreProcessor = new CoreKeyboardProcessor(); - await coreProcessor.init(coreurl, new VariableStoreCookieSerializer()); - state = createState('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - context = KM_Core.instance.state_context(state); - sandbox = sinon.createSandbox(); - Deadkey.ordinalSeed = 0; - }); - - afterEach(() => { - sandbox.restore(); - sandbox = null; - }) - - it('saves markers to TextStore (BMP)', function() { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - // Text index : 0 1 1 1 2 3 3 - // context index: 0 1 2 3 4 5 6 - // ContextItems : a dk1 dk2 b c dk3 d - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, 'a', false); - addContextItem(contextItems, 1, true); // deadkey 1 - addContextItem(contextItems, 2, true); // deadkey 2 - addContextItem(contextItems, 'b', false); - addContextItem(contextItems, 'c', false); - addContextItem(contextItems, 3, true); // deadkey 3 - addContextItem(contextItems, 'd', false); - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void {} - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 3, 'Should have 3 deadkeys'); - assert.equal(textStore.deadkeys().dks[0].toString(), 'Deadkey { p: 1, d: 1, o: 0, matched: 0}', 'dks[0]'); - assert.equal(textStore.deadkeys().dks[1].toString(), 'Deadkey { p: 1, d: 2, o: 1, matched: 0}', 'dks[1]'); - assert.equal(textStore.deadkeys().dks[2].toString(), 'Deadkey { p: 3, d: 3, o: 2, matched: 0}', 'dks[2]'); - }); - - it('saves markers to TextStore (SMP)', function () { - // Setup - textStore = new SyntheticTextStore('𐌀𐌁𐌂𐌃', 6); // U+10300 U+10301 U+10302 U+10303 - // Text index : 0 1 2 2 2 3 4 5 6 6 7 - // context index: 0 1 2 3 4 5 6 - // ContextItems : 𐌀 dk1 dk2 𐌁 𐌂 dk3 END - // ContextItem.character is a UTF-32 value! - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, '𐌀', false); - addContextItem(contextItems, 1, true); // deadkey 1 - addContextItem(contextItems, 2, true); // deadkey 2 - addContextItem(contextItems, '𐌁', false); - addContextItem(contextItems, '𐌂', false); - addContextItem(contextItems, 3, true); // deadkey 3 - addContextItem(contextItems, '𐌃', false); - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void { } - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 3, 'Should have 3 deadkeys'); - assert.equal(textStore.deadkeys().dks[0].toString(), 'Deadkey { p: 2, d: 1, o: 0, matched: 0}', 'dks[0]'); - assert.equal(textStore.deadkeys().dks[1].toString(), 'Deadkey { p: 2, d: 2, o: 1, matched: 0}', 'dks[1]'); - assert.equal(textStore.deadkeys().dks[2].toString(), 'Deadkey { p: 6, d: 3, o: 2, matched: 0}', 'dks[2]'); - }); - - it('can save to TextStore without markers (BMP)', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - // Text index : 0 1 2 3 - // context index: 0 1 2 3 - // ContextItems : a b c d - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, 'a', false); - addContextItem(contextItems, 'b', false); - addContextItem(contextItems, 'c', false); - addContextItem(contextItems, 'd', false); - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void { } - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 0, 'Should have 0 deadkeys'); - }); - - it('can save to TextStore without markers (SMP)', function () { - // Setup - textStore = new SyntheticTextStore('𐌀𐌁𐌂𐌃', 6); // U+10300 U+10301 U+10302 U+10303 - // Text index : 0 1 2 3 4 5 - // context index: 0 1 2 3 - // ContextItems : 𐌀 𐌁 𐌂 END - // ContextItem.character is a UTF-32 value! - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, '𐌀', false); - addContextItem(contextItems, '𐌁', false); - addContextItem(contextItems, '𐌂', false); - addContextItem(contextItems, '𐌃', false); - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void { } - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 0, 'Should have 0 deadkeys'); - }); - - it('saves markers to TextStore for empty text', function () { - // Setup - textStore = new SyntheticTextStore('', 0); - // Text index : 0 0 0 - // context index: 0 1 2 - // ContextItems : dk1 dk2 END - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, 1, true); // deadkey 1 - addContextItem(contextItems, 2, true); // deadkey 2 - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void { } - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 2, 'Should have 2 deadkeys'); - assert.equal(textStore.deadkeys().dks[0].toString(), 'Deadkey { p: 0, d: 1, o: 0, matched: 0}', 'dks[0]'); - assert.equal(textStore.deadkeys().dks[1].toString(), 'Deadkey { p: 0, d: 2, o: 1, matched: 0}', 'dks[1]'); - }); - }); - - - describe('applyContextFromTextStore', function () { - beforeEach(async function () { - coreProcessor = new CoreKeyboardProcessor(); - await coreProcessor.init(coreurl, new VariableStoreCookieSerializer()); - state = createState('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - context = KM_Core.instance.state_context(state); - }); - - it('applies deadkeys from TextStore to Core context (BMP)', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - textStore.deadkeys().add(new Deadkey(1, 1)); // before 'b' - textStore.deadkeys().add(new Deadkey(1, 2)); - textStore.deadkeys().add(new Deadkey(3, 3)); // before 'd' - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 1 1 2 3 3 - // Text: : a b c | d - // context index: 0 1 2 3 4 5 6 - // ContextItems : a dk1 dk2 b c dk3 END - assert.equal(items.size(), 7, 'Should have 7 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 'a'.charCodeAt(0), 'Item 0 should be "a"'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 1, 'Item 1 should be marker 1'); - assert.equal(items.get(2).type, KM_CORE_CT.MARKER, 'Item 2 should be MARKER'); - assert.equal(items.get(2).marker, 2, 'Item 2 should be marker 2'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 'b'.charCodeAt(0), 'Item 3 should be "b"'); - assert.equal(items.get(4).type, KM_CORE_CT.CHAR, 'Item 4 should be CHAR'); - assert.equal(items.get(4).character, 'c'.charCodeAt(0), 'Item 4 should be "c"'); - assert.equal(items.get(5).type, KM_CORE_CT.MARKER, 'Item 5 should be MARKER'); - assert.equal(items.get(5).marker, 3, 'Item 5 should be marker 3'); - assert.equal(items.get(6).type, KM_CORE_CT.END, 'Item 6 should be END'); - result.delete(); - }); - - it('applies deadkeys from TextStore to Core context (SMP)', function () { - // Setup - // Use a string with Old Italic letters which will consist of surrogate pairs - // in a UTF-16 string. - textStore = new SyntheticTextStore('𐌀𐌁𐌂𐌃', 6); // U+10300 U+10301 U+10302 U+10303 - textStore.deadkeys().add(new Deadkey(2, 1)); // before '𐌁' - textStore.deadkeys().add(new Deadkey(2, 2)); - textStore.deadkeys().add(new Deadkey(6, 3)); // before '𐌃' - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 2 2 2 3 4 5 6 6 7 - // Text: : 𐌀 𐌁 𐌂 | 𐌃 - // context index: 0 1 2 3 4 5 6 - // ContextItems : 𐌀 dk1 dk2 𐌁 𐌂 dk3 END - // ContextItem.character is a UTF-32 value! - assert.equal(items.size(), 7, 'Should have 7 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 0x10300, 'Item 0 should be "𐌀"'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 1, 'Item 1 should be marker 1'); - assert.equal(items.get(2).type, KM_CORE_CT.MARKER, 'Item 2 should be MARKER'); - assert.equal(items.get(2).marker, 2, 'Item 2 should be marker 2'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 0x10301, 'Item 3 should be "𐌁"'); - assert.equal(items.get(4).type, KM_CORE_CT.CHAR, 'Item 4 should be CHAR'); - assert.equal(items.get(4).character, 0x10302, 'Item 4 should be "𐌂"'); - assert.equal(items.get(5).type, KM_CORE_CT.MARKER, 'Item 5 should be MARKER'); - assert.equal(items.get(5).marker, 3, 'Item 5 should be marker 3'); - assert.equal(items.get(6).type, KM_CORE_CT.END, 'Item 6 should be END'); - result.delete(); - }); - - it('applies text from TextStore to Core context without deadkeys (BMP)', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 2 3 - // Text: : a b c | d - // context index: 0 1 2 3 - // ContextItems : a b c END - assert.equal(items.size(), 4, 'Should have 4 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 'a'.charCodeAt(0), 'Item 0 should be "a"'); - assert.equal(items.get(1).type, KM_CORE_CT.CHAR, 'Item 1 should be CHAR'); - assert.equal(items.get(1).character, 'b'.charCodeAt(0), 'Item 1 should be "b"'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 'c'.charCodeAt(0), 'Item 2 should be "c"'); - assert.equal(items.get(3).type, KM_CORE_CT.END, 'Item 3 should be END'); - result.delete(); - }); - - it('applies text from TextStore to Core context without deadkeys (SMP)', function () { - // Setup - // Use a string with Old Italic letters which will consist of surrogate pairs - // in a UTF-16 string. - textStore = new SyntheticTextStore('𐌀𐌁𐌂𐌃', 6); // U+10300 U+10301 U+10302 U+10303 - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 2 3 4 5 6 7 - // Text: : 𐌀 𐌁 𐌂 | 𐌃 - // context index: 0 1 2 3 - // ContextItems : 𐌀 𐌁 𐌂 END - // ContextItem.character is a UTF-32 value! - assert.equal(items.size(), 4, 'Should have 4 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 0x10300, 'Item 0 should be "𐌀"'); - assert.equal(items.get(1).type, KM_CORE_CT.CHAR, 'Item 1 should be CHAR'); - assert.equal(items.get(1).character, 0x10301, 'Item 1 should be "𐌁"'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 0x10302, 'Item 2 should be "𐌂"'); - assert.equal(items.get(3).type, KM_CORE_CT.END, 'Item 3 should be END'); - result.delete(); - }); - - it('applies text from TextStore to Core context also after deadkeys', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - textStore.deadkeys().add(new Deadkey(1, 1)); // before 'b' - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 1 2 3 - // Text: : a b c | d - // context index: 0 1 2 3 4 - // ContextItems : a dk1 b c END - assert.equal(items.size(), 5, 'Should have 5 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 'a'.charCodeAt(0), 'Item 0 should be "a"'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 1, 'Item 1 should be marker 1'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 'b'.charCodeAt(0), 'Item 2 should be "b"'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 'c'.charCodeAt(0), 'Item 3 should be "c"'); - assert.equal(items.get(4).type, KM_CORE_CT.END, 'Item 4 should be END'); - result.delete(); - }); - - it('works with empty text', function () { - // Setup - textStore = new SyntheticTextStore('', 0); - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 - // Text: : | - // context index: 0 - // ContextItems : END - assert.equal(items.size(), 1, 'Should have 1 context item'); - assert.equal(items.get(0).type, KM_CORE_CT.END, 'Item 0 should be END'); - result.delete(); - }); - - it('works with deadkey before the text', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - textStore.deadkeys().add(new Deadkey(0, 1)); // before 'a' - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 0 1 2 3 - // Text: : a b c | d - // context index: 0 1 2 3 4 - // ContextItems : dk1 a b c END - assert.equal(items.size(), 5, 'Should have 5 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.MARKER, 'Item 0 should be MARKER'); - assert.equal(items.get(0).marker, 1, 'Item 0 should be marker 1'); - assert.equal(items.get(1).type, KM_CORE_CT.CHAR, 'Item 1 should be CHAR'); - assert.equal(items.get(1).character, 'a'.charCodeAt(0), 'Item 1 should be "a"'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 'b'.charCodeAt(0), 'Item 2 should be "b"'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 'c'.charCodeAt(0), 'Item 3 should be "c"'); - assert.equal(items.get(4).type, KM_CORE_CT.END, 'Item 4 should be END'); - result.delete(); - }); - - it('works with deadkeys before empty text', function () { - // Setup - textStore = new SyntheticTextStore('', 0); - textStore.deadkeys().add(new Deadkey(0, 1)); // before caret - textStore.deadkeys().add(new Deadkey(0, 2)); // before caret - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 - // Text: : | - // context index: 0 1 2 - // ContextItems : dk1 dk2 END - assert.equal(items.size(), 3, 'Should have 3 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.MARKER, 'Item 0 should be MARKER'); - assert.equal(items.get(0).marker, 1, 'Item 0 should be marker 1'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 2, 'Item 1 should be marker 2'); - assert.equal(items.get(2).type, KM_CORE_CT.END, 'Item 2 should be END'); - result.delete(); - }); - - it('skips invalid deadkeys', function () { - // Setup - textStore = new SyntheticTextStore('abcde', 3); - textStore.deadkeys().add(new Deadkey(-1, 1)); // invalid - textStore.deadkeys().add(new Deadkey(1, 2)); // after 'b' - textStore.deadkeys().add(new Deadkey(4, 3)); // invalid (after caret) - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 1 2 3 - // Text: : | - // context index: 0 1 2 3 4 - // ContextItems : a dk2 b c END - assert.equal(items.size(), 5, 'Should have 5 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 'a'.charCodeAt(0), 'Item 0 should be "a"'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 2, 'Item 1 should be marker 2'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 'b'.charCodeAt(0), 'Item 2 should be "b"'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 'c'.charCodeAt(0), 'Item 3 should be "c"'); - assert.equal(items.get(4).type, KM_CORE_CT.END, 'Item 4 should be END'); - result.delete(); - }); - }); - - describe('processKeystroke', function () { - let process_event_spy: any; - - beforeEach(async function () { - coreProcessor = new CoreKeyboardProcessor(); - await coreProcessor.init(coreurl, new VariableStoreCookieSerializer()); - state = createState('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - context = KM_Core.instance.state_context(state); - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - sandbox = null; - }) - - for (const eventType of ['keydown', 'keyup']) { - const isKeyDown = eventType === 'keydown'; - - it(`passes the correct value for a ${eventType} event`, function () { - // Setup - const keyEvent = new KeyEvent({ - Lcode: Codes.keyCodes.K_A, - Lmodifiers: Codes.modifierCodes.SHIFT, - Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK, - LisVirtualKey: true, - device: null, - kName: 'K_A' - }); - keyEvent.source = { type: eventType }; - - const coreKeyboard = loadKeyboard('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - const kmxKeyboard = new KMXKeyboard(coreKeyboard); - sandbox.replaceGetter(coreProcessor, 'activeKeyboard', () => { return kmxKeyboard; }); - // We return a non-ok value just so that we can return early from - // processKeyStroke() - process_event_spy = sandbox.spy(KM_Core.instance, 'process_event'); - - // Execute - coreProcessor.processKeystroke(keyEvent, new SyntheticTextStore()); - - // Verify - check fourth argument (is_key_down) - assert.equal(process_event_spy.args[0][3], isKeyDown); - }); - } - }); - - describe('doModifierPress', function () { - // const touchable = true; - const nonTouchable = false; - - beforeEach(async function () { - coreProcessor = new CoreKeyboardProcessor(); - await coreProcessor.init(coreurl, new VariableStoreCookieSerializer()); - state = createState('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - context = KM_Core.instance.state_context(state); - sandbox = sinon.createSandbox(); - const coreKeyboard = loadKeyboard('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - coreProcessor.activeKeyboard = new KMXKeyboard(coreKeyboard); - }); - - afterEach(() => { - sandbox.restore(); - sandbox = null; - }) - - for (const key of [ - { code: Codes.keyCodes.K_SHIFT, name: 'Shift' }, - { code: Codes.keyCodes.K_CONTROL, name: 'Control' }, - { code: Codes.keyCodes.K_ALT, name: 'Alt' }, - { code: Codes.keyCodes.K_CAPS, name: 'CapsLock' }, - { code: Codes.keyCodes.K_NUMLOCK, name: 'NumLock' }, - { code: Codes.keyCodes.K_SCROLL, name: 'ScrollLock' }, - ]) { - it(`recognizes ${key.name} as modifier`, function () { - // Setup - const keyEvent = new KeyEvent({ - Lcode: key.code, - Lmodifiers: 0, - Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK, - LisVirtualKey: true, - device: new DeviceSpec('chrome', 'desktop', 'windows', nonTouchable), - kName: key.name - }); - keyEvent.source = { type: 'keydown' }; - - // Execute - const result = coreProcessor.doModifierPress(keyEvent, new SyntheticTextStore(), true); - - // Verify - assert.isTrue(result); - }); - } - - for (const key of [ - { modifiers: 0, name: 'a' }, - { modifiers: Codes.modifierCodes.SHIFT, name: 'A' } - ]) { - it(`recognizes ${key.name} not as modifier`, function () { - // Setup - const keyEvent = new KeyEvent({ - Lcode: Codes.keyCodes.K_A, - Lmodifiers: key.modifiers, - Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK, - LisVirtualKey: true, - device: new DeviceSpec('chrome', 'desktop', 'windows', nonTouchable), - kName: 'K_A' - }); - keyEvent.source = { type: 'keydown' }; - - // Execute - const result = coreProcessor.doModifierPress(keyEvent, new SyntheticTextStore(), true); - - // Verify - assert.isFalse(result); - }); - } - - }); -}); diff --git a/web/src/test/auto/headless/engine/keyboard/kmxkeyboard.tests.ts b/web/src/test/auto/headless/engine/keyboard/kmxkeyboard.tests.ts deleted file mode 100644 index 9754e179775..00000000000 --- a/web/src/test/auto/headless/engine/keyboard/kmxkeyboard.tests.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ -import { assert } from 'chai'; -import { createRequire } from 'module'; -const require = createRequire(import.meta.url); - -import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; -import { NodeKeyboardLoader } from 'keyman/test/resources'; - -describe('KMXKeyboard tests', function () { - // TODO-embed-osk-in-kmx: Enable when hooking up with RTL Core API (#15482) - it.skip('supports RTL layouts', async () => { - const rtlPath = require.resolve('@keymanapp/common-test-resources/keyboards/test_rtl.kmx'); - // -- START: Standard Recorder-based unit test loading boilerplate -- - const harness = new KeyboardHarness({}, MinimalKeymanGlobal); - const keyboardLoader = new NodeKeyboardLoader(harness); - const keyboard = await keyboardLoader.loadKeyboardFromPath(rtlPath); - // -- END: Standard Recorder-based unit test loading boilerplate -- - - assert.isTrue(keyboard.isRTL); - }); -}); diff --git a/web/src/test/auto/headless/engine/loadKeyboardHelper.ts b/web/src/test/auto/headless/engine/loadKeyboardHelper.ts index 5d1dc8b8387..5ffaed719e7 100644 --- a/web/src/test/auto/headless/engine/loadKeyboardHelper.ts +++ b/web/src/test/auto/headless/engine/loadKeyboardHelper.ts @@ -3,14 +3,11 @@ */ import fs from 'node:fs'; -import { pathToFileURL } from 'node:url'; import { getKeymanRoot } from 'keyman/test/resources'; const KEYMAN_ROOT = getKeymanRoot(); -export const coreurl = pathToFileURL(`${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core`).toString(); - export function loadKeyboardBlob(filename: string) { const data = fs.readFileSync(`${KEYMAN_ROOT}${filename}`, null); return new Uint8Array(data); From ee8e3e8664208f7ac191d69bd07682021eac19e8 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 1 Jun 2026 16:54:45 +0200 Subject: [PATCH 31/72] chore: remove keymancore.d.ts --- .../core-adapter/import/core/keymancore.d.ts | 157 ------------------ 1 file changed, 157 deletions(-) delete mode 100644 web/src/engine/src/core-adapter/import/core/keymancore.d.ts diff --git a/web/src/engine/src/core-adapter/import/core/keymancore.d.ts b/web/src/engine/src/core-adapter/import/core/keymancore.d.ts deleted file mode 100644 index 4955b403070..00000000000 --- a/web/src/engine/src/core-adapter/import/core/keymancore.d.ts +++ /dev/null @@ -1,157 +0,0 @@ -// TypeScript bindings for emscripten-generated code. Automatically generated at compile time. -declare namespace RuntimeExports { - /** - * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the - * emscripten HEAP, returns a copy of that string as a Javascript String object. - * - * @param {number} ptr - * @param {number=} maxBytesToRead - An optional length that specifies the - * maximum number of bytes to read. You can omit this parameter to scan the - * string until the first 0 byte. If maxBytesToRead is passed, and the string - * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the - * string will cut short at that byte index (i.e. maxBytesToRead will not - * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing - * frequent uses of UTF8ToString() with and without maxBytesToRead may throw - * JS JIT optimizations off, so it is worth to consider consistently using one - * @return {string} - */ - function UTF8ToString(ptr: number, maxBytesToRead?: number): string; - function stringToNewUTF8(str: any): any; - let wasmExports: any; - let HEAPF32: any; - let HEAPF64: any; - let HEAP_DATA_VIEW: any; - let HEAP8: any; - let HEAPU8: any; - let HEAP16: any; - let HEAPU16: any; - let HEAP32: any; - let HEAPU32: any; - let HEAP64: any; - let HEAPU64: any; -} -interface WasmModule { -} - -type EmbindString = ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string; -export interface km_core_keyboard { - delete(): void; -} - -export interface CoreKeyboardReturn { - readonly object: km_core_keyboard; - readonly status: number; - delete(): void; -} - -export interface CoreKeyboardAttrsReturn { - readonly object: km_core_keyboard_attrs; - readonly status: number; - delete(): void; -} - -export interface CoreContextReturn { - readonly object: km_core_context_items; - readonly status: number; - delete(): void; -} - -export interface km_core_keyboard_attrs { - version_string: string; - id: string; - default_options: any; - delete(): void; -} - -export interface km_core_actions { - do_alert: boolean; - emit_keystroke: boolean; - new_caps_lock_state: number; - code_points_to_delete: number; - output: string; - deleted_context: string; - persist_options: any; - toString(): string; - delete(): void; -} - -export interface km_core_state { - delete(): void; -} - -export interface CoreStateReturn { - readonly object: km_core_state; - readonly status: number; - delete(): void; -} - -export interface km_core_context { - delete(): void; -} - -export interface km_core_context_items { - data(): km_core_context_item; - push_back(item: km_core_context_item): void; - resize(size: number): void; - size(): number; - get(index: number): km_core_context_item; - set(index: number, item: km_core_context_item): void; - toString(): string; - delete(): void; -} - -export interface km_core_context_item { - readonly type: number; - character: number; - marker: number; - toString(): string; - delete(): void; -} - -export type km_core_attr = { - max_context: number, - current: number, - revision: number, - age: number, - technology: number -}; - -export type km_core_option_item = { - key: string, - value: string, - scope: number -}; - -interface EmbindModule { - km_core_keyboard: {}; - CoreKeyboardReturn: {}; - CoreKeyboardAttrsReturn: {}; - CoreContextReturn: {}; - km_core_keyboard_attrs: {}; - km_core_actions: {}; - km_core_state: {}; - CoreStateReturn: {}; - km_core_context: {}; - km_core_context_items: {new(): km_core_context_items}; - km_core_context_item: {new(): km_core_context_item}; - create_end_context(): km_core_context_item; - keyboard_get_attrs(keyboard: km_core_keyboard): CoreKeyboardAttrsReturn; - state_clone(state: km_core_state): CoreStateReturn; - state_context(state: km_core_state): km_core_context; - state_get_actions(state: km_core_state): km_core_actions; - context_get(context: km_core_context): CoreContextReturn; - keyboard_dispose(keyboard: km_core_keyboard): void; - state_dispose(state: km_core_state): void; - process_event(state: km_core_state, vk: number, modifier_state: number, is_key_down: boolean, event_flags: number): number; - state_context_clear(state: km_core_state): number; - context_set(context: km_core_context, context_items: km_core_context_items): number; - tmp_wasm_attributes(): km_core_attr; - state_context_set_if_needed(state: km_core_state, application_context: string): number; - state_context_debug(state: km_core_state, context_type: number): string; - keyboard_load_from_blob(kb_name: EmbindString, blob: any): CoreKeyboardReturn; - state_create(keyboard: km_core_keyboard, env: any): CoreStateReturn; - state_options_update(state: km_core_state, new_options: any): number; -} - -export type MainModule = WasmModule & typeof RuntimeExports & EmbindModule; -export default function MainModuleFactory (options?: unknown): Promise; From bf1e272d17ce9fb4317f3e89f0195776d3ba7e33 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 1 Jun 2026 16:56:00 +0200 Subject: [PATCH 32/72] chore: remove commented code --- web/src/engine/src/main/headless/inputProcessor.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/web/src/engine/src/main/headless/inputProcessor.ts b/web/src/engine/src/main/headless/inputProcessor.ts index 633f121835f..2a079776924 100644 --- a/web/src/engine/src/main/headless/inputProcessor.ts +++ b/web/src/engine/src/main/headless/inputProcessor.ts @@ -74,13 +74,7 @@ export class InputProcessor { public set activeKeyboard(keyboard: Keyboard) { - // TODO-web-core if (keyboard instanceof JSKeyboard || keyboard == null) { - // TODO-web-core: consider keyboard==null scenario; which keyboardProcessor should be active? - this._keyboardProcessor = this.jsKbdProcessor; - // TODO-web-core } else { - // TODO-web-core this._keyboardProcessor = this.coreKbdProcessor; - // TODO-web-core } - + this._keyboardProcessor = this.jsKbdProcessor; this.keyboardInterface.activeKeyboard = keyboard; // All old deadkeys and keyboard-specific cache should immediately be invalidated From 3cb59e3cf9208b1a9192c77ea16f1cb40630b0b0 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 1 Jun 2026 17:51:42 +0200 Subject: [PATCH 33/72] fix(web): fix race displaying active keyboard in menu Fixes: #16041 Build-bot: skip release:web Test-bot: skip --- web/src/app/ui/kmwuitoggle.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/web/src/app/ui/kmwuitoggle.ts b/web/src/app/ui/kmwuitoggle.ts index 1aa45614654..3f48c261970 100644 --- a/web/src/app/ui/kmwuitoggle.ts +++ b/web/src/app/ui/kmwuitoggle.ts @@ -542,8 +542,18 @@ if(!keymanweb) { } // Highlight the last active keyboard - const sk=keymanweb.getSavedKeyboard().split(':'); - this.updateMenu(sk[0],sk[1]); + let activeKeyboard = keymanweb.getActiveKeyboard(); + let activeLanguage = ''; + if (activeKeyboard) { + activeLanguage = keymanweb.getActiveLanguage(); + } else { + // savedKeyboard is only correct if we use global keyboard settings, + // otherwise it's set to the first keyboard in the list + const savedKeyboard = keymanweb.getSavedKeyboard().split(':'); + activeKeyboard = savedKeyboard[0]; + activeLanguage = savedKeyboard[1]; + } + this.updateMenu(activeKeyboard, activeLanguage); } /* ---------------------------------------- From 29618fb9437b2fae99e4150e85a6b0059eb8b944 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 2 Jun 2026 08:26:21 +0200 Subject: [PATCH 34/72] chore: remove kmx-specific keyboard tests --- web/src/test/auto/e2e/keyboard.tests.ts | 31 ------------------------- 1 file changed, 31 deletions(-) diff --git a/web/src/test/auto/e2e/keyboard.tests.ts b/web/src/test/auto/e2e/keyboard.tests.ts index 590da8a840b..a9f2c0b69eb 100644 --- a/web/src/test/auto/e2e/keyboard.tests.ts +++ b/web/src/test/auto/e2e/keyboard.tests.ts @@ -3,37 +3,6 @@ */ import { test, expect, type Page, type Locator } from '@playwright/test'; -test.describe('KMX keyboards', function () { - let textarea: Locator; - const beforeEach = async (page: Page) => { - await page.goto('http://localhost:3000/src/test/manual/web/kmxkeyboard.html?type=kmx'); - await page.evaluate(async () => { await window.KmwLoaded; }); - textarea = page.locator('#inputarea'); - await textarea.click(); - // we don't have a OSK yet, so don't wait for it to appear - } - - test('can type on a (simulated) hardware keyboard', async ({ page }) => { - await beforeEach(page); - await page.keyboard.press('x'); - await page.keyboard.press('j'); - await page.keyboard.press('m'); - await page.keyboard.press('Shift+e'); - await page.keyboard.press('r'); - await expect(textarea).toHaveValue('ខ្មែរ', { timeout: 500 }); - }); - - test('can type on a (simulated) hardware keyboard with reordering', async ({ page }) => { - await beforeEach(page); - await page.keyboard.press('x'); - await page.keyboard.press('Shift+e'); - await page.keyboard.press('j'); - await page.keyboard.press('m'); - await page.keyboard.press('r'); - await expect(textarea).toHaveValue('ខ្មែរ', { timeout: 500 }); - }); -}); - test.describe('JS keyboards', function () { let textarea: Locator; const beforeEach = async (page: Page) => { From dec447f2101fb5612e70f8f03f6b1bea38f109d6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 2 Jun 2026 10:10:03 +0200 Subject: [PATCH 35/72] chore(web): disable .kmx baseline tests --- web/src/test/auto/e2e/baseline/baseline.tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/test/auto/e2e/baseline/baseline.tests.ts b/web/src/test/auto/e2e/baseline/baseline.tests.ts index 72e58629461..3f9143df077 100644 --- a/web/src/test/auto/e2e/baseline/baseline.tests.ts +++ b/web/src/test/auto/e2e/baseline/baseline.tests.ts @@ -86,7 +86,7 @@ test.describe('Baseline tests', () => { // Find all k_*.kmn files in testDir const files = fs.readdirSync(KeymanRoot() + testDir).filter(f => f.match(/^k_.*\.kmn$/)); - for (const ext of ['.kmx', '.js']) { + for (const ext of ['.js']) { test.describe(`${ext} tests`, () => { for (const file of files) { test(file, async ({ page }) => { From f82793636223119eb3a5416a6c59f31bf6c75637 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 2 Jun 2026 12:15:12 +0200 Subject: [PATCH 36/72] fix(web): fix race in `kmwuifloat.ts` as well --- web/src/app/ui/kmwuifloat.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/web/src/app/ui/kmwuifloat.ts b/web/src/app/ui/kmwuifloat.ts index f67463cf486..add9b4f6003 100644 --- a/web/src/app/ui/kmwuifloat.ts +++ b/web/src/app/ui/kmwuifloat.ts @@ -261,12 +261,19 @@ if(!keymanweb) { } this.updateList = false; - // Set the menu selector to the currently saved keyboard - const sk = keymanweb.getSavedKeyboard().split(':'); - if(sk.length < 2) { - sk[1] = ''; + // Set the menu selector to the last active keyboard + let activeKeyboard = keymanweb.getActiveKeyboard(); + let activeLanguage = ''; + if (activeKeyboard) { + activeLanguage = keymanweb.getActiveLanguage(); + } else { + // savedKeyboard is only correct if we use global keyboard settings, + // otherwise it's set to the first keyboard in the list + const savedKeyboard = keymanweb.getSavedKeyboard().split(':'); + activeKeyboard = savedKeyboard[0]; + activeLanguage = savedKeyboard.length < 2 ? '' : savedKeyboard[1]; } - this.updateMenu(sk[0],sk[1]); + this.updateMenu(activeKeyboard, activeLanguage); // Redisplay the UI to correct width for any new language entries if(keymanweb.getLastActiveElement()) { From 92440317b8b51ad8a82c24e704edd8c73962337a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:52:22 +0000 Subject: [PATCH 37/72] chore(deps): bump minimatch in /core/tests/unit/wasm Bumps and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together. Updates `minimatch` from 9.0.5 to 9.0.9 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v9.0.5...v9.0.9) Updates `minimatch` from 5.1.6 to 5.1.9 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v9.0.5...v9.0.9) --- updated-dependencies: - dependency-name: minimatch dependency-version: 9.0.9 dependency-type: indirect - dependency-name: minimatch dependency-version: 5.1.9 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- core/tests/unit/wasm/package-lock.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/tests/unit/wasm/package-lock.json b/core/tests/unit/wasm/package-lock.json index f2c3485da56..ab83293f545 100644 --- a/core/tests/unit/wasm/package-lock.json +++ b/core/tests/unit/wasm/package-lock.json @@ -133,9 +133,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -572,13 +572,13 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -770,9 +770,9 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { From 45329e96808ccdafd26b25d4b03434de4764d41e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:52:23 +0000 Subject: [PATCH 38/72] chore(deps): bump glob from 10.4.5 to 10.5.0 in /core/tests/unit/wasm Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0. - [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md) - [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0) --- updated-dependencies: - dependency-name: glob dependency-version: 10.5.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- core/tests/unit/wasm/package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/tests/unit/wasm/package-lock.json b/core/tests/unit/wasm/package-lock.json index f2c3485da56..4b185e0119b 100644 --- a/core/tests/unit/wasm/package-lock.json +++ b/core/tests/unit/wasm/package-lock.json @@ -538,9 +538,10 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { From 199bd9ae075e8acc9be5d45e4ff4027e869d2069 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:52:24 +0000 Subject: [PATCH 39/72] chore(deps-dev): bump js-yaml in /core/tests/unit/wasm Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.2.0. - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/commits) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.2.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- core/tests/unit/wasm/package-lock.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/core/tests/unit/wasm/package-lock.json b/core/tests/unit/wasm/package-lock.json index f2c3485da56..19d5fd4a481 100644 --- a/core/tests/unit/wasm/package-lock.json +++ b/core/tests/unit/wasm/package-lock.json @@ -710,10 +710,20 @@ } }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" From 0cc44cf63e64018cc01acb13658294eb8dc265da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:52:33 +0000 Subject: [PATCH 40/72] chore(deps-dev): bump picomatch in /core/tests/unit/wasm Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2) --- updated-dependencies: - dependency-name: picomatch dependency-version: 2.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- core/tests/unit/wasm/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tests/unit/wasm/package-lock.json b/core/tests/unit/wasm/package-lock.json index f2c3485da56..0ba34734131 100644 --- a/core/tests/unit/wasm/package-lock.json +++ b/core/tests/unit/wasm/package-lock.json @@ -932,9 +932,9 @@ } }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { From 9404a7457f9ac3a337708dc80b528c3640647b23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:53:38 +0000 Subject: [PATCH 41/72] chore(deps): bump qs and express Bumps [qs](https://github.com/ljharb/qs) to 6.15.2 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `qs` from 6.13.0 to 6.15.2 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.15.2) Updates `express` from 4.22.1 to 4.22.2 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/v4.22.2/History.md) - [Commits](https://github.com/expressjs/express/compare/v4.22.1...v4.22.2) --- updated-dependencies: - dependency-name: express dependency-version: 4.22.2 dependency-type: direct:production - dependency-name: qs dependency-version: 6.15.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- developer/src/server/package.json | 2 +- package-lock.json | 147 +++++++++++++++++------------- web/package.json | 2 +- 3 files changed, 86 insertions(+), 65 deletions(-) diff --git a/developer/src/server/package.json b/developer/src/server/package.json index 662be2add6c..37c92b7f518 100644 --- a/developer/src/server/package.json +++ b/developer/src/server/package.json @@ -13,7 +13,7 @@ "@ngrok/ngrok": "^1.7.0", "@sentry/node": "^7.57.0", "chalk": "^4.1.2", - "express": "^4.22.1", + "express": "^4.22.2", "multer": "^2.1.1", "open": "^8.4.0", "restructure": "^3.0.1", diff --git a/package-lock.json b/package-lock.json index dd4169fb4e9..7871a018869 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1144,7 +1144,7 @@ "@ngrok/ngrok": "^1.7.0", "@sentry/node": "^7.57.0", "chalk": "^4.1.2", - "express": "^4.22.1", + "express": "^4.22.2", "multer": "^2.1.1", "open": "^8.4.0", "restructure": "^3.0.1", @@ -5191,22 +5191,23 @@ "license": "Apache-2.0" }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -5217,6 +5218,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -5225,14 +5227,60 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/brace-expansion": { "version": "1.1.12", @@ -5490,6 +5538,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -6262,6 +6311,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -7722,14 +7772,14 @@ "dev": true }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -7748,7 +7798,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -7798,40 +7848,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/express/node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/express/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -8557,6 +8573,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -11676,12 +11693,12 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -11736,6 +11753,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -12165,6 +12183,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -12232,14 +12251,16 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -14149,7 +14170,7 @@ "@sentry/cli": "^2.31.0", "@zip.js/zip.js": "^2.7.32", "c8": "^7.12.0", - "express": "^4.19.2", + "express": "^4.22.2", "jsdom": "^23.0.1", "promise-status-async": "^1.2.10", "tsx": "^4.19.0" diff --git a/web/package.json b/web/package.json index fcc6d767907..5ec0135add8 100644 --- a/web/package.json +++ b/web/package.json @@ -107,7 +107,7 @@ "@sentry/cli": "^2.31.0", "@zip.js/zip.js": "^2.7.32", "c8": "^7.12.0", - "express": "^4.19.2", + "express": "^4.22.2", "jsdom": "^23.0.1", "promise-status-async": "^1.2.10", "tsx": "^4.19.0" From a7b9d5da5aa0e290b7aba6b88ef6770fb29c4214 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 2 Jun 2026 18:24:16 +0200 Subject: [PATCH 42/72] fix(web): fix displaying of keyboard menu The recent partial merge of epic/web-core missed some code that causes the keyboard menu to work. This change re-applies those changes. Test-bot: skip --- web/src/app/browser/src/keymanEngine.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index bf5543e2cd8..76f02bc941a 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -400,7 +400,7 @@ export class KeymanEngine extends KeymanEngineBase Date: Tue, 2 Jun 2026 13:07:57 -0500 Subject: [PATCH 43/72] auto: increment master version to 19.0.242 Test-bot: skip Build-bot: skip --- HISTORY.md | 6 ++++++ VERSION.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 462bb9a48df..9615026796a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # Keyman Version History +## 19.0.241 alpha 2026-06-02 + +* chore(web): web-core preflight - strip core references (#16040) +* docs: add note on how to use composer on dockerized websites (#16029) +* fix(web): fix race displaying active keyboard in menu (#16042) + ## 19.0.240 alpha 2026-05-28 * chore(web): add filename to link text of manual web tests (#16019) diff --git a/VERSION.md b/VERSION.md index e14cb86f3e7..bfdab4c783b 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.241 \ No newline at end of file +19.0.242 \ No newline at end of file From ef9c0331d0b75ae5c037d3ad0b23173a25bf63fd Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 1 Jun 2026 19:23:19 +0200 Subject: [PATCH 44/72] chore(web): remove unnecessary .editorconfig file Build-bot: skip Test-bot: skip --- web/src/.editorconfig | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 web/src/.editorconfig diff --git a/web/src/.editorconfig b/web/src/.editorconfig deleted file mode 100644 index 34f03b7b15b..00000000000 --- a/web/src/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -# top-most EditorConfig file -root = true - -; applies to all files -[*] -indent_style = space - -[*.{java, js, ts, sh, css, html, xml}] -indent_size = 2 - From 43398df2432cd35024378a17b101182132815216 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 2 Jun 2026 15:41:58 +0200 Subject: [PATCH 45/72] refactor(web): extract function, rename variables Test-bot: skip --- web/src/app/browser/src/keymanEngine.ts | 27 +++---- web/src/app/ui/kmwuitoggle.ts | 100 +++++++++++++----------- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 76f02bc941a..16afdee6c90 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -275,35 +275,34 @@ export class KeymanEngine extends KeymanEngineBase { - // Ensure that the ui.controller is visible if help is displayed - this.controller.style.display = 'block'; - this.oskButton._setSelected(true); + // Ensure that the ui.controller is visible if help is displayed + this.controller.style.display = 'block'; + this.oskButton._setSelected(true); - return oskPosition; - }); + return oskPosition; + }); osk.addEventListener('hide', (byUser) => { if(byUser['HiddenByUser']) { @@ -565,24 +565,24 @@ if(!keymanweb) { /** * Function selecKbd * Scope Private - * @param {number} _kbd + * @param {number} kbdIndex * Description Select a keyboard from the drop down menu **/ - private async selectKbd(_kbd: number): Promise { - let _name,_lgCode; - if(_kbd < 0) { - _name = ''; - _lgCode=''; + private async selectKbd(kbdIndex: number): Promise { + let name, languageCode; + if (kbdIndex < 0) { + name = ''; + languageCode = ''; } else { - _name = this.keyboards[_kbd]._InternalName; - _lgCode = this.keyboards[_kbd]._LanguageCode; + name = this.keyboards[kbdIndex]._InternalName; + languageCode = this.keyboards[kbdIndex]._LanguageCode; } - await keymanweb.setActiveKeyboard(_name,_lgCode); + await keymanweb.setActiveKeyboard(name, languageCode); keymanweb.focusLastActiveElement(); - this.kbdButton._setSelected(_name != ''); - if(_kbd >= 0) { - this.lastActiveKeyboard = _kbd; + this.kbdButton._setSelected(name != ''); + if (kbdIndex >= 0) { + this.lastActiveKeyboard = kbdIndex; } return false; @@ -697,6 +697,22 @@ if(!keymanweb) { `; } + private createMenuItem(index: number, id: string, name: string, isSelected: boolean): { li: HTMLLIElement, a: HTMLAnchorElement } { + const li = util.createElement('li'); + const a = util.createElement('a'); + a.innerHTML = name; + a.href = "#"; + a.onclick = ((x) => { + return () => this.selectKbd(x); + })(index); + a.id = id; + if (isSelected) { + a.className = 'selected'; + } + li.appendChild(a); + return { li, a }; + } + /** * Function createMenu * Scope Private @@ -711,38 +727,29 @@ if(!keymanweb) { this.keyboardMenu.innerHTML = ''; // I2403 - Allow toggle design to be loaded twice } - const _li = util.createElement('li'); - const _a = util.createElement('a'); - _a.innerHTML='(System keyboard)'; - _a.href="#"; - _a.onclick = () => { - return this.selectKbd(-1); - }; - _a.id='KMWSel_$'; - _a.className='selected'; - _li.appendChild(_a); - - this.selectedMenuItem=_a; - this.keyboardMenu.appendChild(_li); + // Add the default (system) keyboard as the first entry in the menu + const { li, a } = this.createMenuItem(-1, 'KMWSel_$', '(System keyboard)', true); + this.selectedMenuItem = a; + this.keyboardMenu.appendChild(li); - const _kbds=keymanweb.getKeyboards(), _added: Record = {}; + // Add loaded keyboards to the menu this.keyboards = []; - for(let _kbd = 0; _kbd < _kbds.length; _kbd++) { - const _li1=util.createElement('li'); - const _a1=util.createElement('a'); - _a1.innerHTML=_kbds[_kbd].LanguageName + ' - ' + _kbds[_kbd].Name; - if(!_added[_kbds[_kbd].InternalName]) _added[_kbds[_kbd].InternalName]=0; - _added[_kbds[_kbd].InternalName]++; - - const _n=_added[_kbds[_kbd].InternalName]; - this.keyboards.push({_InternalName: _kbds[_kbd].InternalName, _LanguageCode:_kbds[_kbd].LanguageCode, _Index: _n}); + const keyboards = keymanweb.getKeyboards(); + const added: Record = {}; + for (let kbdIndex = 0; kbdIndex < keyboards.length; kbdIndex++) { + if (!added[keyboards[kbdIndex].InternalName]) { + added[keyboards[kbdIndex].InternalName] = 0; + } + added[keyboards[kbdIndex].InternalName]++; - _a1.href="#"; - _a1.onclick = ((x) => { return () => this.selectKbd(x); })(this.keyboards.length-1); - _a1.id='KMWSel_'+_kbds[_kbd].InternalName+'$'+_n; + const kbdInstanceCount = added[keyboards[kbdIndex].InternalName]; + this.keyboards.push({ _InternalName: keyboards[kbdIndex].InternalName, _LanguageCode: keyboards[kbdIndex].LanguageCode, _Index: kbdInstanceCount }); - _li1.appendChild(_a1); - this.keyboardMenu.appendChild(_li1); + const { li } = this.createMenuItem(this.keyboards.length - 1, + `KMWSel_${keyboards[kbdIndex].InternalName}\$${kbdInstanceCount}`, + `${keyboards[kbdIndex].LanguageName} - ${keyboards[kbdIndex].Name}`, + false); + this.keyboardMenu.appendChild(li); } //if(!ui.initialized) // I2403 - Allow toggle design to be loaded twice @@ -750,7 +757,6 @@ if(!keymanweb) { this.kbdButton.getElem().appendChild(this.keyboardMenu); } }; - } @@ -805,4 +811,4 @@ if(!keymanweb) { ui.initialize(); } catch(ex){} -} \ No newline at end of file +} From 9ced04684a2c2f8b23013e670fde7aa29f5d7101 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 3 Jun 2026 18:54:26 +0200 Subject: [PATCH 46/72] chore(web): improve the KeymanWeb integration documentation - replace use of `kmw` with `keyman` - unify pattern to load keyboards: do it in `keyman.init().then()` instead of in the document `onload` Part-of: keymanapp/keyman.com#449 Test-bot: skip --- .../engine/guide/examples/__auto-control.html | 25 ++++---- .../guide/examples/__control-by-control.html | 40 ++++++------- .../guide/examples/__first-example.html | 15 +++-- .../guide/examples/__full-manual-control.html | 44 ++++++-------- .../guide/examples/__manual-control.html | 20 +++---- .../guide/examples/automatic-control.md | 20 +++---- .../guide/examples/control-by-control.md | 42 ++++++-------- .../guide/examples/full-manual-control.md | 40 ++++++------- .../guide/examples/js/unified_loader.js | 30 +++++----- .../engine/guide/examples/manual-control.md | 13 +---- web/src/samples/complex/root/pages/index.html | 5 +- web/src/samples/samplehdr.js | 12 ++-- web/src/samples/simplest/index.html | 3 +- web/src/samples/subfolder_toggle/index.html | 3 +- web/src/samples/subfolder_toolbar/index.html | 3 +- .../test/manual/web/attachment-api/index.html | 7 ++- .../manual/web/attachment-api/utilities.js | 27 +++++---- .../test/manual/web/basic-iframe/index.html | 7 ++- .../web/build-visual-keyboard/index.html | 6 +- web/src/test/manual/web/chirality/index.html | 7 ++- .../test/manual/web/chirality/utilities.js | 12 ++-- web/src/test/manual/web/ckeditor/index.html | 7 ++- web/src/test/manual/web/ckeditor/inline.html | 7 ++- web/src/test/manual/web/ckeditor/utilities.js | 10 ++-- web/src/test/manual/web/commonHeader.js | 8 +-- .../test/manual/web/default-subkey/index.html | 5 +- .../test/manual/web/desktop-ui/button.html | 3 +- web/src/test/manual/web/desktop-ui/float.html | 3 +- .../test/manual/web/desktop-ui/toggle.html | 5 +- .../test/manual/web/desktop-ui/toolbar.html | 7 ++- web/src/test/manual/web/empty-row/index.html | 8 +-- .../manual/web/init-race-10743/index.html | 3 +- web/src/test/manual/web/inline-osk/index.html | 3 +- web/src/test/manual/web/issue005/header.js | 40 ++++++------- web/src/test/manual/web/issue005/index.html | 16 ++--- web/src/test/manual/web/issue103/index.html | 9 +-- web/src/test/manual/web/issue103/samplehdr.js | 58 +++++++++---------- web/src/test/manual/web/issue115/index.html | 5 +- web/src/test/manual/web/issue116/index.html | 8 +-- web/src/test/manual/web/issue11785/index.html | 5 +- .../manual/web/issue11785/load-keyboards.js | 8 +-- web/src/test/manual/web/issue1332/header.js | 12 ++-- web/src/test/manual/web/issue1332/index.html | 7 ++- web/src/test/manual/web/issue160/index.html | 10 ++-- web/src/test/manual/web/issue266/index.html | 10 ++-- web/src/test/manual/web/issue271/header.js | 4 +- web/src/test/manual/web/issue271/index.html | 7 ++- web/src/test/manual/web/issue29/index.html | 7 ++- web/src/test/manual/web/issue3701/index.html | 7 +-- web/src/test/manual/web/issue382/index.html | 22 +++---- web/src/test/manual/web/issue53/index.html | 7 ++- web/src/test/manual/web/issue53/issue53.js | 16 +++-- web/src/test/manual/web/issue6005/index.html | 31 +++++----- web/src/test/manual/web/issue62/index.html | 7 ++- web/src/test/manual/web/issue63/index.html | 7 ++- .../issue917-context-and-notany/index.html | 5 +- web/src/test/manual/web/issue920/index.html | 15 ++--- web/src/test/manual/web/issue9469/index.html | 9 ++- .../manual/web/keyboard-errors/errorhdr.js | 10 ++-- .../manual/web/keyboard-errors/index.html | 7 ++- web/src/test/manual/web/mnemonic/index.html | 25 ++++---- .../manual/web/options-with-save/index.html | 25 ++++---- .../manual/web/osk-event-buttons/index.html | 3 +- .../test/manual/web/osk-movement/index.html | 5 +- web/src/test/manual/web/platform/index.html | 11 ++-- web/src/test/manual/web/platform/utilities.js | 8 +-- web/src/test/manual/web/pr10506/header.js | 5 +- web/src/test/manual/web/pr10506/index.html | 7 ++- .../manual/web/prediction-mtnt/index.html | 15 +++-- .../test/manual/web/prediction-ui/index.html | 13 ++--- .../test/manual/web/promise-api/index.html | 6 +- .../test/manual/web/promise-api/promisehdr.js | 14 ++--- .../manual/web/rotation-events/index.html | 29 +++++----- .../manual/web/sentry-integration/errorhdr.js | 28 +++++---- .../manual/web/sentry-integration/index.html | 7 ++- .../manual/web/test-updateLayer/index.html | 7 ++- .../manual/web/test-updateLayer/utilities.js | 8 +-- .../web/text_selection_tests_9073/index.html | 30 +++++----- .../test/manual/web/unminified - manual.html | 5 +- web/src/test/manual/web/unminified.html | 3 +- .../tools/testing/bulk_rendering/index.html | 9 ++- .../bulk_rendering/local-renderer.html | 5 +- .../testing/bulk_rendering/renderer_core.ts | 2 +- web/src/tools/testing/recorder/index.html | 4 +- 84 files changed, 491 insertions(+), 582 deletions(-) diff --git a/web/docs/engine/guide/examples/__auto-control.html b/web/docs/engine/guide/examples/__auto-control.html index 050e03a9f03..efe5467bfd5 100644 --- a/web/docs/engine/guide/examples/__auto-control.html +++ b/web/docs/engine/guide/examples/__auto-control.html @@ -1,20 +1,17 @@ + - - @@ -28,4 +25,4 @@

Automatic Mode Example

Back to Document - \ No newline at end of file + diff --git a/web/docs/engine/guide/examples/__control-by-control.html b/web/docs/engine/guide/examples/__control-by-control.html index 93a481a75f6..07736a05db2 100644 --- a/web/docs/engine/guide/examples/__control-by-control.html +++ b/web/docs/engine/guide/examples/__control-by-control.html @@ -1,3 +1,4 @@ + @@ -8,28 +9,23 @@ diff --git a/web/docs/engine/guide/examples/__first-example.html b/web/docs/engine/guide/examples/__first-example.html index feb96f4daa0..ece6a9b9eaa 100644 --- a/web/docs/engine/guide/examples/__first-example.html +++ b/web/docs/engine/guide/examples/__first-example.html @@ -6,15 +6,14 @@ - @@ -23,4 +22,4 @@

First Example

Back to Document - \ No newline at end of file + diff --git a/web/docs/engine/guide/examples/__full-manual-control.html b/web/docs/engine/guide/examples/__full-manual-control.html index 5a70c7183ff..6313c135184 100644 --- a/web/docs/engine/guide/examples/__full-manual-control.html +++ b/web/docs/engine/guide/examples/__full-manual-control.html @@ -1,45 +1,39 @@ + - diff --git a/web/docs/engine/guide/examples/__manual-control.html b/web/docs/engine/guide/examples/__manual-control.html index 4680400e4df..209c8fe1763 100644 --- a/web/docs/engine/guide/examples/__manual-control.html +++ b/web/docs/engine/guide/examples/__manual-control.html @@ -1,29 +1,23 @@ + - @@ -38,4 +32,4 @@

Manual Mode Example

Back to Document - \ No newline at end of file + diff --git a/web/docs/engine/guide/examples/automatic-control.md b/web/docs/engine/guide/examples/automatic-control.md index c804556e180..14b703d35aa 100644 --- a/web/docs/engine/guide/examples/automatic-control.md +++ b/web/docs/engine/guide/examples/automatic-control.md @@ -13,17 +13,15 @@ In this example, we use only the LaoKey keyboard. Please click [this link](./__a diff --git a/web/docs/engine/guide/examples/control-by-control.md b/web/docs/engine/guide/examples/control-by-control.md index 974338ef1fe..5f57a5a7785 100644 --- a/web/docs/engine/guide/examples/control-by-control.md +++ b/web/docs/engine/guide/examples/control-by-control.md @@ -10,28 +10,23 @@ Include the following script in the HEAD of your page: ```js ``` @@ -48,8 +43,9 @@ Also include the following HTML code: - + ``` + --- **Note:** In this example we disabled the first element (`document.f.address`) by API call. A later API call can re-enable KeymanWeb for this control should it fit the page's design. Alternatively, this can be done by instead adding the class `'kmw-disabled'` to the control. This will permanently block KeymanWeb from handling its input. diff --git a/web/docs/engine/guide/examples/full-manual-control.md b/web/docs/engine/guide/examples/full-manual-control.md index e9e8d2908a8..b03d4933575 100644 --- a/web/docs/engine/guide/examples/full-manual-control.md +++ b/web/docs/engine/guide/examples/full-manual-control.md @@ -12,27 +12,23 @@ Include the following script in the HEAD of your page: @@ -56,7 +57,7 @@ - +

KeymanWeb Sample Page - Attachment API Testing

This page is designed to stress-test the new attachment/enablement API model and its functions.

You are currently testing under the attachment mode. Two other modes are available: diff --git a/web/src/test/manual/web/attachment-api/utilities.js b/web/src/test/manual/web/attachment-api/utilities.js index 5793c12af6f..f6c404932ea 100644 --- a/web/src/test/manual/web/attachment-api/utilities.js +++ b/web/src/test/manual/web/attachment-api/utilities.js @@ -1,7 +1,6 @@ // Page-global variable definitions. { var inputCounter = 0; -var kmw = keyman; } function generateDiagnosticDiv(elem) { @@ -23,8 +22,8 @@ function generateDiagnosticDiv(elem) { attachBtn.type = 'button'; attachBtn.id = 'attach_' + elemId; attachBtn.onclick = function() { - kmw.attachToControl(document.getElementById(elemId)); - var attached = kmw.isAttached(elem); + keyman.attachToControl(document.getElementById(elemId)); + var attached = keyman.isAttached(elem); document.getElementById('attachment_' + elemId).textContent = " Currently " + (attached ? "attached." : "detached."); const indepLabel = document.getElementById("independence_" + elemId); // does not exist for iframes @@ -40,8 +39,8 @@ function generateDiagnosticDiv(elem) { detachBtn.type = 'button'; detachBtn.id = 'detach_' + elemId; detachBtn.onclick = function() { - kmw.detachFromControl(document.getElementById(elemId)); - var attached = kmw.isAttached(elem); + keyman.detachFromControl(document.getElementById(elemId)); + var attached = keyman.isAttached(elem); document.getElementById('attachment_' + elemId).textContent = " Currently " + (attached ? "attached." : "detached."); const indepLabel = document.getElementById("independence_" + elemId); // does not exist for iframes @@ -58,7 +57,7 @@ function generateDiagnosticDiv(elem) { var attachLabel = document.createElement("a"); attachLabel.id = 'attachment_' + elemId; window.setTimeout(function() { - attachLabel.textContent = " Currently " + (kmw.isAttached(elem) ? "attached." : "detached."); + attachLabel.textContent = " Currently " + (keyman.isAttached(elem) ? "attached." : "detached."); }, attachTimer); div.appendChild(attachLabel); @@ -69,7 +68,7 @@ function generateDiagnosticDiv(elem) { enableBtn.type = 'button'; enableBtn.id = 'enable_' + elemId; enableBtn.onclick = function() { - kmw.enableControl(document.getElementById(elemId)); + keyman.enableControl(document.getElementById(elemId)); }; enableBtn.value = 'Enable'; div.appendChild(enableBtn); @@ -79,7 +78,7 @@ function generateDiagnosticDiv(elem) { disableBtn.type = 'button'; disableBtn.id = 'disable_' + elemId; disableBtn.onclick = function() { - kmw.disableControl(document.getElementById(elemId)); + keyman.disableControl(document.getElementById(elemId)); }; disableBtn.value = 'Disable'; div.appendChild(disableBtn); @@ -107,9 +106,9 @@ function generateDiagnosticDiv(elem) { setBtn.type = 'button'; setBtn.id = 'set_' + elemId; setBtn.onclick = function() { - kmw.setKeyboardForControl(document.getElementById(elemId), 'dzongkha', 'dz'); + keyman.setKeyboardForControl(document.getElementById(elemId), 'dzongkha', 'dz'); - if(kmw.isAttached(elem)) { + if(keyman.isAttached(elem)) { kbdLabel.textContent = " Using independently-tracked keyboard."; } }; @@ -121,9 +120,9 @@ function generateDiagnosticDiv(elem) { clearBtn.type = 'button'; clearBtn.id = 'clear_' + elemId; clearBtn.onclick = function() { - kmw.setKeyboardForControl(document.getElementById(elemId), null, null); + keyman.setKeyboardForControl(document.getElementById(elemId), null, null); - if(kmw.isAttached(elem)) { + if(keyman.isAttached(elem)) { kbdLabel.textContent = " Using globally-selected keyboard."; } }; @@ -133,8 +132,8 @@ function generateDiagnosticDiv(elem) { var kbdLabel = document.createElement("a"); kbdLabel.id = 'independence_' + elemId; window.setTimeout(function () { - if(kmw.isAttached(elem)) { - var attached = kmw.isAttached(elem); + if(keyman.isAttached(elem)) { + var attached = keyman.isAttached(elem); kbdLabel.textContent = attached ? " Using globally-selected keyboard." : ''; } }, 1); diff --git a/web/src/test/manual/web/basic-iframe/index.html b/web/src/test/manual/web/basic-iframe/index.html index 8a935f2cc65..56441455ddf 100644 --- a/web/src/test/manual/web/basic-iframe/index.html +++ b/web/src/test/manual/web/basic-iframe/index.html @@ -36,8 +36,9 @@ @@ -46,7 +47,7 @@ - +

KeymanWeb: Test IFrame connectivity

diff --git a/web/src/test/manual/web/build-visual-keyboard/index.html b/web/src/test/manual/web/build-visual-keyboard/index.html index 2845f3db86c..54a47172d0b 100644 --- a/web/src/test/manual/web/build-visual-keyboard/index.html +++ b/web/src/test/manual/web/build-visual-keyboard/index.html @@ -37,8 +37,6 @@ @@ -51,7 +52,7 @@ - +

KeymanWeb Sample Page - Chirality Testing

This page is designed to stress-test the new support for chiral modifiers and state keys.

Be sure to reference the developer console for additional feedback.

diff --git a/web/src/test/manual/web/chirality/utilities.js b/web/src/test/manual/web/chirality/utilities.js index 16c9caaf026..5c5fe5c805d 100644 --- a/web/src/test/manual/web/chirality/utilities.js +++ b/web/src/test/manual/web/chirality/utilities.js @@ -1,9 +1,7 @@ -function loadKeyboards() -{ - var kmw=keyman; - +async function loadKeyboards() +{ // A testing keyboard handwritten with chirality information. - kmw.addKeyboards({id:'chirality',name:'Chirality Testing', + await keyman.addKeyboards({id:'chirality',name:'Chirality Testing', languages:{ id:'en',name:'English',region:'North America' }, @@ -12,8 +10,8 @@ function loadKeyboards() // A testing keyboard using 10.0 format and the KLS layout specifier // without the 'shift' layer properly defined. - // Add a fully-specified, locally-sourced, keyboard with custom font - kmw.addKeyboards({id:'halfDefined',name:'Undefined Shift Layer Keyboard', + // Add a fully-specified, locally-sourced, keyboard with custom font + await keyman.addKeyboards({id:'halfDefined',name:'Undefined Shift Layer Keyboard', languages:{ id:'en',name:'English',region:'North America' }, diff --git a/web/src/test/manual/web/ckeditor/index.html b/web/src/test/manual/web/ckeditor/index.html index 950f8bcb9b0..25dfc7e9e8e 100644 --- a/web/src/test/manual/web/ckeditor/index.html +++ b/web/src/test/manual/web/ckeditor/index.html @@ -30,9 +30,10 @@ @@ -44,7 +45,7 @@ - +

KeymanWeb Sample Page - Test CKEditor Integration

Test CKEditor Integration

diff --git a/web/src/test/manual/web/ckeditor/inline.html b/web/src/test/manual/web/ckeditor/inline.html index 463ebf21388..f54d98e0ff0 100644 --- a/web/src/test/manual/web/ckeditor/inline.html +++ b/web/src/test/manual/web/ckeditor/inline.html @@ -30,9 +30,10 @@ @@ -44,7 +45,7 @@ - +

KeymanWeb Sample Page - Test CKEditor Integration

Test CKEditor Integration

diff --git a/web/src/test/manual/web/ckeditor/utilities.js b/web/src/test/manual/web/ckeditor/utilities.js index 2ca0fd324c3..8c281e22239 100644 --- a/web/src/test/manual/web/ckeditor/utilities.js +++ b/web/src/test/manual/web/ckeditor/utilities.js @@ -1,15 +1,13 @@ -function loadKeyboards() +async function loadKeyboards() { - var kmw=keyman; - // Add more keyboards to the language menu, by keyboard name, // keyboard name and language code, or just the BCP-47 language code. // We use a different loading pattern here than in the samples version to provide a slightly different set of test cases. - kmw.addKeyboards('french','@he'); - kmw.addKeyboards({id:'sil_euro_latin', name:'SIL EuroLatin', languages: [{id:'no'}, {id:'sv'}]}); // Loads from partial stub instead of the compact string. + await keyman.addKeyboards('french', '@he', + { id: 'sil_euro_latin', name: 'SIL EuroLatin', languages: [{ id: 'no' }, { id: 'sv' }] }); // Loads from partial stub instead of the compact string. // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - kmw.addKeyboardsForLanguage('Dzongkha'); + await keyman.addKeyboardsForLanguage('Dzongkha'); } \ No newline at end of file diff --git a/web/src/test/manual/web/commonHeader.js b/web/src/test/manual/web/commonHeader.js index 74cb22e1cdb..53f6611b266 100644 --- a/web/src/test/manual/web/commonHeader.js +++ b/web/src/test/manual/web/commonHeader.js @@ -74,8 +74,6 @@ function loadKeyboards(nestLevel) { - var kmw=keyman; - var base_prefix = '../'; var prefix = './'; // The default - when prefix == 0. @@ -133,8 +131,8 @@ // The following two optional calls should be delayed until language menus are fully loaded: // (a) a specific mapped input element input is focused, to ensure that the OSK appears // (b) a specific keyboard is loaded, rather than the keyboard last used. - //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500); - //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000); + //window.setTimeout(function(){keyman.setActiveElement('ta1',true);},2500); + //window.setTimeout(function(){keyman.setActiveKeyboard('Keyboard_french','fr');},3000); // Note that locally specified keyboards will be listed before keyboards // requested from the remote server by user interfaces that do not order @@ -144,7 +142,7 @@ // Script to allow a user to add any keyboard to the keyboard menu function addKeyboard(n) { - var sKbd,kmw=keyman; + var sKbd; switch(n) { case 1: diff --git a/web/src/test/manual/web/default-subkey/index.html b/web/src/test/manual/web/default-subkey/index.html index 728355c43b4..6ce3bee562b 100644 --- a/web/src/test/manual/web/default-subkey/index.html +++ b/web/src/test/manual/web/default-subkey/index.html @@ -36,11 +36,10 @@ - +

KeymanWeb Testing Page - Desktop UI module Testing

This page is designed to facilitate testing of various aspects of the Toolbar UI for KeymanWeb. Originally developed to help address issue #1275. diff --git a/web/src/test/manual/web/empty-row/index.html b/web/src/test/manual/web/empty-row/index.html index 02953493e91..ee6da66cca9 100644 --- a/web/src/test/manual/web/empty-row/index.html +++ b/web/src/test/manual/web/empty-row/index.html @@ -36,18 +36,16 @@ - - + + @@ -47,7 +49,7 @@ - +

KeymanWeb: 'Test case' for proper implementation of the removeKeyboards API function

diff --git a/web/src/test/manual/web/issue103/index.html b/web/src/test/manual/web/issue103/index.html index 35b006e3472..e2420715119 100644 --- a/web/src/test/manual/web/issue103/index.html +++ b/web/src/test/manual/web/issue103/index.html @@ -36,10 +36,11 @@ @@ -48,7 +49,7 @@ - +

KeymanWeb Sample Page - Keyboard Registration Check

This page is designed to test KeymanWeb's behavior when being told to load a keyboard multiple times, both via diff --git a/web/src/test/manual/web/issue103/samplehdr.js b/web/src/test/manual/web/issue103/samplehdr.js index 36b08861c18..51a38e4effe 100644 --- a/web/src/test/manual/web/issue103/samplehdr.js +++ b/web/src/test/manual/web/issue103/samplehdr.js @@ -1,70 +1,66 @@ // Modified version of commonHeader - contains repeated requests for the same language. - function loadKeyboards() - { - var kmw=keyman; - + function loadKeyboards() { // The first keyboard added will be the default keyboard for touch devices. // For faster loading, it may be best for the default keyboard to be // locally sourced. - kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, + keyman.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, filename:'../us-1.0.js'}); - + // Add more keyboards to the language menu, by keyboard name, // keyboard name and language code, or just the BCP-47 language code. - kmw.addKeyboards('french','european2@sv','european2@sv','european2@sv','european2@sv','european2@sv','european2@sv','european2@no','@he'); - kmw.addKeyboards('@he'); - kmw.addKeyboards('@he'); - + keyman.addKeyboards('french','european2@sv','european2@sv','european2@sv','european2@sv','european2@sv','european2@sv','european2@no','@he'); + keyman.addKeyboards('@he'); + keyman.addKeyboards('@he'); + // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - kmw.addKeyboardsForLanguage('Dzongkha'); - - // Add a fully-specified, locally-sourced, keyboard with custom font - kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', + keyman.addKeyboardsForLanguage('Dzongkha'); + + // Add a fully-specified, locally-sourced, keyboard with custom font + keyman.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', languages:{ id:'lo',name:'Lao',region:'Asia' }, filename:'../lao_2008_basic.js' - }); + }); // The following two optional calls should be delayed until language menus are fully loaded: // (a) a specific mapped input element input is focused, to ensure that the OSK appears - // (b) a specific keyboard is loaded, rather than the keyboard last used. - //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500); - //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000); - - // Note that locally specified keyboards will be listed before keyboards + // (b) a specific keyboard is loaded, rather than the keyboard last used. + //window.setTimeout(function(){keyman.setActiveElement('ta1',true);},2500); + //window.setTimeout(function(){keyman.setActiveKeyboard('Keyboard_french','fr');},3000); + + // Note that locally specified keyboards will be listed before keyboards // requested from the remote server by user interfaces that do not order // keyboards alphabetically by language. } - - // Script to allow a user to add any keyboard to the keyboard menu + + // Script to allow a user to add any keyboard to the keyboard menu function addKeyboard(n) - { - var sKbd,kmw=keyman; + { + let sKbd; switch(n) { case 1: sKbd=document.getElementById('kbd_id1').value; - kmw.addKeyboards(sKbd); + keyman.addKeyboards(sKbd); break; case 2: sKbd=document.getElementById('kbd_id2').value.toLowerCase(); - kmw.addKeyboards('@'+sKbd); + keyman.addKeyboards('@'+sKbd); break; case 3: sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd); + keyman.addKeyboardsForLanguage(sKbd); break; } } - + // Add keyboard on Enter (as well as pressing button) - function clickOnEnter(e,id) - { + function clickOnEnter(e,id) { e = e || window.event; - if(e.keyCode == 13) addKeyboard(id); + if(e.keyCode == 13) addKeyboard(id); } diff --git a/web/src/test/manual/web/issue115/index.html b/web/src/test/manual/web/issue115/index.html index cd9afcb8fa0..f55d98d382b 100644 --- a/web/src/test/manual/web/issue115/index.html +++ b/web/src/test/manual/web/issue115/index.html @@ -36,11 +36,10 @@ diff --git a/web/src/test/manual/web/issue116/index.html b/web/src/test/manual/web/issue116/index.html index d81fb947c04..d8f64d9f749 100644 --- a/web/src/test/manual/web/issue116/index.html +++ b/web/src/test/manual/web/issue116/index.html @@ -36,12 +36,12 @@ diff --git a/web/src/test/manual/web/issue11785/index.html b/web/src/test/manual/web/issue11785/index.html index 5df453aab92..fb51a97bcf4 100644 --- a/web/src/test/manual/web/issue11785/index.html +++ b/web/src/test/manual/web/issue11785/index.html @@ -40,9 +40,8 @@ @@ -49,7 +50,7 @@ - +

KeymanWeb Sample Page - Mixed Case of TTF font files

Previously, KMW was not applying .TTF fonts on mobile devices.

See issue #1332 diff --git a/web/src/test/manual/web/issue160/index.html b/web/src/test/manual/web/issue160/index.html index 33219e7fdf7..3808341b7d9 100644 --- a/web/src/test/manual/web/issue160/index.html +++ b/web/src/test/manual/web/issue160/index.html @@ -36,12 +36,12 @@ diff --git a/web/src/test/manual/web/issue266/index.html b/web/src/test/manual/web/issue266/index.html index 56e52016d76..adf03ffe706 100644 --- a/web/src/test/manual/web/issue266/index.html +++ b/web/src/test/manual/web/issue266/index.html @@ -36,12 +36,12 @@ diff --git a/web/src/test/manual/web/issue271/header.js b/web/src/test/manual/web/issue271/header.js index b0d52effe97..6fd6bec984d 100644 --- a/web/src/test/manual/web/issue271/header.js +++ b/web/src/test/manual/web/issue271/header.js @@ -1,7 +1,5 @@ function setActiveKeyboard() { - var kmw=window['keyman'] ? keyman : tavultesoft.keymanweb; - var sKbd = document.getElementById('kbd_id4').value; var sLng = document.getElementById('lang_id4').value; - var result = kmw.setActiveKeyboard("Keyboard_" + sKbd, sLng); + var result = keyman.setActiveKeyboard("Keyboard_" + sKbd, sLng); } \ No newline at end of file diff --git a/web/src/test/manual/web/issue271/index.html b/web/src/test/manual/web/issue271/index.html index fa969195a13..0bf96247798 100644 --- a/web/src/test/manual/web/issue271/index.html +++ b/web/src/test/manual/web/issue271/index.html @@ -36,9 +36,10 @@ @@ -49,7 +50,7 @@ - +

KeymanWeb: Test page for interactions between setActiveKeyboard and the toolbar UI

This version is used to test the interactions of SetActiveKeyboard with the Toolbar UI.

diff --git a/web/src/test/manual/web/issue29/index.html b/web/src/test/manual/web/issue29/index.html index 24a226326ca..3f007c9cb4b 100644 --- a/web/src/test/manual/web/issue29/index.html +++ b/web/src/test/manual/web/issue29/index.html @@ -36,8 +36,9 @@ @@ -47,7 +48,7 @@ - +

KeymanWeb: Test desktop MutationObserver functionality

diff --git a/web/src/test/manual/web/issue3701/index.html b/web/src/test/manual/web/issue3701/index.html index c27bba6c81f..6393f5fe7c8 100644 --- a/web/src/test/manual/web/issue3701/index.html +++ b/web/src/test/manual/web/issue3701/index.html @@ -36,11 +36,10 @@ diff --git a/web/src/test/manual/web/issue53/index.html b/web/src/test/manual/web/issue53/index.html index 4c730a3f392..d58dbcfdf48 100644 --- a/web/src/test/manual/web/issue53/index.html +++ b/web/src/test/manual/web/issue53/index.html @@ -36,9 +36,10 @@ @@ -48,7 +49,7 @@ - +

KeymanWeb: Tests layering transitions for keyboards with variable layer sizes.

This test should be run from mobile devices or with Chrome's mobile device emulation settings available within Developer Mode.

diff --git a/web/src/test/manual/web/issue53/issue53.js b/web/src/test/manual/web/issue53/issue53.js index 03d11758661..4cef9371cd5 100644 --- a/web/src/test/manual/web/issue53/issue53.js +++ b/web/src/test/manual/web/issue53/issue53.js @@ -1,9 +1,7 @@ function loadKeyboards() { - var kmw=keyman; - // Add a fully-specified, locally-sourced, keyboard. - kmw.addKeyboards({id:'layered_debug_keyboard',name:'Web_Layer_Debugging', + keyman.addKeyboards({id:'layered_debug_keyboard',name:'Web_Layer_Debugging', languages:{ id:'dbg',name:'Debug',region:'North America' }, @@ -14,28 +12,28 @@ function loadKeyboards() // Script to allow a user to add any keyboard to the keyboard menu function addKeyboard(n) { - var sKbd,kmw=keyman; + var sKbd; switch(n) { case 1: sKbd=document.getElementById('kbd_id1').value; - kmw.addKeyboards(sKbd); + keyman.addKeyboards(sKbd); break; case 2: sKbd=document.getElementById('kbd_id2').value.toLowerCase(); - kmw.addKeyboards('@'+sKbd); + keyman.addKeyboards('@'+sKbd); break; case 3: sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd); + keyman.addKeyboardsForLanguage(sKbd); break; } } function removeKeyboard(n) { - var sKbd=document.getElementById('kbd_id4').value, kmw=keyman; - kmw["removeKeyboards"](sKbd); + var sKbd=document.getElementById('kbd_id4').value; + keyman.removeKeyboards(sKbd); } // Add keyboard on Enter (as well as pressing button) diff --git a/web/src/test/manual/web/issue6005/index.html b/web/src/test/manual/web/issue6005/index.html index 7326f99c7db..9410bed5640 100644 --- a/web/src/test/manual/web/issue6005/index.html +++ b/web/src/test/manual/web/issue6005/index.html @@ -40,13 +40,11 @@ const alertType = (new URLSearchParams(window.location.search)).get("useAlerts") != "false"; var alertText = alertType ? 'enabled(default)' : 'disabled'; - var kmw=window.keyman; - - kmw.init({ + keyman.init({ 'attachType': 'auto' }).then(function() { // if mobile device, activate pred text. - if(kmw.util.isTouchDevice()) { + if(keyman.util.isTouchDevice()) { var pageRef = (window.location.protocol == 'file:') ? window.location.href.substr(0, window.location.href.lastIndexOf('/')+1) : window.location.href; @@ -60,24 +58,25 @@ languages: ['en'], path: (pageRef + "prediction-mtnt/nrc.en.mtnt.model.js") }; - kmw.addModel(modelStub1); + keyman.addModel(modelStub1); var modelStub2 = {'id': 'nrc.en.mtnt', languages: ['pny-latn'], // yep. Total cheat for the sake of testing. path: (pageRef + "prediction-mtnt/nrc.en.mtnt.model.js") }; - kmw.addModel(modelStub2); + keyman.addModel(modelStub2); } + + keyman.addKeyboards('sil_euro_latin@en', + 'galaxie_hebrew_positional@he', + 'sil_cameroon_qwerty@pny-latn'); + + keyman.addKeyboards({ + id:'unmatched_final_group_model_interactions_6005', + name:'"Unmatched Vowel Rule"', + languages:{id:'en',name:'English'}, + filename:('unmatched_final_group_model_interactions_6005.js') + }); }); - kmw.addKeyboards('sil_euro_latin@en', - 'galaxie_hebrew_positional@he', - 'sil_cameroon_qwerty@pny-latn'); - - kmw.addKeyboards({ - id:'unmatched_final_group_model_interactions_6005', - name:'"Unmatched Vowel Rule"', - languages:{id:'en',name:'English'}, - filename:('unmatched_final_group_model_interactions_6005.js') - }); diff --git a/web/src/test/manual/web/issue62/index.html b/web/src/test/manual/web/issue62/index.html index 23ac9889df3..c647b03e88a 100644 --- a/web/src/test/manual/web/issue62/index.html +++ b/web/src/test/manual/web/issue62/index.html @@ -36,9 +36,10 @@ @@ -49,7 +50,7 @@ - +

KeymanWeb: Light test for touch-based MutationObserver functionality

diff --git a/web/src/test/manual/web/issue63/index.html b/web/src/test/manual/web/issue63/index.html index 55cee63c979..acb2cdd40f1 100644 --- a/web/src/test/manual/web/issue63/index.html +++ b/web/src/test/manual/web/issue63/index.html @@ -36,9 +36,10 @@ @@ -49,7 +50,7 @@ - +

KeymanWeb: Test/stress-test touch-based MutationObserver functionality

diff --git a/web/src/test/manual/web/issue917-context-and-notany/index.html b/web/src/test/manual/web/issue917-context-and-notany/index.html index 251edb9142a..f7941b8a0d9 100644 --- a/web/src/test/manual/web/issue917-context-and-notany/index.html +++ b/web/src/test/manual/web/issue917-context-and-notany/index.html @@ -36,11 +36,10 @@ diff --git a/web/src/test/manual/web/issue920/index.html b/web/src/test/manual/web/issue920/index.html index b20f536906f..03265f30bf3 100644 --- a/web/src/test/manual/web/issue920/index.html +++ b/web/src/test/manual/web/issue920/index.html @@ -36,15 +36,10 @@ - +

KeymanWeb Sample Page - deadkeys and multiple groups Testing

See issue #920 for details on what this test is all about. Both ;e and .e should produce ə but ;e uses a deadkey and fails.

diff --git a/web/src/test/manual/web/issue9469/index.html b/web/src/test/manual/web/issue9469/index.html index 78c3a15f95e..5c7e33daa34 100644 --- a/web/src/test/manual/web/issue9469/index.html +++ b/web/src/test/manual/web/issue9469/index.html @@ -36,11 +36,10 @@ @@ -48,7 +49,7 @@ - +

KeymanWeb Sample Page - Error Testing page

Pick one of the 'debugging' keyboards to test whether the represented error is handled correctly.

diff --git a/web/src/test/manual/web/mnemonic/index.html b/web/src/test/manual/web/mnemonic/index.html index e0b0f0de79b..a77c8622199 100644 --- a/web/src/test/manual/web/mnemonic/index.html +++ b/web/src/test/manual/web/mnemonic/index.html @@ -33,24 +33,21 @@ - - diff --git a/web/src/test/manual/web/options-with-save/index.html b/web/src/test/manual/web/options-with-save/index.html index 9f607fd11f4..2e69318bbca 100644 --- a/web/src/test/manual/web/options-with-save/index.html +++ b/web/src/test/manual/web/options-with-save/index.html @@ -33,24 +33,21 @@ - - diff --git a/web/src/test/manual/web/osk-event-buttons/index.html b/web/src/test/manual/web/osk-event-buttons/index.html index 92dafcdb4a9..95ce7c77077 100644 --- a/web/src/test/manual/web/osk-event-buttons/index.html +++ b/web/src/test/manual/web/osk-event-buttons/index.html @@ -36,8 +36,7 @@ @@ -51,7 +52,7 @@ - +

KeymanWeb Sample Page - Platform Testing

This page is designed to test the Platform statement for consistency. Refer to PR #969.
diff --git a/web/src/test/manual/web/platform/utilities.js b/web/src/test/manual/web/platform/utilities.js index 5c351e03170..bb02bdd6a10 100644 --- a/web/src/test/manual/web/platform/utilities.js +++ b/web/src/test/manual/web/platform/utilities.js @@ -1,8 +1,6 @@ -function loadKeyboards() -{ - var kmw=keyman; - - kmw.addKeyboards({id:'platformtest',name:'Platform Testing', +function loadKeyboards() +{ + keyman.addKeyboards({id:'platformtest',name:'Platform Testing', languages:{ id:'en',name:'English',region:'North America' }, diff --git a/web/src/test/manual/web/pr10506/header.js b/web/src/test/manual/web/pr10506/header.js index 0107af35256..986cc004334 100644 --- a/web/src/test/manual/web/pr10506/header.js +++ b/web/src/test/manual/web/pr10506/header.js @@ -1,11 +1,10 @@ function loadKeyboards() { - var kmw=keyman; - kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, + keyman.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, filename:('../us-1.0.js')}); // Add a fully-specified, locally-sourced, keyboard with custom font - kmw.addKeyboards({id:'dotty_keys',name:'Dotty Keys', + keyman.addKeyboards({id:'dotty_keys',name:'Dotty Keys', languages:{ id:'und',name:'Special',region:'World', font:{family:'DejaVu Dots',source:['./DejaVu-Dots.ttf']} diff --git a/web/src/test/manual/web/pr10506/index.html b/web/src/test/manual/web/pr10506/index.html index 06eae113623..84ff48f868a 100644 --- a/web/src/test/manual/web/pr10506/index.html +++ b/web/src/test/manual/web/pr10506/index.html @@ -36,9 +36,10 @@ @@ -49,7 +50,7 @@ - +

KeymanWeb Test Page - Key-cap scaling / font-load delay interactions

To run the test, simply change from the default (English) keyboard to the "Special" keyboard.

When things are working properly, the base keys on the "Special" keyboard should contain diff --git a/web/src/test/manual/web/prediction-mtnt/index.html b/web/src/test/manual/web/prediction-mtnt/index.html index 34f43f3c5b7..0e41b5b63a4 100644 --- a/web/src/test/manual/web/prediction-mtnt/index.html +++ b/web/src/test/manual/web/prediction-mtnt/index.html @@ -36,16 +36,15 @@ - +

@@ -72,8 +73,8 @@ e.value = e.value + event + ': w.iw='+window.innerWidth+' w.ih='+window.innerHeight+orientText+orient+'\n'; - if(event == 'kmw.ri' || event == 'kmw.rh' || event == 'kmw.re') { - e.value = e.value + ' osk.w=' + keyman.osk.computedWidth + ' osk.h=' + keyman.osk.computedHeight + ' kmw.vs=' + keyman.util.getViewportScale() + '\n'; + if(event == 'keyman.ri' || event == 'keyman.rh' || event == 'keyman.re') { + e.value = e.value + ' osk.w=' + keyman.osk.computedWidth + ' osk.h=' + keyman.osk.computedHeight + ' keyman.vs=' + keyman.util.getViewportScale() + '\n'; } e.scrollTop = e.scrollHeight; @@ -89,22 +90,22 @@ window.addEventListener('orientationchange', function() { doLog('orientationchange-b', false) }, false); window.addEventListener('orientationchange', function() { doLog('orientationchange-c', false) }, true); - var kmw_ri = com.keyman.RotationManager.prototype.initNewRotation; + var keyman_ri = com.keyman.RotationManager.prototype.initNewRotation; com.keyman.RotationManager.prototype.initNewRotation = function() { - doLog('kmw.ri'); - kmw_ri.call(this); + doLog('keyman.ri'); + keyman_ri.call(this); } - var kmw_rh = com.keyman.RotationManager.prototype.iOSEventHandler; + var keyman_rh = com.keyman.RotationManager.prototype.iOSEventHandler; com.keyman.RotationManager.prototype.iOSEventHandler = function() { - doLog('kmw.rh'); - kmw_rh.call(this); + doLog('keyman.rh'); + keyman_rh.call(this); } - var kmw_irh = com.keyman.RotationManager.prototype.resolve; + var keyman_irh = com.keyman.RotationManager.prototype.resolve; com.keyman.RotationManager.prototype.resolve = function() { - doLog('kmw.re'); - kmw_irh.call(this); + doLog('keyman.re'); + keyman_irh.call(this); }

Return to main index. diff --git a/web/src/test/manual/web/sentry-integration/errorhdr.js b/web/src/test/manual/web/sentry-integration/errorhdr.js index f5e4ec283fb..81ccf2abbde 100644 --- a/web/src/test/manual/web/sentry-integration/errorhdr.js +++ b/web/src/test/manual/web/sentry-integration/errorhdr.js @@ -1,27 +1,25 @@ // JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages -/* +/* This script is designed to test KeymanWeb error message handling. */ -function loadKeyboards() { - var kmw=keyman; - +function loadKeyboards() { // We start by adding a keyboard correctly. It's best to include a 'control' in our experiment. - kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, + keyman.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, filename:'../us-1.0.js'}); - + // Insert a keyboard that cannot be found. - kmw.addKeyboards({id:'lao_2008_basic',name:'wrong-filename', + keyman.addKeyboards({id:'lao_2008_basic',name:'wrong-filename', languages:{ id:'lo',name:'debugging',region:'Asia', font:{family:'LaoWeb',source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot']} }, filename:'./missing_file.js' // Intentional error - the file doesn't exist, so the @@ -51,7 +52,7 @@ - +

KeymanWeb Sample Page - Shift Testing

See issue #978 for details on what this test is all about.

diff --git a/web/src/test/manual/web/test-updateLayer/utilities.js b/web/src/test/manual/web/test-updateLayer/utilities.js index 41704fa0daf..378f44c87e2 100644 --- a/web/src/test/manual/web/test-updateLayer/utilities.js +++ b/web/src/test/manual/web/test-updateLayer/utilities.js @@ -1,8 +1,6 @@ -function loadKeyboards() -{ - var kmw=keyman; - - kmw.addKeyboards({id:'rac_balti',name:'RAC Balti', +function loadKeyboards() +{ + keyman.addKeyboards({id:'rac_balti',name:'RAC Balti', languages:{ id:'en',name:'English',region:'North America' }, diff --git a/web/src/test/manual/web/text_selection_tests_9073/index.html b/web/src/test/manual/web/text_selection_tests_9073/index.html index 20bd3cda686..7851186892f 100644 --- a/web/src/test/manual/web/text_selection_tests_9073/index.html +++ b/web/src/test/manual/web/text_selection_tests_9073/index.html @@ -20,23 +20,21 @@ + }); + diff --git a/web/src/test/manual/web/unminified - manual.html b/web/src/test/manual/web/unminified - manual.html index dbe7cdae489..5ff0c686e3d 100644 --- a/web/src/test/manual/web/unminified - manual.html +++ b/web/src/test/manual/web/unminified - manual.html @@ -36,8 +36,7 @@ diff --git a/web/src/tools/testing/bulk_rendering/renderer_core.ts b/web/src/tools/testing/bulk_rendering/renderer_core.ts index fa90283838a..597ef36950d 100644 --- a/web/src/tools/testing/bulk_rendering/renderer_core.ts +++ b/web/src/tools/testing/bulk_rendering/renderer_core.ts @@ -326,5 +326,5 @@ export class BatchRenderer { // BatchRenderer's internal state stuff is static on the class and is not exposed with // the line below. // @ts-ignore - window['kmw_renderer'] = new BatchRenderer(); + window['keyman_renderer'] = new BatchRenderer(); })(); \ No newline at end of file diff --git a/web/src/tools/testing/recorder/index.html b/web/src/tools/testing/recorder/index.html index f4a68ba7690..a9af8e20296 100644 --- a/web/src/tools/testing/recorder/index.html +++ b/web/src/tools/testing/recorder/index.html @@ -57,6 +57,8 @@ keyman.init({ attachType: 'auto', keyboards: '' + }).then(function () { + loadKeyboards(); }); document.getElementById("body").focus(); @@ -67,7 +69,7 @@ - +
From d7439e68ac7bd817de045213defadd0cd6c0b1f1 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 3 Jun 2026 19:40:01 +0200 Subject: [PATCH 47/72] chore(web): more improvements to documentation - update text in `__control-by-control.html` sample - some small updates to the text Build-bot: skip Test-bot: skip --- web/docs/engine/guide/examples/__control-by-control.html | 2 +- web/docs/engine/guide/examples/automatic-control.md | 2 -- web/docs/engine/guide/examples/control-by-control.md | 2 -- .../headless/engine/main/headless/languageProcessor.tests.js | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/web/docs/engine/guide/examples/__control-by-control.html b/web/docs/engine/guide/examples/__control-by-control.html index 07736a05db2..349faf9a394 100644 --- a/web/docs/engine/guide/examples/__control-by-control.html +++ b/web/docs/engine/guide/examples/__control-by-control.html @@ -34,7 +34,7 @@

Control-by-Control Example

Email to (KeymanWeb not enabled)

-

Subject (Defaults to 'English' or 'off' unless on a touch-based device)

+

Subject (Defaults to the first language added to KeymanWeb after initialization)

Message body (Defaults to 'LaoKeys')

diff --git a/web/docs/engine/guide/examples/automatic-control.md b/web/docs/engine/guide/examples/automatic-control.md index 14b703d35aa..a12de04b492 100644 --- a/web/docs/engine/guide/examples/automatic-control.md +++ b/web/docs/engine/guide/examples/automatic-control.md @@ -29,8 +29,6 @@ In this example, we use only the LaoKey keyboard. Please click [this link](./__a ``` -As you can see above, the second line in the code snippet above references the LaoKey keyboard loader JavaScript file. This is a small stub file, typically less than 200 bytes, that defines the name and actual location of the real keyboard file (in this case, **laokeys.js**). When a page may reference many keyboards, this saves downloading potentially hundreds of kilobytes of unused Javascript keyboards - the keyboard is downloaded when it is first selected by the user. - ## API References On initialization: [`keyman.init()`](../../reference/core/init). diff --git a/web/docs/engine/guide/examples/control-by-control.md b/web/docs/engine/guide/examples/control-by-control.md index 5f57a5a7785..b637b0b693a 100644 --- a/web/docs/engine/guide/examples/control-by-control.md +++ b/web/docs/engine/guide/examples/control-by-control.md @@ -41,8 +41,6 @@ Also include the following HTML code: // example without detailing paths precisely in the init() call's parameter.--> - - ``` diff --git a/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js b/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js index 33752796491..6364832fc45 100644 --- a/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js +++ b/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js @@ -63,7 +63,7 @@ describe('LanguageProcessor', function() { assert.isTrue(languageProcessor.mayPredict); // Some aspects of initialization must wait until after construction and overall - // load of the core. See /web/source/kmwbase.ts, in the final IIFE. + // load of the core. assert.isOk(languageProcessor.lmEngine); }); }); From f218aa92db6845f3187067099bc88cebbf484c91 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 3 Jun 2026 19:41:47 +0200 Subject: [PATCH 48/72] refactor(web): reformatting of files Test-bot: skip --- .../guide/examples/automatic-control.md | 6 +++-- .../guide/examples/control-by-control.md | 10 ++++++-- .../guide/examples/full-manual-control.md | 7 +++--- .../engine/guide/examples/manual-control.md | 5 ++-- .../app/browser/src/context/focusAssistant.ts | 2 +- .../keyboard/keyboards/keyboardProperties.ts | 2 +- .../web/build-visual-keyboard/index.html | 23 ++++++++----------- .../test/manual/web/chirality/utilities.js | 2 +- 8 files changed, 32 insertions(+), 25 deletions(-) diff --git a/web/docs/engine/guide/examples/automatic-control.md b/web/docs/engine/guide/examples/automatic-control.md index c804556e180..ef8a57f00fd 100644 --- a/web/docs/engine/guide/examples/automatic-control.md +++ b/web/docs/engine/guide/examples/automatic-control.md @@ -2,9 +2,11 @@ title: Automatic Mode Example --- -This page shows how to include a local keyboard from an arbitrary location in your website's file structure. +This page shows how to include a local keyboard from an arbitrary location +in your website's file structure. -In this example, we use only the LaoKey keyboard. Please click [this link](./__auto-control.html) to open the test page. +In this example, we use only the LaoKey keyboard. Please click +[this link](./__auto-control.html) to open the test page. ## Code Walkthrough diff --git a/web/docs/engine/guide/examples/control-by-control.md b/web/docs/engine/guide/examples/control-by-control.md index 974338ef1fe..d8492b99bb5 100644 --- a/web/docs/engine/guide/examples/control-by-control.md +++ b/web/docs/engine/guide/examples/control-by-control.md @@ -2,7 +2,10 @@ title: Control-by-Control Example --- -In this example, a simulated webmail form, the default and permissible keyboard for each control is managed by the web page. We use the automatic mode for simplicity of demonstration. We also link to the CDN for KeymanWeb in this example. Please click [this link](__control-by-control.html) to open the test page. +In this example, a simulated webmail form, the default and permissible keyboard for each control +is managed by the web page. We use the automatic mode for simplicity of demonstration. We also +link to the CDN for KeymanWeb in this example. Please click [this link](__control-by-control.html) +to open the test page. ## Code Walkthrough @@ -51,7 +54,10 @@ Also include the following HTML code: ``` --- -**Note:** In this example we disabled the first element (`document.f.address`) by API call. A later API call can re-enable KeymanWeb for this control should it fit the page's design. Alternatively, this can be done by instead adding the class `'kmw-disabled'` to the control. This will permanently block KeymanWeb from handling its input. +**Note:** In this example we disabled the first element (`document.f.address`) by API call. A +later API call can re-enable KeymanWeb for this control should it fit the page's design. +Alternatively, this can be done by instead adding the class `'kmw-disabled'` to the control. This +will permanently block KeymanWeb from handling its input. --- diff --git a/web/docs/engine/guide/examples/full-manual-control.md b/web/docs/engine/guide/examples/full-manual-control.md index e9e8d2908a8..5aa9189316b 100644 --- a/web/docs/engine/guide/examples/full-manual-control.md +++ b/web/docs/engine/guide/examples/full-manual-control.md @@ -2,7 +2,9 @@ title: Manual Control - Custom Interface --- -In this example, the web page designer has opted for their own user interface instead of the KeymanWeb interface. The keyboards in the selector are populated from the KeymanWeb list of keyboards. Please click [this link](__full-manual-control.html) to open the test page. +In this example, the web page designer has opted for their own user interface instead of the +KeymanWeb interface. The keyboards in the selector are populated from the KeymanWeb list of +keyboards. Please click [this link](__full-manual-control.html) to open the test page. ## Code Walkthrough @@ -35,8 +37,7 @@ Include the following script in the HEAD of your page: } /* KWControlChange: Called when user selects an item in the KWControl SELECT */ - function KWControlChange() - { + function KWControlChange() { /* Select the keyboard in KeymanWeb */ var name = KWControl.value.substr(0, KWControl.value.indexOf("$$")); var languageCode = KWControl.value.substr(KWControl.value.indexOf("$$"+2)); diff --git a/web/docs/engine/guide/examples/manual-control.md b/web/docs/engine/guide/examples/manual-control.md index 1087c518c8c..d1bd854d9b4 100644 --- a/web/docs/engine/guide/examples/manual-control.md +++ b/web/docs/engine/guide/examples/manual-control.md @@ -23,8 +23,7 @@ Include the following script in the HEAD of your page: } /* KWControlClick: Called when user clicks on the KWControl IMG */ - function KWControlClick() - { + function KWControlClick() { if(keyman.osk.isEnabled()) { keyman.osk.hide(); } else { @@ -61,9 +60,11 @@ And finally, include the control img for KeymanWeb: ## API References On programmatically setting the keyboard: + - [`keyman.setActiveKeyboard()`](../../reference/core/setActiveKeyboard). On managing the visibility of the OSK: + - [`keyman.osk.hide()`](../../reference/osk/hide) - [`keyman.osk.show()`](../../reference/osk/show). diff --git a/web/src/app/browser/src/context/focusAssistant.ts b/web/src/app/browser/src/context/focusAssistant.ts index accb9b811ec..ade72ea65a8 100644 --- a/web/src/app/browser/src/context/focusAssistant.ts +++ b/web/src/app/browser/src/context/focusAssistant.ts @@ -149,7 +149,7 @@ export class FocusAssistant extends EventEmitter { * @param {(boolean|number)} state Activate (true,false) */ setMaintainingFocus(state: boolean) { - this.maintainingFocus = state ? true : false; + this.maintainingFocus = !!state; } setFocusTimer(): void { diff --git a/web/src/engine/src/keyboard/keyboards/keyboardProperties.ts b/web/src/engine/src/keyboard/keyboards/keyboardProperties.ts index 1a3a9e215a4..b9b32b444a9 100644 --- a/web/src/engine/src/keyboard/keyboards/keyboardProperties.ts +++ b/web/src/engine/src/keyboard/keyboards/keyboardProperties.ts @@ -255,7 +255,7 @@ export class KeyboardProperties implements KeyboardInternalPropertySpec { return null; } - public validateForCustomKeyboard(): Error { + public validateForCustomKeyboard(): Error|null { if(!this.KI || !this.KN || !this.KL || !this.KLC) { return new Error("To use a custom keyboard, you must specify keyboard id, keyboard name, language and language code."); } else { diff --git a/web/src/test/manual/web/build-visual-keyboard/index.html b/web/src/test/manual/web/build-visual-keyboard/index.html index 2845f3db86c..e6231dd6913 100644 --- a/web/src/test/manual/web/build-visual-keyboard/index.html +++ b/web/src/test/manual/web/build-visual-keyboard/index.html @@ -51,7 +51,7 @@ platform == 'phone' ? 240 : 360; }; - let shadowfy = function(container) { + function shadowfy(container) { const shadow = container.attachShadow({ mode: "open" }); const docBase = container.children[0]; @@ -67,7 +67,7 @@ shadow.appendChild(docBase); } - let documentKeyboard = async function(kbdid, langid, formFactor, layer) { + async function documentKeyboard(kbdid, langid, formFactor, layer) { let docTarget = document.getElementById("docTarget"); label = document.createElement('h3'); docTarget.appendChild(label); @@ -77,8 +77,8 @@ const container = document.createElement('div'); - await kmw.addKeyboards(`${kbdid}@${langid}`); - await kmw.setActiveKeyboard(kbdid, langid); + await keyman.addKeyboards(`${kbdid}@${langid}`); + await keyman.setActiveKeyboard(kbdid, langid); docBase = keyman.BuildVisualKeyboard(kbdid, 1, formFactor, layer); label.textContent = `${keyman.getKeyboard(kbdid).Name} - ${formFactor} - ${layer} layer`; @@ -121,15 +121,12 @@

KeymanWeb: Keyboard documentation rendering

(It is ignored by other User Interfaces.) -->
-

This test page exists for testing KMW's keyboard-documentation rendering system.

- -
-
- -

Return to testing home page

- - - +

This test page exists for testing KMW's keyboard-documentation rendering system.

+ +
+
+ +

Return to testing home page

From c1113ac21d5d876b69504e557c0c9aa97459fdbb Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 8 Jun 2026 19:27:12 +0200 Subject: [PATCH 55/72] chore(web): address code review comments Also change a few places that I missed. --- .../keyboards/test_8568_deadkeys.html | 28 ++++++++--------- .../help/reference/kmc/cli/get-started.md | 2 +- developer/src/kmc/README.md | 2 +- web/docs/engine/guide/adding-keyboards.md | 18 +++++------ .../engine/guide/examples/__auto-control.html | 4 +-- .../guide/examples/__control-by-control.html | 7 ++--- .../guide/examples/__first-example.html | 6 ++-- .../guide/examples/automatic-control.md | 4 +-- web/docs/engine/guide/get-started.md | 10 +++---- .../engine/reference/core/addKeyboards.md | 12 +++++++- .../reference/core/addKeyboardsForLanguage.md | 2 +- web/docs/engine/reference/core/getKeyboard.md | 4 +-- .../engine/reference/core/getKeyboards.md | 4 +-- .../reference/interface/registerStub.md | 4 +-- web/src/app/ui/kmwuitoolbar.ts | 4 +-- web/src/samples/complex/root/pages/index.html | 10 +++---- web/src/samples/samplehdr.js | 30 ++++++++++--------- web/src/samples/subfolder_toolbar/index.html | 11 ++++--- .../web/build-visual-keyboard/index.html | 5 ++-- web/src/test/manual/web/ckeditor/index.html | 4 +-- web/src/test/manual/web/ckeditor/inline.html | 4 +-- .../test/manual/web/default-subkey/index.html | 4 +-- .../test/manual/web/desktop-ui/toolbar.html | 4 +-- web/src/test/manual/web/issue103/index.html | 4 +-- web/src/test/manual/web/issue103/samplehdr.js | 28 +++++++++-------- 25 files changed, 110 insertions(+), 105 deletions(-) diff --git a/common/test/resources/keyboards/test_8568_deadkeys.html b/common/test/resources/keyboards/test_8568_deadkeys.html index 1b4cf314247..054f19b4f81 100644 --- a/common/test/resources/keyboards/test_8568_deadkeys.html +++ b/common/test/resources/keyboards/test_8568_deadkeys.html @@ -36,22 +36,20 @@ + }); + diff --git a/developer/docs/help/reference/kmc/cli/get-started.md b/developer/docs/help/reference/kmc/cli/get-started.md index 348bf4c4b9b..2316abef38c 100644 --- a/developer/docs/help/reference/kmc/cli/get-started.md +++ b/developer/docs/help/reference/kmc/cli/get-started.md @@ -110,7 +110,7 @@ hamburger menu in the Keyman app. **Web**: copy khmer_angkor.js to your website, then [load it with KeymanWeb][load-keymanweb-keyboard]: ```js -keyman.addKeyboards({ +await keyman.addKeyboards({ id:'khmer_angkor', // The keyboard's unique identification code. name:'Khmer Angkor', // The keyboard's user-readable name. language:{ diff --git a/developer/src/kmc/README.md b/developer/src/kmc/README.md index 0490998e4d8..05fd265a50f 100644 --- a/developer/src/kmc/README.md +++ b/developer/src/kmc/README.md @@ -109,7 +109,7 @@ hamburger menu in the Keyman app. **Web**: copy khmer_angkor.js to your website, then [load it with KeymanWeb][load-keymanweb-keyboard]: ```js -keyman.addKeyboards({ +await keyman.addKeyboards({ id:'khmer_angkor', // The keyboard's unique identification code. name:'Khmer Angkor', // The keyboard's user-readable name. language:{ diff --git a/web/docs/engine/guide/adding-keyboards.md b/web/docs/engine/guide/adding-keyboards.md index 221f495de1b..2874d449bc8 100644 --- a/web/docs/engine/guide/adding-keyboards.md +++ b/web/docs/engine/guide/adding-keyboards.md @@ -8,8 +8,8 @@ There are multiple ways to add and install keyboards into your KeymanWeb install The most efficient way to utilize a keyboard is to obtain a local copy of it and place this copy in a static location on your website. Once this is done, it can be directly linked into KeymanWeb as follows. -```c -keyman.addKeyboards({ +```typescript +await keyman.addKeyboards({ id:'us', // The keyboard's unique identification code. name:'English', // The keyboard's user-readable name. language:{ @@ -22,7 +22,7 @@ keyman.addKeyboards({ Custom fonts may also be utilized via the `language.font` property. For example: -```c +```typescript font:{ family:'LaoWeb', source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot'] @@ -33,16 +33,16 @@ font:{ To obtain the default Keyman keyboard for a given language, call the following function. -```c -keyman.addKeyboardsForLanguage('Dzongkha'); +```typescript +await keyman.addKeyboardsForLanguage('Dzongkha'); ``` This example would find the default keyboard for the Dzongkha language. This method will fail if the name doesn't perfectly match any language found in the CDN's repository. Alternatively, languages may be looked up via their BCP 47 language code as follows: -```c -keyman.addKeyboards('@he') +```typescript +await keyman.addKeyboards('@he') ``` The `@` prefix indicates the use of the BCP 47 language code, which in this case corresponds with Hebrew. @@ -51,8 +51,8 @@ The `@` prefix indicates the use of the BCP 47 language code, which in this case To obtain a specific keyboard by name or by keyboard name and language code as a pair, see the following: -```c -keyman.addKeyboards('french','sil_euro_latin@sv','sil_euro_latin@no') +```typescript +await keyman.addKeyboards('french','sil_euro_latin@sv','sil_euro_latin@no') ``` This will install three keyboards - one for French (named, quite simply, "French") and two copies of the EuroLatin keyboard - one for Swedish and one for Norwegian. diff --git a/web/docs/engine/guide/examples/__auto-control.html b/web/docs/engine/guide/examples/__auto-control.html index efe5467bfd5..c2a8b472b09 100644 --- a/web/docs/engine/guide/examples/__auto-control.html +++ b/web/docs/engine/guide/examples/__auto-control.html @@ -3,8 +3,8 @@ - @@ -20,8 +19,8 @@ // Disable KeymanWeb interaction on the 'Email to' TEXT control keyman.disableControl(document.f.address); - // Set the default keyboard for the 'Subject' TEXT control to 'off' (i.e. default - // browser keyboard) + // Set the default keyboard for the 'Subject' TEXT control to the first language + // added to KeymanWeb after initialization. keyman.setKeyboardForControl(document.f.subject, '', ''); // Set the default keyboard for the 'Message body' TEXTAREA to the LaoKeys keyboard keyman.setKeyboardForControl(document.f.text, 'Keyboard_laokeys', 'lo'); @@ -42,4 +41,4 @@

Control-by-Control Example

Back to Document - \ No newline at end of file + diff --git a/web/docs/engine/guide/examples/__first-example.html b/web/docs/engine/guide/examples/__first-example.html index ece6a9b9eaa..24a58c48916 100644 --- a/web/docs/engine/guide/examples/__first-example.html +++ b/web/docs/engine/guide/examples/__first-example.html @@ -10,9 +10,9 @@ diff --git a/web/docs/engine/guide/examples/automatic-control.md b/web/docs/engine/guide/examples/automatic-control.md index 14b703d35aa..d185e4efe31 100644 --- a/web/docs/engine/guide/examples/automatic-control.md +++ b/web/docs/engine/guide/examples/automatic-control.md @@ -13,8 +13,8 @@ In this example, we use only the LaoKey keyboard. Please click [this link](./__a diff --git a/web/docs/engine/reference/core/addKeyboards.md b/web/docs/engine/reference/core/addKeyboards.md index fc5e3ce07bf..fd4049f221d 100644 --- a/web/docs/engine/reference/core/addKeyboards.md +++ b/web/docs/engine/reference/core/addKeyboards.md @@ -9,7 +9,7 @@ Adds keyboards to KeymanWeb. ## Syntax ```js -keyman.addKeyboards(spec[, spec...]) +await keyman.addKeyboards([spec...]) ``` ### Parameters @@ -179,3 +179,13 @@ The `spec.languages.font` object contains the following members: : `string` optional Font size (in CSS dimensions). If not specified, then `1em` is used. + +### Not passing any parameter + +Not passing any parameter will obtain the Keyman keyboards for English +from the CDN, i.e. these two functions calls are identical: + +```js +await keyman.addKeyboards(); +await keyman.addKeyboards('@en'); +``` diff --git a/web/docs/engine/reference/core/addKeyboardsForLanguage.md b/web/docs/engine/reference/core/addKeyboardsForLanguage.md index bf97a06b8d7..e3a1e553c7a 100644 --- a/web/docs/engine/reference/core/addKeyboardsForLanguage.md +++ b/web/docs/engine/reference/core/addKeyboardsForLanguage.md @@ -9,7 +9,7 @@ Add default or all keyboards for a given language to KeymanWeb. ## Syntax ```js -keyman.addKeyboardsForLanguage(spec[, spec...]) +await keyman.addKeyboardsForLanguage(spec[, spec...]) ``` ### Parameters diff --git a/web/docs/engine/reference/core/getKeyboard.md b/web/docs/engine/reference/core/getKeyboard.md index 0d6f8eb52e9..9b728995d07 100644 --- a/web/docs/engine/reference/core/getKeyboard.md +++ b/web/docs/engine/reference/core/getKeyboard.md @@ -8,7 +8,7 @@ Get keyboard meta data for the selected keyboard and language. ## Syntax -```c +```js keyman.getKeyboard(keyboardName, languageCode) ``` @@ -75,5 +75,5 @@ The `keyboard` object contains the following members: : `string` *optional* : The font packaged with the keyboard to properly display specialized OSK characters. -## See also +## See also - [keyman.addKeyboards()](addKeyboards) and its documentation about keyboard specification objects. diff --git a/web/docs/engine/reference/core/getKeyboards.md b/web/docs/engine/reference/core/getKeyboards.md index 8d84521b31a..cd8b0f2d9c6 100644 --- a/web/docs/engine/reference/core/getKeyboards.md +++ b/web/docs/engine/reference/core/getKeyboards.md @@ -8,7 +8,7 @@ Get details of currently installed keyboards. ## Syntax -```c +```js keyman.getKeyboards() ``` @@ -25,5 +25,5 @@ None. See [keyman.getKeyboard()](getKeyboard) for detail on the returned keyboard specification objects. -## See also +## See also - [keyman.addKeyboards()](addKeyboards) diff --git a/web/docs/engine/reference/interface/registerStub.md b/web/docs/engine/reference/interface/registerStub.md index 2d9c8ef5a8e..9dc8e10c34a 100644 --- a/web/docs/engine/reference/interface/registerStub.md +++ b/web/docs/engine/reference/interface/registerStub.md @@ -8,13 +8,13 @@ Registers the keyboard stub or returns true if already registered. ## Syntax -```c +```js keyman.interface.registerStub(Pstub); ``` or -```c +```js KeymanWeb.KRS(Pstub); // Shorthand ``` diff --git a/web/src/app/ui/kmwuitoolbar.ts b/web/src/app/ui/kmwuitoolbar.ts index cf736777f91..6ed8d82e231 100644 --- a/web/src/app/ui/kmwuitoolbar.ts +++ b/web/src/app/ui/kmwuitoolbar.ts @@ -1150,8 +1150,8 @@ if(!keymanweb) { internalName: string, languageCode: string }) => { // Uses a different format than .getKeyboards(), b/c why not? - // https://help.keyman.com/developer/engine/web/16.0/reference/core/getKeyboards vs - // https://help.keyman.com/developer/engine/web/16.0/reference/events/kmw.keyboardchange + // https://help.keyman.com/developer/engine/web/current-version/reference/core/getKeyboards vs + // https://help.keyman.com/developer/engine/web/current-version/reference/events/kmw.keyboardchange this.lastSelectedKeyboard = null; const kbName=p.internalName, lgName=keymanweb.util.getLanguageCodes(p.languageCode)[0]; diff --git a/web/src/samples/complex/root/pages/index.html b/web/src/samples/complex/root/pages/index.html index 8da72fcccb2..2e8122b1da1 100644 --- a/web/src/samples/complex/root/pages/index.html +++ b/web/src/samples/complex/root/pages/index.html @@ -44,9 +44,9 @@ // KMW will look for them here - two folders up from root, which is the base sample directory. keyboards: '/../../', attachType:'auto' - }).then(function() { + }).then(async function() { // Loads keyboards from the base sample folder - keyman.addKeyboards({ + await keyman.addKeyboards({ id:'us', name:'English', languages: { @@ -57,7 +57,7 @@ filename: 'us-1.0.js' }); // For contrast: - keyman.addKeyboards({ + await keyman.addKeyboards({ id:'lao_2008_basic', name:'Lao Basic', languages: { @@ -73,12 +73,12 @@ // 1. keyboard name ('french'), // 2. keyboard name and language code ('sil_euro_latin@no,sv'), // 3. or just the BCP-47 language code ('@he'). - keyman.addKeyboards('french', 'sil_euro_latin@no,sv', '@he'); + await keyman.addKeyboards('french', 'sil_euro_latin@no,sv', '@he'); // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - doAddKeyboardsForLanguage('Dzongkha'); + await doAddKeyboardsForLanguage('Dzongkha'); }); diff --git a/web/src/samples/samplehdr.js b/web/src/samples/samplehdr.js index ad9dd762a3d..d3f89e5c4a5 100644 --- a/web/src/samples/samplehdr.js +++ b/web/src/samples/samplehdr.js @@ -43,9 +43,9 @@ function errToString(err) { // Painful? Kinda. But needed on un-updated Android API 21! if(Array.isArray(err)) { - var result = ''; - for(var i = 0; i < err.length; i++) { - var e = err[i]; + let result = ''; + for(let i = 0; i < err.length; i++) { + const e = err[i]; if(e.error instanceof Error) { result += e.error.message + '\n'; } else { @@ -73,13 +73,13 @@ } async function loadKeyboards(nestLevel) { - var base_prefix = '../'; - var prefix = './'; // The default - when prefix == 0. + const base_prefix = '../'; + let prefix = './'; // The default - when prefix == 0. if(nestLevel !== undefined && nestLevel > 0) { prefix = ''; - for(var i=0; i < nestLevel; i++) { - prefix = prefix + base_prefix; + for(let i=0; i < nestLevel; i++) { + prefix += base_prefix; } } @@ -116,29 +116,31 @@ } // Script to allow a user to add any keyboard to the keyboard menu - function addKeyboard(n) { - var sKbd; + async function addKeyboard(n) { + let sKbd; switch(n) { case 1: sKbd=document.getElementById('kbd_id1').value; - doAddKeyboards(sKbd); + await doAddKeyboards(sKbd); break; case 2: sKbd=document.getElementById('kbd_id2').value.toLowerCase(); - doAddKeyboards('@'+sKbd); + await doAddKeyboards('@'+sKbd); break; case 3: // Add keyboard for comma-separated language name(s) sKbd=document.getElementById('kbd_id3').value; - doAddKeyboardsForLanguage(sKbd); + await doAddKeyboardsForLanguage(sKbd); break; } } // Add keyboard on Enter (as well as pressing button) - function clickOnEnter(e,id) + async function clickOnEnter(e,id) { e = e || window.event; - if(e.keyCode == 13) addKeyboard(id); + if (e.keyCode == 13) { + await addKeyboard(id); + } } diff --git a/web/src/samples/subfolder_toolbar/index.html b/web/src/samples/subfolder_toolbar/index.html index e3813e4b523..8a4c8d6b033 100644 --- a/web/src/samples/subfolder_toolbar/index.html +++ b/web/src/samples/subfolder_toolbar/index.html @@ -30,8 +30,8 @@ diff --git a/web/src/test/manual/web/build-visual-keyboard/index.html b/web/src/test/manual/web/build-visual-keyboard/index.html index 54a47172d0b..77ccc07fbc1 100644 --- a/web/src/test/manual/web/build-visual-keyboard/index.html +++ b/web/src/test/manual/web/build-visual-keyboard/index.html @@ -75,8 +75,8 @@ const container = document.createElement('div'); - await kmw.addKeyboards(`${kbdid}@${langid}`); - await kmw.setActiveKeyboard(kbdid, langid); + await keyman.addKeyboards(`${kbdid}@${langid}`); + await keyman.setActiveKeyboard(kbdid, langid); docBase = keyman.BuildVisualKeyboard(kbdid, 1, formFactor, layer); label.textContent = `${keyman.getKeyboard(kbdid).Name} - ${formFactor} - ${layer} layer`; @@ -94,6 +94,7 @@ } keyman.init().then(async function() { + await loadKeyboards(1); await documentKeyboard("gff_amharic", "am", "phone", "default"); await documentKeyboard("sil_euro_latin", "no", "phone", "shift"); await documentKeyboard("lao_2008_basic", "lo", "tablet", "default"); diff --git a/web/src/test/manual/web/ckeditor/index.html b/web/src/test/manual/web/ckeditor/index.html index 25dfc7e9e8e..16de19ba021 100644 --- a/web/src/test/manual/web/ckeditor/index.html +++ b/web/src/test/manual/web/ckeditor/index.html @@ -32,9 +32,7 @@ diff --git a/web/src/test/manual/web/ckeditor/inline.html b/web/src/test/manual/web/ckeditor/inline.html index f54d98e0ff0..06ec8ca0e5e 100644 --- a/web/src/test/manual/web/ckeditor/inline.html +++ b/web/src/test/manual/web/ckeditor/inline.html @@ -32,9 +32,7 @@ diff --git a/web/src/test/manual/web/default-subkey/index.html b/web/src/test/manual/web/default-subkey/index.html index 6ce3bee562b..22f00ef3fef 100644 --- a/web/src/test/manual/web/default-subkey/index.html +++ b/web/src/test/manual/web/default-subkey/index.html @@ -38,8 +38,8 @@ diff --git a/web/src/test/manual/web/issue103/index.html b/web/src/test/manual/web/issue103/index.html index e2420715119..53c0447cbd1 100644 --- a/web/src/test/manual/web/issue103/index.html +++ b/web/src/test/manual/web/issue103/index.html @@ -38,8 +38,8 @@ diff --git a/web/src/test/manual/web/issue103/samplehdr.js b/web/src/test/manual/web/issue103/samplehdr.js index 51a38e4effe..bf8cabae0c6 100644 --- a/web/src/test/manual/web/issue103/samplehdr.js +++ b/web/src/test/manual/web/issue103/samplehdr.js @@ -1,25 +1,25 @@ // Modified version of commonHeader - contains repeated requests for the same language. - function loadKeyboards() { + async function loadKeyboards() { // The first keyboard added will be the default keyboard for touch devices. // For faster loading, it may be best for the default keyboard to be // locally sourced. - keyman.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, + await keyman.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, filename:'../us-1.0.js'}); // Add more keyboards to the language menu, by keyboard name, // keyboard name and language code, or just the BCP-47 language code. - keyman.addKeyboards('french','european2@sv','european2@sv','european2@sv','european2@sv','european2@sv','european2@sv','european2@no','@he'); - keyman.addKeyboards('@he'); - keyman.addKeyboards('@he'); + await keyman.addKeyboards('french','european2@sv','european2@sv','european2@sv','european2@sv','european2@sv','european2@sv','european2@no','@he'); + await keyman.addKeyboards('@he'); + await keyman.addKeyboards('@he'); // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - keyman.addKeyboardsForLanguage('Dzongkha'); + await keyman.addKeyboardsForLanguage('Dzongkha'); // Add a fully-specified, locally-sourced, keyboard with custom font - keyman.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', + await keyman.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', languages:{ id:'lo',name:'Lao',region:'Asia' }, @@ -38,29 +38,31 @@ } // Script to allow a user to add any keyboard to the keyboard menu - function addKeyboard(n) + async function addKeyboard(n) { let sKbd; switch(n) { case 1: sKbd=document.getElementById('kbd_id1').value; - keyman.addKeyboards(sKbd); + await keyman.addKeyboards(sKbd); break; case 2: sKbd=document.getElementById('kbd_id2').value.toLowerCase(); - keyman.addKeyboards('@'+sKbd); + await keyman.addKeyboards('@'+sKbd); break; case 3: sKbd=document.getElementById('kbd_id3').value; - keyman.addKeyboardsForLanguage(sKbd); + await keyman.addKeyboardsForLanguage(sKbd); break; } } // Add keyboard on Enter (as well as pressing button) - function clickOnEnter(e,id) { + async function clickOnEnter(e,id) { e = e || window.event; - if(e.keyCode == 13) addKeyboard(id); + if (e.keyCode == 13) { + await addKeyboard(id); + } } From 19e397de3532dc42f13cde0f96922135fb034aa9 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 8 Jun 2026 13:02:23 -0500 Subject: [PATCH 56/72] auto: increment master version to 19.0.244 Test-bot: skip Build-bot: skip --- HISTORY.md | 9 +++++++++ VERSION.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 7c83b63e6a4..53fbcf5934a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,14 @@ # Keyman Version History +## 19.0.243 alpha 2026-06-08 + +* fix(developer): reduce km_core_keyboard_attrs size to 12 (#16075) +* chore(deps): bump minimatch in /core/tests/unit/wasm (#16045) +* chore(deps): bump glob from 10.4.5 to 10.5.0 in /core/tests/unit/wasm (#16046) +* chore(deps-dev): bump js-yaml from 4.1.0 to 4.2.0 in /core/tests/unit/wasm (#16047) +* chore(deps-dev): bump picomatch from 2.3.1 to 2.3.2 in /core/tests/unit/wasm (#16048) +* chore(deps): bump qs and express (#16009) + ## 19.0.242 alpha 2026-06-03 * fix(web): fix displaying of keyboard menu (#16061) diff --git a/VERSION.md b/VERSION.md index 1b1ffdead86..9cfd5bdf98f 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.243 \ No newline at end of file +19.0.244 \ No newline at end of file From 135de352c4ba67166669975356f7db6edfee2212 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 9 Jun 2026 17:11:43 +0200 Subject: [PATCH 57/72] Apply suggestions from code review Co-authored-by: Marc Durdin --- web/docs/engine/guide/adding-keyboards.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/docs/engine/guide/adding-keyboards.md b/web/docs/engine/guide/adding-keyboards.md index 2874d449bc8..957df80270c 100644 --- a/web/docs/engine/guide/adding-keyboards.md +++ b/web/docs/engine/guide/adding-keyboards.md @@ -42,7 +42,7 @@ This example would find the default keyboard for the Dzongkha language. This met Alternatively, languages may be looked up via their BCP 47 language code as follows: ```typescript -await keyman.addKeyboards('@he') +await keyman.addKeyboards('@he'); ``` The `@` prefix indicates the use of the BCP 47 language code, which in this case corresponds with Hebrew. @@ -52,7 +52,7 @@ The `@` prefix indicates the use of the BCP 47 language code, which in this case To obtain a specific keyboard by name or by keyboard name and language code as a pair, see the following: ```typescript -await keyman.addKeyboards('french','sil_euro_latin@sv','sil_euro_latin@no') +await keyman.addKeyboards('french', 'sil_euro_latin@sv', 'sil_euro_latin@no'); ``` This will install three keyboards - one for French (named, quite simply, "French") and two copies of the EuroLatin keyboard - one for Swedish and one for Norwegian. From cf3d87d2f377fe7127731906a22acf9429b5c87e Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 9 Jun 2026 15:30:55 +0200 Subject: [PATCH 58/72] chore(web): update sample doc to match sample html The guide examples load `keymanweb.js` from the CDN, but the documentation explaining the examples showed loading local file. This change adjusts the docs to show loading from CDN. Built-bot: skip Test-bot: skip --- web/docs/engine/guide/examples/automatic-control.md | 2 +- web/docs/engine/guide/examples/control-by-control.md | 8 ++------ web/docs/engine/guide/examples/full-manual-control.md | 5 +---- web/docs/engine/guide/examples/manual-control.md | 5 +---- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/web/docs/engine/guide/examples/automatic-control.md b/web/docs/engine/guide/examples/automatic-control.md index d185e4efe31..6e6231d09d9 100644 --- a/web/docs/engine/guide/examples/automatic-control.md +++ b/web/docs/engine/guide/examples/automatic-control.md @@ -11,7 +11,7 @@ In this example, we use only the LaoKey keyboard. Please click [this link](./__a ```html - + - + + - - - - ``` --- diff --git a/web/docs/engine/guide/examples/full-manual-control.md b/web/docs/engine/guide/examples/full-manual-control.md index c958bd87d90..e9c56a3a0c3 100644 --- a/web/docs/engine/guide/examples/full-manual-control.md +++ b/web/docs/engine/guide/examples/full-manual-control.md @@ -47,14 +47,11 @@ Also include the following HTML code: ```html - + - - - ``` - File: [unified_loader.js](js/unified_loader.js) diff --git a/web/docs/engine/guide/examples/manual-control.md b/web/docs/engine/guide/examples/manual-control.md index 3f536d02e8c..7a6f4f09888 100644 --- a/web/docs/engine/guide/examples/manual-control.md +++ b/web/docs/engine/guide/examples/manual-control.md @@ -33,13 +33,10 @@ Also include the following HTML code: ```html - + - - - ``` - File: [laokeys_load.js](js/laokeys_load.js) From cea706c562b87c3515ad24f5e7b44d4ca972fc44 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 9 Jun 2026 17:13:47 +0200 Subject: [PATCH 59/72] chore(web): address code review comments --- .../guide/examples/__control-by-control.html | 9 +- .../engine/reference/core/addKeyboards.md | 8 +- web/src/samples/samplehdr.js | 95 ++++--- web/src/test/manual/web/commonHeader.js | 268 +++++++----------- web/src/test/manual/web/issue103/samplehdr.js | 112 ++++---- 5 files changed, 224 insertions(+), 268 deletions(-) diff --git a/web/docs/engine/guide/examples/__control-by-control.html b/web/docs/engine/guide/examples/__control-by-control.html index a76cb12fd2e..4f6e6d48507 100644 --- a/web/docs/engine/guide/examples/__control-by-control.html +++ b/web/docs/engine/guide/examples/__control-by-control.html @@ -10,7 +10,7 @@ - - - - ``` -- File: [laokeys_load.js](js/laokeys_load.js) - And finally, include the control img for KeymanWeb: ```html diff --git a/web/src/engine/src/main/keyboardInterfaceBase.ts b/web/src/engine/src/main/keyboardInterfaceBase.ts index 4f830561035..fbf1d20f24b 100644 --- a/web/src/engine/src/main/keyboardInterfaceBase.ts +++ b/web/src/engine/src/main/keyboardInterfaceBase.ts @@ -85,7 +85,6 @@ export class KeyboardInterfaceBase Date: Tue, 9 Jun 2026 13:01:55 -0500 Subject: [PATCH 65/72] auto: increment master version to 19.0.245 Test-bot: skip Build-bot: skip --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 53fbcf5934a..436ef8c5f5b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 19.0.244 alpha 2026-06-09 + +* refactor(web): extract function, rename variables (#16063) +* refactor(web): reformatting of files (#16066) +* chore(web): improve the KeymanWeb integration documentation (#16064) +* chore(web): more improvements to documentation (#16070) + ## 19.0.243 alpha 2026-06-08 * fix(developer): reduce km_core_keyboard_attrs size to 12 (#16075) diff --git a/VERSION.md b/VERSION.md index 9cfd5bdf98f..fdf064ce162 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.244 \ No newline at end of file +19.0.245 \ No newline at end of file From a005e45d25c438d55d577ef3a22dbc22a7f4ff3b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 9 Jun 2026 20:58:12 +0200 Subject: [PATCH 66/72] chore: Apply suggestions from code review Co-authored-by: rc-swag <58423624+rc-swag@users.noreply.github.com> --- .../src/tike/compile/Keyman.Developer.System.KmcWrapper.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas b/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas index f64e961b15e..6c33760ffc7 100644 --- a/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas +++ b/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas @@ -63,7 +63,7 @@ function TKmcWrapper.CompileForPublishing( else cmdline := cmdline + ['--no-compiler-warnings-as-errors']; - if not FGlobalProject.Options.WarnDeprecatedCode then + if Assigned(FGlobalProject) and (not FGlobalProject.Options.WarnDeprecatedCode) then cmdline := cmdline + ['--no-warn-deprecated-code']; if debug then From a47fd7d36584d593f44096bc3c32e2805354b0a9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 10 Jun 2026 05:43:42 +0200 Subject: [PATCH 67/72] fix: use meson subsystem and needs_exe_wrapper for cross builds on Windows Relates-to: mesonbuild/meson#15840 Fixes: #15753 Test-bot: skip --- .github/workflows/core-arm64-windows-test.yml | 2 +- core/cross-arm64.build | 1 + core/cross-x64.build | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/core-arm64-windows-test.yml b/.github/workflows/core-arm64-windows-test.yml index 0eb4e8f8f62..76d273de22b 100644 --- a/.github/workflows/core-arm64-windows-test.yml +++ b/.github/workflows/core-arm64-windows-test.yml @@ -119,7 +119,7 @@ jobs: - name: Install meson shell: bash run: | - pip3 install --user meson==1.10.1 + pip3 install --user meson==1.11.0 - name: Run test shell: bash diff --git a/core/cross-arm64.build b/core/cross-arm64.build index 9fd7cc6d147..7bea64992ee 100644 --- a/core/cross-arm64.build +++ b/core/cross-arm64.build @@ -1,5 +1,6 @@ [host_machine] system = 'windows' +subsystem = 'windows' cpu_family = 'aarch64' cpu = 'arm64' endian = 'little' diff --git a/core/cross-x64.build b/core/cross-x64.build index df3ab0b5dc6..ecc0486e088 100644 --- a/core/cross-x64.build +++ b/core/cross-x64.build @@ -1,5 +1,6 @@ [host_machine] system = 'windows' +subsystem = 'windows' cpu_family = 'x86_64' cpu = 'x86_64' endian = 'little' @@ -11,3 +12,6 @@ ar = 'lib' ld = 'link' strip = 'llvm-strip' pkgconfig = 'pkg-config' + +[properties] +needs_exe_wrapper = false From 6cd27503fe1c08390f03f21ef9b58075c61975d9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 10 Jun 2026 10:28:09 +0200 Subject: [PATCH 68/72] docs(windows): add context documentation for additional Advanced options Add Base Keyboard, Proxy Settings, and Keyman System Settings documentation and links to the Options context help. Fixes: keymanapp/help.keyman.com#10 Test-bot: skip --- windows/docs/help/basic/config/options.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/windows/docs/help/basic/config/options.md b/windows/docs/help/basic/config/options.md index 3b4bbd86b0b..f4084f64650 100644 --- a/windows/docs/help/basic/config/options.md +++ b/windows/docs/help/basic/config/options.md @@ -180,6 +180,28 @@ To open the Options tab of Keyman Configuration: log on for short and isolated tests as specifically requested by Keyman Technical Support. +- Proxy Settings + + In some environments, you may need to configure proxy settings in + order to download keyboards or updates for Keyman. Check with your + network administrator. More information is available in the + [Proxy Configuration](../../advanced/proxy_config) documentation. + +- Keyman System Settings + + The Keyman System Settings dialog provides access to various low-level + Keyman configuration settings. Generally, these settings do not need + to be changed, unless you instructed to do so by Keyman support staff. + +- Base Keyboard + + The Base Keyboard dialog allows you to select the physical Latin-script + layout of your keyboard, which will allow certain Keyman keyboards to remap + themselves dynamically to match your key caps. + + [Base Keyboard dialog](../../context/base-keyboard) + + ## Related Topics - [Keyman Configuration](../config/) From 624e076fe7e46513742e1d167eb5beb7feedb056 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 10 Jun 2026 13:02:19 -0500 Subject: [PATCH 69/72] auto: increment master version to 19.0.246 Test-bot: skip Build-bot: skip --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 436ef8c5f5b..fa463aa2a4f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 19.0.245 alpha 2026-06-10 + +* chore(web): hide KMX keyboard test page (#16083) +* docs(web): remove unnecessary mentioning of Keyman 17 from docs (#16085) +* chore(web): remove outdated `*_load.js` files (#16086) +* fix(web): fix `KeyboardStub.validateForCustomKeyboard` (#16067) + ## 19.0.244 alpha 2026-06-09 * refactor(web): extract function, rename variables (#16063) diff --git a/VERSION.md b/VERSION.md index fdf064ce162..825791dd343 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.245 \ No newline at end of file +19.0.246 \ No newline at end of file From e9a85dfd2f8f3bf6bcc0ade07a1cfee06ecc00b2 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 15 Jun 2026 13:02:00 -0500 Subject: [PATCH 70/72] auto: increment master version to 19.0.247 Test-bot: skip Build-bot: skip --- HISTORY.md | 6 ++++++ VERSION.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index fa463aa2a4f..4ef4e13561d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # Keyman Version History +## 19.0.246 alpha 2026-06-15 + +* feat(developer): run project validation from IDE (#16076) +* fix(core): use meson subsystem and needs_exe_wrapper for cross builds on Windows (#16088) +* docs(windows): add context documentation for additional Advanced options (#16089) + ## 19.0.245 alpha 2026-06-10 * chore(web): hide KMX keyboard test page (#16083) diff --git a/VERSION.md b/VERSION.md index 825791dd343..5c233ca148b 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.246 \ No newline at end of file +19.0.247 \ No newline at end of file From 72a17703216109d1e1852340fbfb126ef7545755 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 16 Jun 2026 10:33:34 +0200 Subject: [PATCH 71/72] chore: update multi-labeler to 5.0.0 Also update actions/labeler to 6.1.0. This moves to node v24. Relates-to: keymanapp/s.keyman.com#1607 --- .github/workflows/labeler.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index ee7fe7d03d9..9eb25a49e7a 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -8,12 +8,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Update labels based on changed files - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 + uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" - name: Update labels based on PR title id: labeler - uses: fuxingloh/multi-labeler@f5bd7323b53b0833c1e4ed8d7b797ae995ef75b4 # v2.0.1 + uses: fuxingloh/multi-labeler@bcd50af464202999e57f556b4aefcf05a34abf85 # v5.0.0 with: github-token: ${{secrets.GITHUB_TOKEN}} config-path: .github/multi-labeler.yml From 06eb7dee930b5e1f01a3cb99e02d6cbd95417475 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 16 Jun 2026 13:01:42 -0500 Subject: [PATCH 72/72] auto: increment master version to 19.0.248 Test-bot: skip Build-bot: skip --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 4ef4e13561d..f2d3cab56b0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.247 alpha 2026-06-16 + +* chore: update multi-labeler to 5.0.0 (#16100) + ## 19.0.246 alpha 2026-06-15 * feat(developer): run project validation from IDE (#16076) diff --git a/VERSION.md b/VERSION.md index 5c233ca148b..a0b5f4d5f24 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.247 \ No newline at end of file +19.0.248 \ No newline at end of file