diff --git a/.flake8 b/.flake8 index c211bcb7f6..bfc9e2b850 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,6 @@ [flake8] max-line-length = 120 +indent-size = 2 ignore = E402, # module level import not at top of file W504, # line break after binary operator diff --git a/.gitignore b/.gitignore index 57e73e9ed7..0d544a4078 100644 --- a/.gitignore +++ b/.gitignore @@ -108,6 +108,9 @@ # /engine/keyman32/ /windows/src/engine/keyman32/Keyman32.opensdf +/windows/src/engine/keyman32/*.obj +/windows/src/engine/keyman32/*.cod +/windows/src/engine/keyman32/*.pdb # /engine/keymanx64/ /windows/src/engine/keymanx64/x64 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce2e5c8fec..7354c987f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,7 +50,7 @@ This project and everyone participating in it is governed by the [Keyman Code of Keyman is a complex project that spans six different platforms, with many a variety of components on each platform. We have chosen to host the Keyman project in the [keymanapp/keyman](https://github.com/keymanapp/keyman) monorepo, but [keyboard layouts](https://github.com/keymanapp/keyboards) and [lexical models](https://github.com/keymanapp/lexical-models) are stored in their own repositories. * Issues relating to the Keyman apps and their interactions with operating systems or other apps should be opened in the Keyman repo -* For keyboard-specific or model-specific issues, open issues in the corresponding respository. +* For keyboard-specific or model-specific issues, open issues in the corresponding repository. * The Keyman websites are also hosted in their own repositories. Contributions, including issues and pull requests can be opened against these repositories also. * Product documentation for the Keyman app is hosted in the Keyman app repository, and replicated to the help.keyman.com repository. @@ -216,7 +216,7 @@ The labels are loosely grouped by their purpose, but it's not required that ever | `feat` | [search][search-feat] | Feature requests. | | `bug` | [search][search-bug] | Confirmed bugs or reports that are very likely to be bugs. | | `chore` | [search][search-chore] | Minor updates to Keyman. | -| `question` | [search][search-question] | Questions more than bug reports or feature requests (e.g. how do I do X). [**Usually better on the Keyman Community**][https://community.software.sil.org/c/keyman] | +| `question` | [search][search-question] | Questions more than bug reports or feature requests (e.g. how do I do X). [**Usually better on the Keyman Community**](https://community.software.sil.org/c/keyman) | | `spec` | [search][search-spec] | Issues which are specifications for a large scale feature. | | `docs` | [search][search-docs] | Related to any type of documentation. | diff --git a/HISTORY.md b/HISTORY.md index 3eb340ff9c..588f19fd08 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,141 @@ # Keyman Version History +## 15.0.156 alpha 2021-11-26 + +* fix(windows): fix loadkeyboardoptions core memory error (#5959) + +## 15.0.155 alpha 2021-11-24 + +* fix(web): reset scroll anchor on touchend (#5919) +* fix(web): shorten setFocusTimer() (#5946) +* fix(web): clear longpress timeout if user does a flick up (#5952) + +## 15.0.154 alpha 2021-11-22 + +* fix(web): simplify hide transition (#5910) +* fix(web): inline osk keytip position (#5938) +* feat(mac): i18n, support localization of Keyman for Mac (#5869) +* chore(linux): Remove unused test methods (#5954) +* chore(linux): Run ibus-keyman tests as part of the build (#5889) + +## 15.0.153 alpha 2021-11-20 + +* fix(web): resolve unhandled exception in promise (#5902) + +## 15.0.152 alpha 2021-11-19 + +* fix(common/core): Update decxstr to check all characters while decrementing (#5842) + +## 15.0.151 alpha 2021-11-18 + +* fix(developer): use correct tike icon (#5925) +* fix(android/engine): Support for U_xxxx_yyyy subkeys (#5913) +* fix(mac): use xcframework to support m1 (#5933) +* fix(developer): move web osk beneath text area (#5937) + +## 15.0.150 alpha 2021-11-17 + +* fix(web): remove canvas use for iOS compatibility (#5915) + +## 15.0.149 alpha 2021-11-17 + +* chore(developer): Update jszip version (#5770) +* chore(common): Check in crowdin strings for Fulfulde Nigeria (#5841) + +## 15.0.148 alpha 2021-11-16 + +* chore(common): Check in crowdin strings for Mandara (Wandala) (#5857) +* chore(linux): Update method comments (#5883) + +## 15.0.147 alpha 2021-11-15 + +* chore(android,linux,windows): Check in crowdin strings for Bura-Pabir (#5839) +* chore: add keyman-local.com to iis https setup (#5892) +* fix(web): avoid error if timerid not valid (#5908) + +## 15.0.146 alpha 2021-11-14 + +* feat(developer): support for U_xxxx_yyyy (#5894) + +## 15.0.145 alpha 2021-11-13 + +* fix(developer): crash exporting OSK on European keyboard (#5893) +* feat(developer): validate .kps files during compile (#5895) + +## 15.0.144 alpha 2021-11-12 + +* fix(android/engine): Truncate language name in list (#5878) +* fix(android/app): Check KMP file exists before attempting to extract (#5849) +* fix(web): Check parent element is defined before assigning (#5874) +* fix(android/app): Revert test code (#5899) + +## 15.0.143 alpha 2021-11-11 + +* fix(developer): handle shortcut keys in debugger (#5776) + +## 15.0.142 alpha 2021-11-08 + +* chore(linux): Cleanup (#5886) +* chore(linux): cleanup defunct tests (#5885) +* chore(linux): fix failing ibus-keyman tests (#5884) + +## 15.0.141 alpha 2021-11-05 + +* chore(linux): Add integration tests for ibus-keyman (#5881) + +## 15.0.140 alpha 2021-11-03 + +* feat(android): Allow uninstall of sil_euro_latin keyboard (#5838) +* chore(common/core): Compile test keyboards to .kmp (#5864) + +## 15.0.139 alpha 2021-11-02 + +* chore(common/core): refactor kmx unit tests (#5862) +* chore(linux): Extract keycode_to_vk[] to separate header file (#5863) + +## 15.0.138 alpha 2021-10-29 + +* chore(linux): Set correct version number on ibus component (#5859) +* chore(common/core): generate kmp.json (#5860) + +## 15.0.137 alpha 2021-10-28 + +* chore(common): Don't check for conventional commit for fixups (#5858) + +## 15.0.136 alpha 2021-10-23 + +* chore(android/engine): Address globe key TODO (#5836) + +## 15.0.135 alpha 2021-10-20 + +* docs(windows): add note to caps-lock-stores test (#5837) + +## 15.0.134 alpha 2021-10-19 + +* docs(windows): add install apps from anywhere instructions (#5829) + +## 15.0.133 alpha 2021-10-16 + +* chore(common): Check in crowdin strings for Hausa (#5768) + +## 15.0.132 alpha 2021-10-15 + +* feat(windows): manual keyboard caps tests (#5808) + +## 15.0.131 alpha 2021-10-13 + +* chore(windows): Add obj cod and pdb to gitignore (#5826) + +## 15.0.130 alpha 2021-10-11 + +* fix(common/core): incxstr checks all character while incrementing (#5712) + +## 15.0.129 alpha 2021-10-08 + +* chore: Create CODE_OF_CONDUCT.md (#5819) +* chore: Add CONTRIBUTING documentation (#5821) +* chore: Add link to SIL (#5823) + ## 15.0.128 alpha 2021-10-07 * fix(ios): move sentry settings responsibility to build agent ‍ ️ (#5805) diff --git a/README.md b/README.md index 6e71ad0d4c..acd890cc45 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,4 @@ Keyman is an open source project distributed under the [MIT license](LICENSE.md) * [Keyboards](https://keyman.com/keyboards) * [Documentation](https://help.keyman.com/) * [Online Keyboard](https://keymanweb.com/) +* [SIL Language Technology - software.sil.org](https://software.sil.org/) diff --git a/VERSION.md b/VERSION.md index 0dd8f1228c..e290593d4c 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -15.0.129 \ No newline at end of file +15.0.157 \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java index ded435eab7..8aee2a6acb 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java @@ -32,6 +32,7 @@ import com.tavultesoft.kmea.KMHelpFileActivity; import com.tavultesoft.kmea.KMManager; import com.tavultesoft.kmea.KMKeyboardDownloaderActivity; import com.tavultesoft.kmea.data.Keyboard; +import com.tavultesoft.kmea.data.KeyboardController; import com.tavultesoft.kmea.util.FileUtils; import com.tavultesoft.kmea.util.FileProviderUtils; import com.tavultesoft.kmea.util.KMLog; @@ -113,9 +114,8 @@ public final class KeyboardSettingsActivity extends AppCompatActivity { hashMap.put(iconKey, icon); infoList.add(hashMap); - // If not default keyboard, display uninstall keyboard - if (!(kbID.equalsIgnoreCase(KMManager.KMDefault_KeyboardID) && - languageID.equalsIgnoreCase(KMManager.KMDefault_LanguageID))) { + // As long as more than 1 keyboard installed, display uninstall keyboard + if (KeyboardController.getInstance().get().size() > 1 && KMManager.canRemoveKeyboard()) { hashMap = new HashMap<>(); hashMap.put(titleKey, getString(R.string.uninstall_keyboard)); hashMap.put(subtitleKey, ""); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java index 2689f0cff7..d18075ce42 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java @@ -65,6 +65,11 @@ public class PackageActivity extends AppCompatActivity implements Bundle bundle = getIntent().getExtras(); if (bundle != null) { kmpFile = new File(bundle.getString("kmpFile")); + if (!kmpFile.exists()) { + KMLog.LogError(TAG, kmpFile.getAbsolutePath() + " not found. Unable to extract"); + showErrorToast(context, getString(R.string.failed_to_extract)); + return; + } installMode = (KmpInstallMode) bundle.getSerializable("installMode"); if(installMode == null) installMode = KmpInstallMode.Full; lastInstallMode = installMode; @@ -230,6 +235,7 @@ public class PackageActivity extends AppCompatActivity implements setResult(1); cleanup(); finish(); + MainActivity.cleanupPackageInstall(); } /** diff --git a/android/KMAPro/kMAPro/src/main/res/values-bwr-rNT/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-bwr-rNT/strings.xml new file mode 100644 index 0000000000..2d53f322a6 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-bwr-rNT/strings.xml @@ -0,0 +1,154 @@ + + + + Tǝra + + Web Brauza + + Olkur Rubutu + + Na damwa + + Cicimta Rubutu + + Labar + + Setiyeri + + Hara instal ar su na bǝlin + + Jili: %1$s + + Keyman akwa bara jili ar Chrome version 57 wala na bǝlinari. + + Jaktǝ bǝlinkur ar Chrome + + Balditǝ hara taipin azi… + + + + Olkur rubutu: %1$d + + Jaktǝ olkur rubutu + + Kǝmta olkur rubutu + + \nShanga rubutuni mda ata cijimta ɗa\n + + Baɗita kulini + + Jaktǝ keyboard na ar hulfur mnya nga + + Vu ka Keyman nzi keyboard na ar shanga sistem ni + + Hara seti ar Keyman katsa nzi default keyboard nga + + Labar na damwa + + Ncangtǝ \"%1$s\" sakatǝ akwa kǝladzi + + Kamnyar hara instal ar keyboard packajyeri, zhar Keyman katsa wutǝ su na akwa sur ɗuwa labar na aɗi akwa na\'ura ni wa. + + Mdǝkǝ ngkatǝ bara wutǝ su na akwa sur ɗuwa su. Ata tsoktǝ ngǝla ka instal keyboard package + + Setiyeri + + + Hulfur mnya natǝ mdǝ hara Instal ɗa (%1$d) + Hulfur mnya natǝ mdǝ hara Instal ɗa (%1$d) + + + Hara instal ar Keyboard wal Dictionary + + Ncangtǝ hulfur mnya + + Jakta wala kǝmta saɓǝlkur keyboard + + Rubutu na ar Spesba + + Keyboard + + Hulfur mnya + + Hulfur mnya + Keyboard + + Kula rubutu ata + + Ncangtǝ thlǝma keyboard ni ata spesba + + Ncangtǝ thlǝma hulfur mnya ni ata spesba + + Ncantǝ thlǝma hulfur mnya ka keyboard ni ata spesba + + Aɗe ncangtǝ rubutǝni ata spesba wa + + Kǝlara saka kara ncangtǝ banner + + Mda ata sǝ hara kǝthlǝr kari + + Ma tsi-tsiyari, mda ata ncangta saka tǝ mdǝ vu ka tsa predictive text hara + + Zhari ka thleng labar ar suna hili atar netwok + + Ma kǝla-kǝlari, mda ata thleng labar suna hili + + Ma tsi-tsiyari, labar suna hili mda aɗeta thlenga wa + + Hara instal ni akwa keyman.com + + Hara instal ni akwa fail natǝ gǝ ɗuwari + + Hara instal akwa na\'ura na damwa + + Jaktǝ hulfur mnyayeri kamnyar hara instal ar keyboard + + (akwa package ar keyboard) + + Carɓǝla Keyboard Package + + Carɓǝla hulfur mnyayeri ala %1$s + + Hulfur mnya natǝ mdǝ jakta %1$s ka mzhitǝ %2$s + + Hulfur mnyayeri natǝ mdǝku kurtǝ ka hara instal ɗa + + Ndǝkǝr keyboard kamnyar jaktǝ wala kǝmta saɓǝlkur ni + + Shinar waya wala hansetu ni kamnyar katsa nzi thlat-thlata wala peri-periari + + Kalaktǝni katsa ni tsu apa tǝ mdǝ nam ni + + Paka wala hara taip ar URL + + Bukmakyeri + + Bukmakyeri aɗiwa + + Jaktǝ Bukmak + + Kǝra ndǝr + + Url + + Instal ar Package %1$s aɗi hara wa + + Akwa hara Download ar keyboard package\n%1$s… + + Ngǝla ta kǝsǝɓǝla + + Hara istal ar Keyboard + + Hara instal ar Dictionary + + %1$s aɗi package fayil ar Keyman na jiri wa.\n%2$s\" + + Keyboard Package aɗika keyboardyeri natǝ mdǝ nama katsa kǝthlǝ mdǝ tsukwa ka tsi natǝ mda ata hara instal ni + + Predictive text na bǝlin aɗiwa natǝ mda ata hara instal ni + + Keyboardyeri wala predictive text aɗiwa natǝ mda ata hara instal ɗa + + Keyboard package aɗika hulfur mnyayeri natǝ kǝ kǝɓa kari natǝ mda ata hara instal wa + + Metadata aɗi kalkal wa/ku sha akwa package + diff --git a/android/KMAPro/kMAPro/src/main/res/values-ff-rNG/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-ff-rNG/strings.xml new file mode 100644 index 0000000000..14179986ed --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-ff-rNG/strings.xml @@ -0,0 +1,154 @@ + + + + Sendu + + Lenguru burawza + + Maunugo ɓaleeri + + Ɓeddaari + + Woggu ɓaleeri + + Bayanu + + Wo`innol + + Kesitiggo + + Nonde: %1$s + + Keyman ɗo mari haaje nonde Chrome 57 ko ko ɓuri kestiggo. + + Kesitu Chrome + + Fuɗɗu windugo haa ɗow… + + + + Mawnugo ɓaleeri: %1$d + + Mawnugo ɓaleeri dow + + Mawnugo ɓaleeri les + + \nBindi man fuu woggan\n + + Fuɗɗu + + Waatu kibod jee ɗemngal ma + + Waatu Keyman dow kibod komputa + + Waatu Keyman dow kibod a` naftirta + + Ɓeddol bayanu + + Hollu \"%1$s\" dow fuɗɗoode + + Ngam a` waata kuuje kibod, accu laawol Keyman jannga kuuje resol jee yaasi. + + Laawol babal resugo sali. Salan resugo kuuje kibod + + Wo`innol + + + Installed languages (%1$d) + Installed languages (%1$d) + + + Wa`unu Kibod ko bo deftere ma`ana + + Hollu ɗemngal + + A` wo`inna juutugo kibod + + Kapshon tappirɗe babal + + Kibod + + Ɗemngal + + Ɗemngal + Kibod + + Walaa kooɗume + + Hollu inde kibod ha tappirɗe babal + + Hollu inde ɗemngal haa tappirɗe babal + + Ɗemngal bee kibod dow tappirɗe babal + + Taa hollu kapshon dow tappirɗe babal + + Koonday holla bana + + Ɗum waɗan + + To ɗo nyifi, holla tan to ɓaleere sappol hokkaama + + Koonday fuu yaara wullandu wonnugo dow netwok + + To ɗo maɓɓiti, wullandu wonnugo yerɓan + + To ɗo nyifi, wullandu wonnugo yerɓataake + + A` wa`inna diga dow keyman.com + + A` wa`inna dow fayel nder komputa + + A` wa`inna diga na`ura feere + + A` waata ɗemngal ngam a` wa`inna kibod + + (Diga kuuje kibod) + + A` supta kuuje Kibod + + A` supta ɗemngal jee %1$s + + Ɗemngal ko wa`inni %1$s yahugo %2$s + + Ɗemngal man fuu arti wa`inni + + Dasa kibod man ngam de`itina juutugo + + Waylutu na`ura man ngam a` de`itina dow bee les + + Lornu ɗum ko naftirta tum + + A` tefa ko a` winda URL + + Bukmaks + + Walaa Bukmaks + + A` waata Bukmak + + Inde + + Url + + Kuuje man %1$s saldori wa`ugo + + Jippina kuuje kibod\n%1$s… + + Saldori nastingo + + Wa`unu kibod + + Wa`unu deftere ma`ana kalimaji + + %1$s naaɗum kuuje fayel Keyman boɗɗum ba.\n%2$s\" + + Kuuje kibod walaa ko meema waɗu optimaizin kibod ngam wa`a + + Walaa ɓaleere kesum ko wa`inta + + Walaa kibod ko ɓaleere ko wa`inta + + Kibod walaa ɗemngal feere ko wondi man ngam wa`inna + + Naa ɗum dayday/metadata man majji nder pakej + diff --git a/android/KMAPro/kMAPro/src/main/res/values-ha-rHG/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-ha-rHG/strings.xml new file mode 100644 index 0000000000..08f3258b02 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-ha-rHG/strings.xml @@ -0,0 +1,154 @@ + + + + Raba + + Manhajar binciko abubuwa a yanar gizo + + Girman rubutu + + Ƙari + + Goge Rubutu + + Bayani + + Saiti + + Shigar da Sabuntawa + + Nau\'i: %1$s + + Keyman na buƙatar manhajar Chrome na\'ui na 57 ko sabuwa. + + Sabunta Chrome + + Fara rubutu anan… + + + + Girman Rubutu: %1$d + + Girman Rubutu sama + + Girman Rubutu ƙasa + + \nZa a goge dukkan rubutu\n + + Farawa + + Saka madannan yarenka + + Barin Keyman a matsayin madannin system-wide + + Saita Keyman a matsayin madanni na zaɓi + + Ƙarin bayani + + Nuna \"%1$s\" farawa + + Domin shigar da ƙunshin madannin, a bari Keyman ya bayar da damar karanta ma\'adanar waje. + + Ba a sami damar izinin ajiya ba. Zai iya hana shigar da ƙunshin madannin + + Saiti + + + Yarukan da aka shigar (%1$d) + Yarukan da aka shigar (%1$d) + + + Shigar da Madannin ko Ƙamus + + Yaren da ake amfani + + Daidaita tsayin madannai + + Madannin bada tazara + + Madanni + + Yare + + Yare + Madanni + + Babu komai + + Nuna sunan madanni akan madannin bada tazara + + Nuna sunan yare akan madannin bada tazara + + Yare da madannin kan madannin bada tazara + + Kada a nuna komai akan madannin bada tazara + + Nuna banner koyaushe + + Da za a aiwatar + + Idan a kashe, rubutun hasashe ne kaɗai za a iya gani + + Bada izinin tura rahoton wata matsala kan yanar gizo + + Lokacin aka kunna, za a aika rahotannin matsalar + + Yayin da yake a kashe, ba za a iya aika rahotonnin matsalar ba + + Shigar daga keyman.com + + Shigar daga lokal fayil + + Shigar daga wata na\'urar + + Ƙara harsuna ga madannan da aka shigar + + (daga ƙunshin madanni) + + Zaɓi Ƙunshin Madanni + + Zaɓi harsuna domin %1$s + + An ƙara harshe %1$s zuwa %2$s + + An riga da an shigar da dukkanin yarukan + + Danne madanni domin sake girman + + Juya na\'urar domin daidaita hoto da gyara shi + + Gyara saiti zuwa yadda ya dace + + Bincika ko rubuta URL + + Alamomi + + Babu alamomi + + Sanya alama + + Suna + + Url + + An kasa shigar da %1$s ƙunshin + + Saukar da ƙunshin madanni \n%1$s… + + An kasa cirewa + + Shigar da madanni + + Shigar da Ƙamus + + %1$s ƙunshin fayil ɗin Keyman ba mai inganci bane.\n%2$s\" + + Ƙunshin madannin ba shi da ingantaccen wajen taɓa madannan da aka inganta da za a shigar + + Babu wani sabon rubutun hasashe da aka shigar + + Babu madannai ko rubutun hasashe da aka shigar + + Ƙunshin madannan bashi da wani yaren da ke da alaƙa da za a shigar + + Rasa inagancin/ɓacewar metadata cikin ƙunshin + diff --git a/android/KMAPro/kMAPro/src/main/res/values-mfi-rNG/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-mfi-rNG/strings.xml new file mode 100644 index 0000000000..83a6916317 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-mfi-rNG/strings.xml @@ -0,0 +1,154 @@ + + + + Digahe + + Web Browser + + Ƴaikire Text + + Farfe adaliya + + Gaggahe Text + + Labare + + Saitine + + Femhe Updates + + Version: %1$s + + Keyman requires Chrome version 57 mtu daula ne. + + Maga Update Chrome + + Fantuhe vinda ahuna… + + + + Ƴaikire Text: %1$d + + Ƴaikire Text at irane + + Ƴaikire Text am ɗyilane + + \nBaɗumma text a de gagga nde\n + + Fantuhe + + Farfe keyboard gé naza nare ŋa + + Magave Keyman gé system-wide keyboard + + Magahe saitin Keyman gé default keyboard + + Farfe labare adaliya + + Mara \"%1$s\" at fantuhe ne + + Gé femhe keyboard pakeji, ɗyanɗya Keyman permission gé magahe karatu external storage. + + Storage permission magavka. Matake a de magavka femhe keyboard pakeji ŋanna + + Saitine + + + Naraha na an fa na (%1$d) + Naraha na an fa na (%1$d) + + + Femhe Keyboard mtu Dictionary + + Mara-nare + + Mbeɗanahe tsa keyboard + + Spacebar caption + + Keyboard + + Nare + + Nare + Keyboard + + Blank + + Mara zhera keyboard at spacebar + + Mara zhera nare at spacebar + + Nare antara keyboard at spacebar + + Ɓáaka mara caption at spacebar + + Ba kullume mam-mara banner + + Gé fantu maga slera anikwa + + Ma seke an já, a mara ba predictive text ŋane a magave + + Ɓilana-ɓila basha jare badza aɗaba network + + Ma seke an kunna, anuga vela basha jare badza ŋanna + + Ma seke an já, ɓáaka vela basha jare badza ŋanna + + A femhe a sawam keyman.com + + A femhe a sawam local file + + A femhe a sawam umbulé gé nawura + + Farfe naraha gé femhe keyboard + + (a sawam keyboard pakeji) + + Ksa Keyboard Pakeji + + Ksa naraha gé %1$s + + Nare na farfe nde ŋanna %1$s to %2$s + + Baɗumma naraha na an fa amikwa ŋanna + + Piya keyboard gé fare tsa ne adaliya + + Mbeɗanahe nja nawura ŋanna gé portrait antara landscape + + Reset to Defaults + + Ɗatsa mtu vinda URL + + Bookmarks + + Ɓáaka Bookmarks + + Farfe Bookmark + + Irane gé duksá + + Url + + Pakeji %1$s kwalla-kwale magave gé femhe + + Tsukwanahe keyboard pakeji\n%1$s… + + Magavka tiɗase ashigashe + + Femhe Keyboard + + Femhe Dictionary + + %1$s ŋanenka kalkale Keyman pakeji file.\n%2$s\" + + Keyboard pakeji ɓáaka elva geja keyboards gé femhe + + Ɓáaka daula predictive text gé femhe + + Ɓáaka keyboards mtu predictive text gé femhe + + Keyboard pakeji ɓáaka naraha harzhe gé femhe + + Kalkalenka/Missing metadata am pakeji + diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index 5255542aac..d0d4f93cf0 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -118,8 +118,7 @@ //window.console.log('onStateChange change: ' + change); // Refresh KMW OSK - var kmw=window['keyman']; - kmw['correctOSKTextSize'](); + keyman.correctOSKTextSize(); fragmentToggle = (fragmentToggle + 1) % 100; if(change != 'configured') { // doesn't change the display; only initiates suggestions. diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/DisplayLanguages.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/DisplayLanguages.java index 246621ecb0..9b518bf921 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/DisplayLanguages.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/DisplayLanguages.java @@ -41,12 +41,16 @@ public class DisplayLanguages { new DisplayLanguageType(unspecifiedLocale, context.getString(R.string.default_locale)), new DisplayLanguageType("am-ET", "አማርኛ (Amharic)"), new DisplayLanguageType("az-AZ", "Azərbaycanca (Azəricə)"), + new DisplayLanguageType("bwr-NT", "Bura-Pabir"), + new DisplayLanguageType("ff-NG", "Ɗemngal Fulfulde"), new DisplayLanguageType("en", "English"), new DisplayLanguageType("es-419", "Español (Spanish - Latin America)"), new DisplayLanguageType("fr-FR", "French"), + new DisplayLanguageType("ha-HG", "Hausa"), new DisplayLanguageType("in-ID", "Indonesian"), new DisplayLanguageType("de-DE", "German"), new DisplayLanguageType("km-KH", "Khmer"), + new DisplayLanguageType("mfi-NG", "Mandara (Wandala)"), new DisplayLanguageType("ann", "Obolo"), new DisplayLanguageType("ff-ZA", "Pulaar-Fulfulde"), // or Fulah new DisplayLanguageType("shu-latn", "Shuwa (Latin)"), diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java index d83639a882..4609292615 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboard.java @@ -676,18 +676,30 @@ final class KMKeyboard extends WebView { } } - // Extract Unicode numbers (\\uxxxx) from a layer to character string. - // Ignores empty strings and layer names - // Returns: String + /** + * Extract Unicode numbers (\\u_xxxx_yyyy) from a layer to character string. + * Ignores empty strings and layer names + * Refer to web/source/osk/oskKey.ts + * @param ktext Unicode string in the format \\uxxxx_yyyy + * @return String to display on subkey + */ protected String convertKeyText(String ktext) { String title = ""; String[] values = ktext.split("\\\\u"); int length = values.length; for (int j = 0; j < length; j++) { if (!values[j].isEmpty() && !values[j].contains("-")) { - int c = Integer.parseInt(values[j], 16); - // TODO: \\uxxxxxx will need to be handled with title.codePointAt(c) - title += String.valueOf((char) c); + // Split U_xxxx_yyyy + String[] codePoints = values[j].split("_"); + for (String codePoint : codePoints) { + int codePointValue = Integer.parseInt(codePoint, 16); + if ((0x0 <= codePointValue && codePointValue <= 0x1F) || (0x80 <= codePointValue && codePointValue <= 0x9F) + || (codePointValue > 0x10FFFF)) { + continue; + } else { + title += new String(Character.toChars(codePointValue)); + } + } } } return title; diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java index 240dc66764..44901358d5 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java @@ -2109,9 +2109,6 @@ public final class KMManager { } else { // clear globeKeyState globeKeyState = GlobeKeyState.GLOBE_KEY_STATE_UP; - - // TODO: This should not be allowed because !shouldAllowSetKeyboard (#5570) - switchToNextKeyboard(context); } } diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java index 48d343077b..b322fbd6f4 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java @@ -136,8 +136,8 @@ public final class KeyboardPickerActivity extends BaseActivity { listView.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView parent, View view, final int position, long id) { - // Prevent the default keyboard from being removed - if (position > 0 && canRemoveKeyboard) { + // As long as more than 1 keyboard installed, display uninstall keyboard + if (KeyboardController.getInstance().get().size() > 1 && canRemoveKeyboard) { PopupMenu popup = new PopupMenu(context, view); popup.getMenuInflater().inflate(R.menu.popup, popup.getMenu()); popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @@ -370,11 +370,8 @@ public final class KeyboardPickerActivity extends BaseActivity { protected static boolean removeKeyboard(Context context, int position) { boolean result = false; - // Prevent the first keyboard (index 0) from being removed - if (position > 0) { - KeyboardController.getInstance().remove(position); - result = KeyboardController.getInstance().save(context); - } + KeyboardController.getInstance().remove(position); + result = KeyboardController.getInstance().save(context); notifyKeyboardsUpdate(context); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/KeyboardController.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/KeyboardController.java index 1793a38d04..3b824d0077 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/KeyboardController.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/data/KeyboardController.java @@ -321,7 +321,7 @@ public class KeyboardController { * @param index */ public void remove(int index) { - // Check initialized, and disallow removing default keyboard + // Check initialized if (!isInitialized) { return; } diff --git a/android/KMEA/app/src/main/res/layout/list_row_layout3.xml b/android/KMEA/app/src/main/res/layout/list_row_layout3.xml index 662c488c80..0847889ccd 100644 --- a/android/KMEA/app/src/main/res/layout/list_row_layout3.xml +++ b/android/KMEA/app/src/main/res/layout/list_row_layout3.xml @@ -12,6 +12,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" + android:layout_toStartOf="@id/imageButton1" android:layout_alignParentStart="true" android:layout_centerVertical="true" android:orientation="vertical" > @@ -22,6 +23,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" + android:ellipsize="end" + android:singleLine="true" android:textSize="16sp" android:textStyle="bold" /> diff --git a/android/KMEA/app/src/main/res/values-bwr-rNT/strings.xml b/android/KMEA/app/src/main/res/values-bwr-rNT/strings.xml new file mode 100644 index 0000000000..647e685e17 --- /dev/null +++ b/android/KMEA/app/src/main/res/values-bwr-rNT/strings.xml @@ -0,0 +1,153 @@ + + + + + + + Keyboard + Keyboardyeri + + + Jaktǝ keyboard na bǝlin + + Hulfur mnyayeri natǝ mdǝkǝ hara installari + + %1$s Setiyeri + + Jakta + + Bwahili + + Awo + + Harta + + Hartǝ Keyman + + Lǝkǝma + + Lakur vu su wala hara rubutu na damwa + + Dawunlodi + + Hara instal + + Danzi + + Lǝkǝma + + OK + + Jakǝngkǝr su na bǝlin + + Aɗa tsoktǝ kǝɓa ka sava ar Keyman wa! + + Ga ata bara kara cijimta keyboard ngǝni ya? + + Ga ata bara kara hara dawunlodi ar jili keyboard ngǝni na bǝlin ni ya? + + Ga ata bara kara wutǝ su na bǝlin na ar keyboardyeri ka dictionariyeri ya kulini? + + Jakǝngkǝr bǝlinkur ar kariyeri + + Kariyeri na bǝlin aɗi + + %1$s (Suayeri na bǝlin aɗi) + + Suayeri na bǝlin aɗi na ar keyboard %1$s: %2$s + + Suayeri na bǝlin aɗi na ar dictionary %1$s: %2$s + + Jili keyboard + + Vi wutǝ dlara + + Kǝlɓǝla keyboard + + [new] %1$s + + Kǝtǝ fwator kod ngǝni kamnyar hara lodi ar \nkeyboard ata na\'ura laga + + Maraba ka si akwa %1$s + + Mda akwa bara library na ar FileProvider kamnyar ka mdǝ wutǝ fayil ar dlara: %1$s + + Dametǝdzi na ar Fatal akwa keyboard ka %1$s:%2$s ala %3$s hulfur mnya. Akwa hara lodi ar default keyboard. + + Dametǝdzi akwa keyboard %1$s:%2$s na ar %3$s hulfur mnya. + + Akwa pakǝ dictionary na kǝɓa kari kamnyar katsa hara dawunlodi ni + + Ga ata bara kara hara dawunlodi ar jili dictionary na ngǝni na bǝlin ni ya? + + Dictionary aɗiwa natǝ mda ata hara dawunlodir ni + + Katalogu na ar kariyeri ni aɗiwa + + Jakǝngkǝr bǝlinkur kataloguni ku kurtǝ ka baɗita akwa bakgrawund + + Katalogu ni akwa hara dawunlodi harba kulini; usa, jaktǝ ngkǝrtǝ bu damzi rakka! + + Akwa pakǝ kariyeri + + Dawunlodi ar keyboard ku baɗita akwa Bakgurawund + + Keyboard natǝ mdǝ carɓǝla ni ku kurtǝ ka baɗitǝ dawunlodi; usa, jaktǝ ngkǝrtǝ bu ɗanzi rakka! + + Dawunlodi ar keyboard kǝ kuri! + + Dawunlodi ar dictionary ku baɗita akwa Bakgurawund + + Dictionary natǝ mdǝ carɓǝla ni ku kurtǝ ka baɗitǝ dawunlodi; usa, jaktǝ ngkǝrtǝ bu ɗanzi rakka! + + Dawunlodi ar dictionary kǝ kuri. + + Dawunlodi aɗi hara wa + + Kǝ ngǝlar ta kǝlɓǝla fayil ar dawunlodi + + Ngǝlari ta kǝɓa ka sava! + + "Shanga kariyeri ni da bǝlin!" + + Pal wala hang ta pal akwa kariyeri ni ngǝla ta nzi bǝlin! + + Kariyeri kǝ kurtǝ ka nzi bǝlin! + + Jili ar dictionary + + Kǝlɓǝla dictionary + + Ga ata bara kara cijimta dictionary ngǝni ya? + + %1$s keyboard ku hara instal + + Vu ka tsa namtǝdzi + + Vu katsa hamta sauri su natǝ asi + + Dictionari + + Pakǝ dictionari na aɗi + + Dictionary: %1$s + + %1$s dictionariyeri + + + Dictionari ku hara instal + + + (%1$d keyboard) + (%1$d keyboardyeri) + + + Default Locale + + + + Cijimta + + + Tsukwa vi ngǝni kamnyar pali keyboard ni + diff --git a/android/KMEA/app/src/main/res/values-ff-rNG/strings.xml b/android/KMEA/app/src/main/res/values-ff-rNG/strings.xml new file mode 100644 index 0000000000..1fed8fd14b --- /dev/null +++ b/android/KMEA/app/src/main/res/values-ff-rNG/strings.xml @@ -0,0 +1,153 @@ + + + + + + + Keyboard + Keyboards + + + Ɓeddu kibod kesum + + Ɗemngal ko wa`inni + + %1$s Settin + + Ɓeddu + + Ɓaawo + + Wurtu + + Maɓɓu + + Maɓɓu Keyman + + Yeeso + + Laawol waatugo jee yeeso + + Jippunu + + Wa`unu + + Yeeso + + Yeeso + + OK + + Kesutu + + Nastata dow sava Keyman + + A` yiɗan woggugo kibod ɗo na? + + A` yiɗan jippingo kibod kesum ɗo na? + + A` yiɗan kesutuggo Kibod ɗo bee defetere ma`ana kalimaji jotta? + + Kare kesutuggo + + Kare kesutiggu ɗo feesi + + %1$s (Kesutuggo ɗo feesi) + + Kesutuggo ɗo feesi jee kibod %1$s: %2$s + + Kesutuggo deftere ma`ana kalimaji ɗo feesi %1$s: %2$s + + Nonde kibod + + Lenguru mballa + + A` itta kibod + + [new] %1$s + + A` waɗa sikanin lamba ɗo ngam a` wa`inna \nkibod man dow na`ura feere + + Jaɓɓaama tow %1$s + + Babal fayel ɗo mari haaje laarugo fayel mballa: %1$s + + Kibod man wari bee masala bee %1$s:%2$s jee %3$s ɗemngal. Ɗo nastina kibod ko naftirta. + + Masala nder kibod %1$s:%2$s jee %3$s ɗemngal. + + Ɗo tefa deftere ma`ana kalimaji ngam jippina + + A` yiɗan jippingo deftere ma`ana kalimaji ɗo kesum na? + + Walaa deftere ma`ana kalimaji ko jippinta + + Limngal kare kuuɗe man feesay + + Kesutuggo limngal kare kuuɗe man fuɗɗaama ha ɓaawo + + Limngal kare kuuɗe man ɗo jippa; usseni a` meeta warugo nder wakkati seɗɗa! + + Ɗo tefa kare kuuɗe + + Kibod man ɗo jippa haa ɓaawo + + Kibod ko supti man arti ɗo jippa; usseni a` lorta nder wakkati seɗɗa! + + Kibod man timmini jippugo! + + Jippingo deftere ma`ana kalimaji man fuɗɗi haa ɓaawo + + Deftere ma`ana kalimaji ko supti man arti ɗo jippa; usseni a` lorta nder wakkati seɗɗa! + + Jippingo deftere ma`ana kalimaji man timmini. + + Jippingo man sali + + Sali wurtungo fayel ko jippi + + Saldori nastugo nder sava! + + "Kare kuuɗe man fuu kesum on!" + + Gootel ko ko ɓuri dow kare kuuɗe man sali kesituggo! + + Kare kuuɗe man kesiti! + + Nonde deftere ma`ana kalimaji + + A` itta deftere ma`ana kalimaji + + A` yiɗan woggugo deftere ma`ana kalimaji ɗo na? + + %1$s Kibod man wa`innaa ma + + Accu wo`inningo + + Accu tammunde + + Deftere ma`ana kalimaji + + Tefu deftere ma`ana kalimaji ko ɗon + + Deftere ma`ana kalimaji: %1$s + + %1$s deftere ma`anaaji + + + Deftere ma`ana kalimaji man wa`i + + + (%1$d keyboard) + (%1$d keyboards) + + + Difolt lokel + + + + Woggu + + + A` meema haa ɗo ngam a` wayla kibod + diff --git a/android/KMEA/app/src/main/res/values-ha-rHG/strings.xml b/android/KMEA/app/src/main/res/values-ha-rHG/strings.xml new file mode 100644 index 0000000000..c478e122ee --- /dev/null +++ b/android/KMEA/app/src/main/res/values-ha-rHG/strings.xml @@ -0,0 +1,153 @@ + + + + + + + Madanni + Madannai + + + Ƙara sabon madanni + + Harsunan da aka shigar + + Saitunan %1$s + + Ƙara + + Baya + + Sokewa + + Rufewa + + Rufe Keyman + + Gaba + + Hanyar Shiga na Gaba + + Saukewa + + Shigarwa + + Daga baya + + Na gaba + + OK + + Sabuntawa + + Kasa shiga rumbun bayanan keyman! + + Ana son goge wannan madannin? + + Ana son sauke sabon nau\'in wannan madannin? + + Ana son sabunta madannai da ƙamus a yanzu? + + Sabunta Abubuwa + + Abubuwan da aka sabunta sun samu + + %1$s (Akwai Sabuntawa) + + An sabunta sabida madannin %1$s: %2$s + + An sabunta sabida ƙamus ɗin %1$s: %2$s + + Nau\'in madanni + + Link ɗin taimako + + Madannan da aka cire + + [sabo] %1$s + + Duba wannan lambar domin shigar da wannan\nmadanni akan wata na\'urar + + Barka da zuwa %1$s + + Ana buƙatar wajen karatu na FileProvider domin duba fayil ɗin taimako: %1$s + + Matsalar madanni da %1$s:%2$s domin %3$s yaruka. Saukar da madanni mai kyau. + + Kuskure a yaren %1$s:%2$s for %3$s madanni. + + Duba ƙamus mai alaƙa domin a sauke + + Ana son sauke sabon nau\'in wannan ƙamus? + + Babu ƙamus ɗin da za a sauke + + Babu cikakkun jerin abubuwansa + + An fara sabunta cikkakkun jerin ta bayan fage + + Har yanzu cikakken jerin na sauka; yi haƙuri ƙara gwadawa na ɗan lokaci! + + Duba abubuwa + + An fara saukar da madanni a bayan fage + + Madannin da aka zaɓa na sauka; yi haƙuri a sake gwadawa cikin ɗan lokaci! + + An kammala sauke madannin! + + An fara sauke ƙamus ta Bayan fage + + Ƙamus ɗin da aka zaɓa an riga da an sauke shi; yi haƙuri a kuma gwadawa na ɗan lokaci! + + An kammala sauke ƙamus ɗin. + + An kasa saukewa + + Ba a yi nasarar dawo da fayil ɗin da aka sauke ba + + An kasa samun damar shiga rumbun bayanai! + + "An sabunta dukkanin abubuwan!" + + Ƙarin wani abin ya ƙi sabuntuwa! + + An yi nasarar sabunta abubuwan! + + Nau\'in ƙamus + + Ƙamus ɗin da aka cire + + Ana son a goge wannan ƙamus ɗin? + + An shigar da madannin %1$s + + Damar gyare-gyare + + Damar hasashe + + Ƙamus + + Bincika ƙamus ɗin da ke akwai + + Ƙamus: %1$s + + %1$s ƙamus + + + An shigar da ƙamus + + + (%1$d madanni) + (%1$d madannai) + + + Default Locale + + + + Goge + + + Danna nan domin canza madannai + diff --git a/android/KMEA/app/src/main/res/values-mfi-rNG/strings.xml b/android/KMEA/app/src/main/res/values-mfi-rNG/strings.xml new file mode 100644 index 0000000000..a3dd810aeb --- /dev/null +++ b/android/KMEA/app/src/main/res/values-mfi-rNG/strings.xml @@ -0,0 +1,153 @@ + + + + + + + Keyboard + Keyboards + + + Farfe daula Keyboard + + Naraha na an fa + + %1$s Saitin + + Farfe + + Fitsanahe aliye + + Tsanahe + + Hiɗante + + Hiɗante-hiɗa Keyman + + Fare a de katafke + + Next Input Method + + Tsukwanahe + + Femfe + + Aliyane + + Dem katafke + + Bastuwa + + Update + + A tá magavka jemhe an Keyman server! + + Ká kátáhe gaggahe una gé keyboard? + + Ká kátáhe tsukwanahe daula version una gé keyboard? + + Ká kátáhe magahe update keyboards antara dictionaries akina? + + Resource Updates + + Resource Updates Aŋkwa na + + %1$s (Update Aŋkwa na) + + Updates a keyboard na aŋkwa na %1$s: %2$s + + Updates a dictionary na aŋkwa na%1$s: %2$s + + Keyboard version + + Link mla + + Tiɗase keyboard + + [new] %1$s + + Ksa fatawe una gé code gé femhe una \nkeyboard am nawura umbulé + + Use gé sawa aza huna %1$s + + FileProvider library a kátá zhara file mla: %1$s + + Kuskure Fatal keyboard antara%1$s:%2$s gé %3$s nare. femhe nde default keyboard. + + Kuskure am keyboard %1$s:%2$s for %3$s nare. + + Zharanahe dictionary harzhe gé tsukwanahe + + Ká kátáhe tsukwanahe daula version gé una gé dictionary? + + Ɓáaka dictionary gé tsukwanahe + + Ɓáaka resource catalog ŋanna amikwa + + Catalog update fantufe am táne + + Catalog ŋanna aŋkwa ba njane tsukwa; tazlaŋa gwadana-gwada adaliya! + + Zharana-zhara resource + + Tsukwanahe keyboard a fantuhe am táne + + Keyboard na a ksa nde ŋanna aŋkwa tsukwa; tazlaŋa gwadana-gwada adaliya! + + Zlawzle Keyboard ŋanna a tsukwa! + + Tsukwanahe dictionary fantuhe am táne + + Dictionary na a ksa nde ŋanna aŋkwa tsukwa; tazlaŋa gwadana-gwada adaliya! + + Zlawzle Dictionary ŋanna a tsukwa. + + Kwalla-kwale tsukwa + + Kwalla-kwale sanhe file na a tsukwana nde ŋanna + + Kwalla-kwale sha server! + + "Baɗumma resources ŋanna ba kalkale a dem vatena!" + + Pállé mtu jawje resources kwalla-kwale maga update! + + Resources updated ba shagera! + + Dictionary version + + Tiɗase-tiɗe dictionary + + Ká kátáhe gaggahe una gé dictionary? + + %1$s keyboard an fa + + Magave kaɓa kuskure + + Magave predictions + + Dictionary + + Zharana-zhara dictionary na aŋkwa na + + Dictionary: %1$s + + %1$s dictionaries + + + Dictionary an fa + + + (%1$d keyboard) + (%1$d keyboards) + + + Default Locale + + + + Gaggahe + + + Piyana-piya mátána gé mbeɗa keyboard + diff --git a/android/help/basic/uninstalling-keyboards.md b/android/help/basic/uninstalling-keyboards.md index 9b3a7130d3..e6ab5dad61 100644 --- a/android/help/basic/uninstalling-keyboards.md +++ b/android/help/basic/uninstalling-keyboards.md @@ -28,7 +28,7 @@ Step 5) The bottom of the keyboard settings menu displays an option to uninstall the keyboard. Select 'Uninstall keyboard' to get a prompt to delete the keyboard. -**Note:** The default 'SIL EuroLatin' keyboard for English cannot be removed. +**Note:** Two or more keyboards need to be installed in order to display this option of uninstalling a keyboard. ![](../android_images/settings-khmer-info-ap.png) diff --git a/common/core/desktop/.gitignore b/common/core/desktop/.gitignore index 90ca8c2bb1..e71a856da3 100644 --- a/common/core/desktop/.gitignore +++ b/common/core/desktop/.gitignore @@ -11,3 +11,9 @@ VERSION.md shellHelperFunctions.sh src/rust/keyman_keyboard_processor/target/ + +# The following files/subdirs are generated by autotools when building ibus-keyman. +# They can be removed after we convert ibus-keyman to use meson. +.deps/ +.dirstamp +*.o diff --git a/common/core/desktop/include/keyman/keyboardprocessor.h b/common/core/desktop/include/keyman/keyboardprocessor.h index 43e364aeb3..2efb591c12 100644 --- a/common/core/desktop/include/keyman/keyboardprocessor.h +++ b/common/core/desktop/include/keyman/keyboardprocessor.h @@ -659,6 +659,12 @@ typedef struct { km_kbp_option_item const * default_options; } km_kbp_keyboard_attrs; +typedef struct { + km_kbp_virtual_key key; + uint32_t modifier_flag; +} km_kbp_keyboard_key; + +#define KM_KBP_KEYBOARD_KEY_LIST_END { 0, 0 } /* ``` ### `km_kbp_keyboard_load` @@ -725,6 +731,43 @@ km_kbp_status km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, km_kbp_keyboard_attrs const **out); +/* +``` +### `km_kbp_keyboard_get_key_list` +##### Description: +Returns the unordered full set of modifier+virtual keys that are handled by the +keyboard. The matching dispose call needs to be called to free the memory. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +##### Parameters: +- __keyboard__: A pointer to the opaque keyboard object to be queried. +- __out__: A pointer to an array of `km_kbp_keyboard_key` structures, + terminated by `KM_KBP_KEYBOARD_KEY_LIST_END`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_keyboard_get_key_list(km_kbp_keyboard const *keyboard, + km_kbp_keyboard_key **out); + + +/* +``` +### `km_kbp_keyboard_key_list_dispose` +##### Description: +Free the allocated memory belonging to a keyboard key list previously +returned by `km_kbp_keyboard_get_key_list`. +##### Parameters: +- __keyboard__: A pointer to the keyboard key list to be + disposed of. + +```c +*/ +void km_kbp_keyboard_key_list_dispose(km_kbp_keyboard_key *key_list); + + /* ``` ### State diff --git a/common/core/desktop/src/km_kbp_keyboard_api.cpp b/common/core/desktop/src/km_kbp_keyboard_api.cpp index 04c8eab956..43ad7bcd17 100644 --- a/common/core/desktop/src/km_kbp_keyboard_api.cpp +++ b/common/core/desktop/src/km_kbp_keyboard_api.cpp @@ -81,3 +81,20 @@ km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, *out = &keyboard->keyboard(); return KM_KBP_STATUS_OK; } + +km_kbp_status +km_kbp_keyboard_get_key_list(km_kbp_keyboard const *keyboard, + km_kbp_keyboard_key **out) +{ + assert(keyboard); assert(out); + if (!keyboard || !out) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + *out = keyboard->get_key_list(); + return KM_KBP_STATUS_OK; +} + +void km_kbp_keyboard_key_list_dispose(km_kbp_keyboard_key *key_list) +{ + delete[] key_list; +} diff --git a/common/core/desktop/src/kmx/kmx_conversion.cpp b/common/core/desktop/src/kmx/kmx_conversion.cpp new file mode 100644 index 0000000000..c893fbfb41 --- /dev/null +++ b/common/core/desktop/src/kmx/kmx_conversion.cpp @@ -0,0 +1,128 @@ + + + +#include "kmx_processevent.h" + +using namespace km::kbp; +using namespace kmx; + +/* This array is lifted from preservedkeymap.cpp */ + +const struct +{ + KMX_DWORD key; + KMX_BOOL shift; +} USCharMap[] = { + { VK_SPACE, FALSE }, // 20 ' ' + { '1', TRUE }, // 21 '!' + { VK_QUOTE, TRUE }, // 22 '"' + { '3', TRUE }, // 23 '#' + { '4', TRUE }, // 24 '$' + { '5', TRUE }, // 25 '%' + { '7', TRUE }, // 26 '&' + { VK_QUOTE, FALSE }, // 27 ''' + { '9', TRUE }, // 28 '(' + { '0', TRUE }, // 29 ')' + { '8', TRUE }, // 2A '*' + { VK_EQUAL, TRUE }, // 2B '+' + { VK_COMMA, FALSE }, // 2C ',' + { VK_HYPHEN, FALSE }, // 2D '-' + { VK_PERIOD, FALSE }, // 2E '.' + { VK_SLASH, FALSE }, // 2F '/' + + { '0', FALSE }, // 30 '0' + { '1', FALSE }, // 31 '1' + { '2', FALSE }, // 32 '2' + { '3', FALSE }, // 33 '3' + { '4', FALSE }, // 34 '4' + { '5', FALSE }, // 35 '5' + { '6', FALSE }, // 36 '6' + { '7', FALSE }, // 37 '7' + { '8', FALSE }, // 38 '8' + { '9', FALSE }, // 39 '9' + { VK_COLON, TRUE }, // 3A ':' + { VK_COLON, FALSE }, // 3B ';' + { VK_COMMA, TRUE }, // 3C '<' + { VK_EQUAL, FALSE }, // 3D '=' + { VK_PERIOD, TRUE }, // 3E '>' + { VK_SLASH, TRUE }, // 3F '?' + + { '2', TRUE }, // 40 '@' + { 'A', TRUE }, // 41 'A' + { 'B', TRUE }, // 42 'B' + { 'C', TRUE }, // 43 'C' + { 'D', TRUE }, // 44 'D' + { 'E', TRUE }, // 45 'E' + { 'F', TRUE }, // 46 'F' + { 'G', TRUE }, // 47 'G' + { 'H', TRUE }, // 48 'H' + { 'I', TRUE }, // 49 'I' + { 'J', TRUE }, // 4A 'J' + { 'K', TRUE }, // 4B 'K' + { 'L', TRUE }, // 4C 'L' + { 'M', TRUE }, // 4D 'M' + { 'N', TRUE }, // 4E 'N' + { 'O', TRUE }, // 4F 'O' + + { 'P', TRUE }, // 50 'P' + { 'Q', TRUE }, // 51 'Q' + { 'R', TRUE }, // 52 'R' + { 'S', TRUE }, // 53 'S' + { 'T', TRUE }, // 54 'T' + { 'U', TRUE }, // 55 'U' + { 'V', TRUE }, // 56 'V' + { 'W', TRUE }, // 57 'W' + { 'X', TRUE }, // 58 'X' + { 'Y', TRUE }, // 59 'Y' + { 'Z', TRUE }, // 5A 'Z' + { VK_LBRKT, FALSE }, // 5B '[' + { VK_BKSLASH, FALSE }, // 5C '\' + { VK_RBRKT, FALSE }, // 5D ']' + { '6', TRUE }, // 5E '^' + { VK_HYPHEN, TRUE }, // 5F '_' + + { VK_ACCENT, FALSE }, // 60 '`' + { 'A', FALSE }, // 61 'a' + { 'B', FALSE }, // 62 'b' + { 'C', FALSE }, // 63 'c' + { 'D', FALSE }, // 64 'd' + { 'E', FALSE }, // 65 'e' + { 'F', FALSE }, // 66 'f' + { 'G', FALSE }, // 67 'g' + { 'H', FALSE }, // 68 'h' + { 'I', FALSE }, // 69 'i' + { 'J', FALSE }, // 6A 'j' + { 'K', FALSE }, // 6B 'k' + { 'L', FALSE }, // 6C 'l' + { 'M', FALSE }, // 6D 'm' + { 'N', FALSE }, // 6E 'n' + { 'O', FALSE }, // 6F 'o' + + { 'P', FALSE }, // 70 'p' + { 'Q', FALSE }, // 71 'q' + { 'R', FALSE }, // 72 'r' + { 'S', FALSE }, // 73 's' + { 'T', FALSE }, // 74 't' + { 'U', FALSE }, // 75 'u' + { 'V', FALSE }, // 76 'v' + { 'W', FALSE }, // 77 'w' + { 'X', FALSE }, // 78 'x' + { 'Y', FALSE }, // 79 'y' + { 'Z', FALSE }, // 7A 'z' + { VK_LBRKT, TRUE }, // 7B '{' + { VK_BKSLASH, TRUE }, // 7C '|' + { VK_RBRKT, TRUE }, // 7D '}' + { VK_ACCENT, TRUE } // 7E '~' +}; + + +KMX_BOOL km::kbp::kmx::MapUSCharToVK(KMX_WORD ch, PKMX_WORD puKey, PKMX_DWORD puShiftFlags) { + assert(puKey != NULL); + assert(puShiftFlags != NULL); + if (ch >= 0x20 && ch < 0x7F) { + *puKey = USCharMap[ch - 0x20].key; + *puShiftFlags = ISVIRTUALKEY | (USCharMap[ch - 0x20].shift ? K_SHIFTFLAG : 0); + return TRUE; + } + return FALSE; +} diff --git a/common/core/desktop/src/kmx/kmx_conversion.h b/common/core/desktop/src/kmx/kmx_conversion.h new file mode 100644 index 0000000000..dacfa58e27 --- /dev/null +++ b/common/core/desktop/src/kmx/kmx_conversion.h @@ -0,0 +1,36 @@ +#pragma once + +#include "kmx_base.h" + +namespace km { +namespace kbp { +namespace kmx { + +#define VK_SPACE 0x20 +#define VK_COLON 0xBA +#define VK_EQUAL 0xBB +#define VK_COMMA 0xBC +#define VK_HYPHEN 0xBD +#define VK_PERIOD 0xBE +#define VK_SLASH 0xBF +#define VK_ACCENT 0xC0 +#define VK_LBRKT 0xDB +#define VK_BKSLASH 0xDC +#define VK_RBRKT 0xDD +#define VK_QUOTE 0xDE +#define VK_xDF 0xDF + +/** + * This maps an ASCII character corresponding to a US EN key cap to a + * Virtual Key and correctly sets the Shift BIT in the ShiftFlag DWORD. + * + * @param ch The character to be mapped + * @param puKey [in,out] Converted virtual key if ch was a ASCII character + * @param puShiftFlags [in,out] K_SHIFTFLAG BIT will be maybe modified as result of conversion + * @return KMX_BOOL True if conversion occurred + */ +KMX_BOOL MapUSCharToVK(KMX_WORD ch, PKMX_WORD puKey, PKMX_DWORD puShiftFlags); + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/core/desktop/src/kmx/kmx_file.cpp b/common/core/desktop/src/kmx/kmx_file.cpp index dfcac5f890..de78178c63 100644 --- a/common/core/desktop/src/kmx/kmx_file.cpp +++ b/common/core/desktop/src/kmx/kmx_file.cpp @@ -3,6 +3,7 @@ Authors: mcdurdin */ #include "kmx_processevent.h" +#include using namespace km::kbp; using namespace kmx; @@ -18,6 +19,37 @@ KMX_BOOL KMX_ProcessEvent::Load(km_kbp_path_name KeyboardName) return TRUE; } +const int km::kbp::kmx::CODE__SIZE[] = { + -1, // undefined 0x00 + 1, // CODE_ANY 0x01 + 2, // CODE_INDEX 0x02 + 0, // CODE_CONTEXT 0x03 + 0, // CODE_NUL 0x04 + 1, // CODE_USE 0x05 + 0, // CODE_RETURN 0x06 + 0, // CODE_BEEP 0x07 + 1, // CODE_DEADKEY 0x08 + -1, // unused 0x09 + 2, // CODE_EXTENDED 0x0A + -1, // CODE_EXTENDEDEND 0x0B (unused) + 1, // CODE_SWITCH 0x0C + -1, // CODE_KEY 0x0D (never used) + 0, // CODE_CLEARCONTEXT 0x0E + 1, // CODE_CALL 0x0F + -1, // UC_SENTINEL_EXTENDEDEND 0x10 (not valid with UC_SENTINEL) + 1, // CODE_CONTEXTEX 0x11 + 1, // CODE_NOTANY 0x12 + 2, // CODE_SETOPT 0x13 + 3, // CODE_IFOPT 0x14 + 1, // CODE_SAVEOPT 0x15 + 1, // CODE_RESETOPT 0x16 + 3, // CODE_IFSYSTEMSTORE 0x17 + 2 // CODE_SETSYSTEMSTORE 0x18 +}; + +// Ensure that all CODE_### sizes are defined +static_assert(sizeof(CODE__SIZE) / sizeof(CODE__SIZE[0]) == (CODE_LASTCODE + 1), "Size of array CODE__SIZE not correct"); + const unsigned long CRCTable[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, @@ -51,8 +83,7 @@ const unsigned long CRCTable[256] = { 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d -}; - +}; /* * This routine calculates the CRC for a block of data using the * table lookup method. It accepts an original value for the crc, diff --git a/common/core/desktop/src/kmx/kmx_file.h b/common/core/desktop/src/kmx/kmx_file.h index 15de5e985f..5771b19999 100644 --- a/common/core/desktop/src/kmx/kmx_file.h +++ b/common/core/desktop/src/kmx/kmx_file.h @@ -42,9 +42,11 @@ namespace kmx { #define VERSION_80 0x00000800 #define VERSION_90 0x00000900 #define VERSION_100 0x00000A00 +#define VERSION_140 0x00000E00 +#define VERSION_150 0x00000F00 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_100 +#define VERSION_MAX VERSION_150 // // Backspace types @@ -273,6 +275,9 @@ typedef COMP_STORE *PCOMP_STORE; typedef COMP_KEY *PCOMP_KEY; typedef COMP_GROUP *PCOMP_GROUP; +extern const int CODE__SIZE[]; +#define CODE__SIZE_MAX 5 + #define KEYBOARDFILEHEADER_SIZE 64 #define KEYBOARDFILESTORE_SIZE 12 #define KEYBOARDFILEGROUP_SIZE 24 diff --git a/common/core/desktop/src/kmx/kmx_processevent.cpp b/common/core/desktop/src/kmx/kmx_processevent.cpp index e6d5817bc5..21dd5b2271 100644 --- a/common/core/desktop/src/kmx/kmx_processevent.cpp +++ b/common/core/desktop/src/kmx/kmx_processevent.cpp @@ -764,7 +764,7 @@ KMX_Environment const *KMX_ProcessEvent::GetEnvironment() const { return &m_environment; } -LPINTKEYBOARDINFO KMX_ProcessEvent::GetKeyboard() { +INTKEYBOARDINFO const *KMX_ProcessEvent::GetKeyboard() const { return &m_keyboard; } diff --git a/common/core/desktop/src/kmx/kmx_processevent.h b/common/core/desktop/src/kmx/kmx_processevent.h index e988556cd4..41faa6c7ec 100644 --- a/common/core/desktop/src/kmx/kmx_processevent.h +++ b/common/core/desktop/src/kmx/kmx_processevent.h @@ -8,8 +8,10 @@ #include "kmx_base.h" #include "kmx_file.h" #include "kmx_context.h" +#include "kmx_conversion.h" #include "kmx_actions.h" #include "kmx_xstring.h" +#include "kmx_actions.h" #include "kmx_options.h" #include "kmx_environment.h" #include "kmx_debugger.h" @@ -98,7 +100,7 @@ public: KMX_Options const *GetOptions() const; KMX_Environment *GetEnvironment(); KMX_Environment const *GetEnvironment() const; - LPINTKEYBOARDINFO GetKeyboard(); + INTKEYBOARDINFO const *GetKeyboard() const; }; inline KMX_BOOL KMX_ProcessEvent::IsCapsLockOn(KMX_DWORD modifiers) { diff --git a/common/core/desktop/src/kmx/kmx_processor.cpp b/common/core/desktop/src/kmx/kmx_processor.cpp index 2e0ac04b6b..1eb3828186 100644 --- a/common/core/desktop/src/kmx/kmx_processor.cpp +++ b/common/core/desktop/src/kmx/kmx_processor.cpp @@ -1,6 +1,7 @@ #include #include "state.hpp" #include "kmx/kmx_processor.hpp" +#include using namespace km::kbp; using namespace kmx; @@ -194,3 +195,48 @@ constexpr km_kbp_attr const engine_attrs = { km_kbp_attr const & kmx_processor::attributes() const { return engine_attrs; } + +km_kbp_keyboard_key * kmx_processor::get_key_list() const { + // Iterate through the groups and get the rules with virtual keys + // and store the key along with the modifer. + const uint32_t group_cnt = _kmx.GetKeyboard()->Keyboard->cxGroupArray; + const LPGROUP group_array = _kmx.GetKeyboard()->Keyboard->dpGroupArray; + GROUP *p_group; + + std::map, uint32_t> map_rules; + km_kbp_virtual_key v_key; + uint32_t modifier_flag; +// Use hash map to get the unique list + for(auto i = decltype(group_cnt){0}; i < group_cnt; i++) + { + p_group = &group_array[i]; + if(p_group->fUsingKeys) + { + for(auto j = decltype(p_group->cxKeyArray){0}; j < p_group->cxKeyArray; j++) + { + v_key = p_group->dpKeyArray[j].Key; + modifier_flag = p_group->dpKeyArray[j].ShiftFlags; + if(modifier_flag == 0) { + // This must be a ASCII character corresponding US Keyboard key cap + if(!MapUSCharToVK(v_key, &v_key, &modifier_flag)) continue; + } + map_rules[std::make_pair(v_key,modifier_flag)] = (modifier_flag & K_MODIFIERFLAG); // Clear kmx special flags + + } + } + } + // Now convert to the keyboard key array + km_kbp_keyboard_key *rules = new km_kbp_keyboard_key[map_rules.size() + 1]; + std::map, uint32_t>::iterator it = map_rules.begin(); + int n = 0; + while (it != map_rules.end()){ + auto pair = it->first; + rules[n].key = pair.first; + rules[n].modifier_flag = it->second; + it++; + n++; + } + // Insert list termination + rules[n] = KM_KBP_KEYBOARD_KEY_LIST_END; + return rules; +} diff --git a/common/core/desktop/src/kmx/kmx_processor.hpp b/common/core/desktop/src/kmx/kmx_processor.hpp index c4303355dd..ef51ba4ee2 100644 --- a/common/core/desktop/src/kmx/kmx_processor.hpp +++ b/common/core/desktop/src/kmx/kmx_processor.hpp @@ -48,6 +48,14 @@ namespace kbp std::u16string const & key, std::u16string const & value ) override; + + /** + * Get the list of keys belonging to the keyboard rules. + * + * @return km_kbp_keyboard_key* + */ + km_kbp_keyboard_key * get_key_list() const override; + }; } // namespace kbp diff --git a/common/core/desktop/src/kmx/kmx_xstring.cpp b/common/core/desktop/src/kmx/kmx_xstring.cpp index 904e24597f..25235eddbb 100644 --- a/common/core/desktop/src/kmx/kmx_xstring.cpp +++ b/common/core/desktop/src/kmx/kmx_xstring.cpp @@ -9,8 +9,6 @@ #include "kmx_processevent.h" #include "utfcodec.hpp" - - using namespace km::kbp; using namespace kmx; @@ -116,49 +114,53 @@ km_kbp_cp *km::kbp::kmx::u16tok(km_kbp_cp *p, km_kbp_cp ch, km_kbp_cp **ctx) { * * xstrlen calculates the length of a string, ignoring some special chars. */ +PKMX_WCHAR km::kbp::kmx::incxstr(PKMX_WCHAR p) { -PKMX_WCHAR km::kbp::kmx::incxstr(PKMX_WCHAR p) -{ - if(*p == 0) return p; - if(*p != UC_SENTINEL) - { - if(*p >= 0xD800 && *p <= 0xDBFF && *(p+1) >= 0xDC00 && *(p+1) <= 0xDFFF) return p+2; - return p+1; + if (*p == 0) + return p; + if (*p != UC_SENTINEL) { + if (*p >= 0xD800 && *p <= 0xDBFF && *(p + 1) >= 0xDC00 && *(p + 1) <= 0xDFFF) + return p + 2; + return p + 1; + } + // UC_SENTINEL(FFFF) with UC_SENTINEL_EXTENDEDEND(0x10) == variable length + if (*(p + 1) == CODE_EXTENDED) { + p += 2; + while (*p && *p != UC_SENTINEL_EXTENDEDEND) + p++; + + if (*p == 0) return p; + return p + 1; } - p+=2; - switch(*(p-1)) - { - case CODE_ANY: return p+1; - case CODE_NOTANY: return p+1; - case CODE_INDEX: return p+2; - case CODE_USE: return p+1; - case CODE_DEADKEY: return p+1; - case CODE_EXTENDED: p += 2; while(*p && *p != UC_SENTINEL_EXTENDEDEND) p++; return p+1; - case CODE_CLEARCONTEXT: return p+1; - case CODE_CALL: return p+1; - case CODE_CONTEXTEX: return p+1; - case CODE_IFOPT: return p+3; - case CODE_IFSYSTEMSTORE: return p+3; - case CODE_SETOPT: return p+2; - case CODE_SETSYSTEMSTORE: return p+2; - case CODE_RESETOPT: return p+1; - case CODE_SAVEOPT: return p+1; - default: return p; + if (*(p + 1) > CODE_LASTCODE || CODE__SIZE[*(p + 1)] == -1) { + return p + 1; } + + int deltaptr = 2 + CODE__SIZE[*(p + 1)]; + + // check for \0 between UC_SENTINEL(FFFF) and next printable character + for (int i = 0; i < deltaptr; i++) { + if (*p == 0) + return p; + p++; + } + return p; } PKMX_WCHAR km::kbp::kmx::decxstr(PKMX_WCHAR p, PKMX_WCHAR pStart) { + PKMX_WCHAR q; + if(p <= pStart) { return NULL; } p--; - if(*p == UC_SENTINEL_EXTENDEDEND) - { + if(*p == UC_SENTINEL_EXTENDEDEND) { int n = 0; - while(*p != UC_SENTINEL && n < 10) { p--; n++; } + while (p >= pStart && *p != UC_SENTINEL && n < 10) { + p--; n++; } if(p < pStart) { // May be a malformed virtual key @@ -167,49 +169,26 @@ PKMX_WCHAR km::kbp::kmx::decxstr(PKMX_WCHAR p, PKMX_WCHAR pStart) return p; } - if(p == pStart) return p; // Don't allow test before pStart + if (p == pStart) { + // Don't allow test before pStart + return p; + } - if(*p >= 0xDC00 && *p <= 0xDFFF && *(p-1) >= 0xD800 && *(p-1) <= 0xDBFF) - { + if(*p >= 0xDC00 && *p <= 0xDFFF && *(p-1) >= 0xD800 && *(p-1) <= 0xDBFF) { return p-1; } - else if(*(p-1) == UC_SENTINEL) return p-1; - else if(p > pStart+1 && *(p-2) == UC_SENTINEL) - { - switch(*(p-1)) - { - case CODE_ANY: - case CODE_NOTANY: - case CODE_USE: - case CODE_DEADKEY: - case CODE_CLEARCONTEXT: - case CODE_CALL: - case CODE_CONTEXTEX: - case CODE_RESETOPT: - case CODE_SAVEOPT: - return p-2; - } + + // Look for a UC_SENTINEL to jump to + // note: If we are pointing to the middle of a UC_SENTINEL CODE_x, then we won't treat it as valid, + // and will just go back a single wchar + q = p; + for (int i = 0; i < CODE__SIZE_MAX && q >= pStart; i++, q--) { + // *q == UC_SENTINEL && *(q + 1) is within CODE__SIZE && next CODE_ right of UC_SENTINEL ( looked up in CODE__SIZE+1) has value i + if (*q == UC_SENTINEL && *(q + 1) <= CODE_LASTCODE && CODE__SIZE[*(q + 1)] + 1 == i) + return q; } - else if(p > pStart+2 && *(p-3) == UC_SENTINEL) - { - switch(*(p-2)) - { - case CODE_INDEX: - case CODE_SETOPT: - case CODE_SETSYSTEMSTORE: - return p-3; - } - } - else if(p > pStart+3 && *(p-4) == UC_SENTINEL) - { - switch(*(p-3)) - { - case CODE_IFOPT: - case CODE_IFSYSTEMSTORE: // I3432 - return p-4; - } - } - return p; + + return p; } int km::kbp::kmx::xstrlen_ignoreifopt(PKMX_WCHAR p) @@ -262,7 +241,6 @@ PKMX_WCHAR km::kbp::kmx::strtowstr(PKMX_CHAR in) return result; } - PKMX_CHAR km::kbp::kmx::wstrtostr(PKMX_WCHAR in) { PKMX_CHAR result; diff --git a/common/core/desktop/src/kmx/kmx_xstring.h b/common/core/desktop/src/kmx/kmx_xstring.h index 22457cb245..410fb6cc11 100644 --- a/common/core/desktop/src/kmx/kmx_xstring.h +++ b/common/core/desktop/src/kmx/kmx_xstring.h @@ -35,6 +35,8 @@ int u16icmp(const km_kbp_cp *p, const km_kbp_cp *q); int u16ncmp(const km_kbp_cp *p, const km_kbp_cp *q, size_t count); km_kbp_cp *u16tok(km_kbp_cp *p, km_kbp_cp ch, km_kbp_cp **ctx); +//KMX_BOOL MapUSCharToVK(KMX_WORD ch, PKMX_WORD puKey, PKMX_DWORD puShiftFlags); + } // namespace kmx } // namespace kbp } // namespace km diff --git a/common/core/desktop/src/meson.build b/common/core/desktop/src/meson.build index 4fea934c98..8dc1440d4e 100644 --- a/common/core/desktop/src/meson.build +++ b/common/core/desktop/src/meson.build @@ -78,6 +78,7 @@ lib = library('kmnkbp0', 'kmx/kmx_actions.cpp', 'kmx/kmx_capslock.cpp', 'kmx/kmx_context.cpp', + 'kmx/kmx_conversion.cpp', 'kmx/kmx_debug.cpp', 'kmx/kmx_debugger.cpp', 'kmx/kmx_environment.cpp', diff --git a/common/core/desktop/src/mock/mock_processor.cpp b/common/core/desktop/src/mock/mock_processor.cpp index 489c5e4f05..eff0ed7634 100644 --- a/common/core/desktop/src/mock/mock_processor.cpp +++ b/common/core/desktop/src/mock/mock_processor.cpp @@ -182,6 +182,10 @@ namespace km { return engine_attrs; } + km_kbp_keyboard_key * mock_processor::get_key_list() const { + km_kbp_keyboard_key* key_list = new km_kbp_keyboard_key(KM_KBP_KEYBOARD_KEY_LIST_END); + return key_list; + } km_kbp_status mock_processor::validate() const { return KM_KBP_STATUS_OK; } km_kbp_status null_processor::validate() const { return KM_KBP_STATUS_INVALID_ARGUMENT; } diff --git a/common/core/desktop/src/mock/mock_processor.hpp b/common/core/desktop/src/mock/mock_processor.hpp index 5562060580..6b90bec99f 100644 --- a/common/core/desktop/src/mock/mock_processor.hpp +++ b/common/core/desktop/src/mock/mock_processor.hpp @@ -50,6 +50,9 @@ namespace kbp std::u16string const & key, std::u16string const & value ) override; + + km_kbp_keyboard_key * get_key_list() const override; + }; class null_processor : public mock_processor { diff --git a/common/core/desktop/src/processor.hpp b/common/core/desktop/src/processor.hpp index 8ff60a14b6..501553bc71 100644 --- a/common/core/desktop/src/processor.hpp +++ b/common/core/desktop/src/processor.hpp @@ -60,6 +60,9 @@ namespace kbp std::u16string const & value ) = 0; + virtual km_kbp_keyboard_key * + get_key_list() const = 0; + friend json & operator << (json &j, abstract_processor const &opts); }; diff --git a/common/core/desktop/src/rust/rust_mock_processor.cpp b/common/core/desktop/src/rust/rust_mock_processor.cpp index 2a0408127d..d245aaf64c 100644 --- a/common/core/desktop/src/rust/rust_mock_processor.cpp +++ b/common/core/desktop/src/rust/rust_mock_processor.cpp @@ -74,6 +74,10 @@ namespace km { return engine_attrs; } + km_kbp_keyboard_key * rust_mock_processor::get_key_list() const { + km_kbp_keyboard_key* key_list = new km_kbp_keyboard_key(KM_KBP_KEYBOARD_KEY_LIST_END); + return key_list; + } km_kbp_status rust_mock_processor::validate() const { return KM_KBP_STATUS_OK; } km_kbp_status rust_null_processor::validate() const { return KM_KBP_STATUS_INVALID_ARGUMENT; } diff --git a/common/core/desktop/src/rust/rust_mock_processor.hpp b/common/core/desktop/src/rust/rust_mock_processor.hpp index d327bd0410..56e407ff50 100644 --- a/common/core/desktop/src/rust/rust_mock_processor.hpp +++ b/common/core/desktop/src/rust/rust_mock_processor.hpp @@ -47,6 +47,9 @@ namespace kbp std::u16string const & key, std::u16string const & value ) override; + + km_kbp_keyboard_key * get_key_list() const override; + }; class rust_null_processor : public rust_mock_processor { diff --git a/common/core/desktop/tests/meson.build b/common/core/desktop/tests/meson.build index 7497fb83d3..3b33c1ef23 100644 --- a/common/core/desktop/tests/meson.build +++ b/common/core/desktop/tests/meson.build @@ -21,8 +21,4 @@ if get_option('default_library') != 'static' endif endif -subdir('unit/json') -subdir('unit/utftest') -subdir('unit/kmnkbd') -subdir('unit/kmx') -subdir('unit/rust_mock') \ No newline at end of file +subdir('unit') diff --git a/common/core/desktop/tests/unit/kmnkbd/keyboard_api.cpp b/common/core/desktop/tests/unit/kmnkbd/keyboard_api.cpp index 67d59a571c..352230e357 100644 --- a/common/core/desktop/tests/unit/kmnkbd/keyboard_api.cpp +++ b/common/core/desktop/tests/unit/kmnkbd/keyboard_api.cpp @@ -23,13 +23,17 @@ int main(int, char *[]) { km_kbp_keyboard * test_kb = nullptr; km_kbp_keyboard_attrs const * kb_attrs = nullptr; + km_kbp_keyboard_key * kb_key_list = nullptr; try_status(km_kbp_keyboard_load(test_kb_path.c_str(), &test_kb)); try_status(km_kbp_keyboard_get_attrs(test_kb, &kb_attrs)); + try_status(km_kbp_keyboard_get_key_list(test_kb,&kb_key_list)); + if (kb_attrs->folder_path != test_kb_path.parent()) return __LINE__; km_kbp_keyboard_dispose(test_kb); + km_kbp_keyboard_key_list_dispose(kb_key_list); return 0; } diff --git a/common/core/desktop/tests/unit/kmnkbd/meson.build b/common/core/desktop/tests/unit/kmnkbd/meson.build index 197c54fcde..4c9a52eae6 100644 --- a/common/core/desktop/tests/unit/kmnkbd/meson.build +++ b/common/core/desktop/tests/unit/kmnkbd/meson.build @@ -2,7 +2,6 @@ # Description: Cross platform build script to compile libkmnkbp API unit tests. # Create Date: 19 Oct 2018 # Authors: Tim Eves (TSE) -# History: 19 Oct 2018 - TSE - Added test for context API functions. # defns=['-DKMN_KBP_STATIC'] @@ -12,6 +11,7 @@ tests = [ ['options-api', 'options_api.cpp'], ['state-api', 'state_api.cpp'], ['debug-api', 'debug_api.cpp'], + ['kmx_xstring', 'test_kmx_xstring.cpp'], ] foreach t : tests diff --git a/common/core/desktop/tests/unit/kmnkbd/test_kmx_xstring.cpp b/common/core/desktop/tests/unit/kmnkbd/test_kmx_xstring.cpp new file mode 100644 index 0000000000..bf730a9759 --- /dev/null +++ b/common/core/desktop/tests/unit/kmnkbd/test_kmx_xstring.cpp @@ -0,0 +1,1231 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Keyman Core - KMX Extended String unit tests + */ + +#include +#include +#include +#include +#include +#include +#include "../../../src/kmx/kmx_xstring.h" +#include "../../../src/kmx/kmx_file.h" +#include "../test_assert.h" + +using namespace km::kbp::kmx; +using namespace std; + +PKMX_WCHAR find_ptr_to_last_character(PKMX_WCHAR p_first) { + int length = std::u16string(p_first).length(); + if (length > 0) + return p_first + length - 1; + else + return p_first; +} + + +void test_decxstr() { + + PKMX_WCHAR p_start; // pointer start of input + PKMX_WCHAR p; // pointer end of input + PKMX_WCHAR q; // pointer output + + // ------------------------------------------------------------------------------------------------------------------------------------------------------- + // UC_SENTINEL followed by valid CODE_xx with sufficient & insufficient number of parameters + // ------------------------------------------------------------------------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"\uFFFF\u0001\u0002"; + p = p_start + 2; // \u0002 in the middle of the otherwise valid UC_SENTINEL CODE_ANY sequence + q = decxstr(p, p_start); + assert(q == p - 1); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0001\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p-1)); + + p_start = (PKMX_WCHAR)u"\uFFFF\u0001\u0001"; + p = p_start + 3; // nul, i.e. at the end of the valid UC_SENTINEL CODE_ANY sequence + q = decxstr(p, p_start); + assert(q == p - 3); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0001\u0001\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p-3)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0001\u0001\U0001F609"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p-1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0001\u0001\U0001F609"; + p = find_ptr_to_last_character(p_start)-1; + q = decxstr(p, p_start); + assert(q == (p-3)); + + // ------------------------------------------------------------------------------------------------------------------------------------------------------- + // even more tests: check for use UC_SENTINEL with F000 + // ------------------------------------------------------------------------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p-1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000\u0002"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000\u0001\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000\u0002\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000\u0002\u0001\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\uF000\u0002\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + // ------------------------------------------------------------------------------------------------------------------------------------------------------- + // more tests: check if we might end up left of pstart + // ------------------------------------------------------------------------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (NULL )); + + p_start = (PKMX_WCHAR)u"\u0001\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\u0001\u0001\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\uFFFF\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + // Note: this test puts the pointer into the middle of a valid `UC_SENTINEL CODE_ANY ` + // so we should expect it to not be properly understood. + p_start = (PKMX_WCHAR)u"\uFFFF\u0001\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\uFFFF\u0001\u0001\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3)); + + p_start = (PKMX_WCHAR)u"\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (NULL)); + + p_start = (PKMX_WCHAR)u"\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\u0014\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\u0014\u0014\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\u0014\u0014\u0014\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\u0014\u0014\u0014\u0014\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\uFFFF\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + // 0x14 = CODE_IFOPT which has 3 parameters, so this is an invalid, so + // go back only one char + p_start = (PKMX_WCHAR)u"\uFFFF\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\uFFFF\u0014\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\uFFFF\u0014\u0014\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + p_start = (PKMX_WCHAR)u"\uFFFF\u0014\u0014\u0014\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 5)); + + p_start = (PKMX_WCHAR)u"\uFFFF\u0014\u0014\u0014\u0014\u0014\u0014"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); + + // ------------------------------------------------------------------------------------------------------------------------------------------------------- + // -- differences in pointer movement for new decxstr ---------------------------------------------------------------------------------------------------- + // ----- OLD version of decxstr: 0x0A not used => return ( p - 1) , 0x0C not used => return ( p - 1) , 0x0E = 1 => return ( p - 3) + // ----- NEW version of decxstr: Values set in in CODE__SIZE[]: 0x0A = 2 => return ( p - 4) , 0x0C =1 => return ( p - 3) , 0x0E = 0 => return ( p - 1) + // ------------------------------------------------------------------------------------------------------------------------------------------------------- + + // runs OK with NEW version of decxstr (with CODE_EXTENDED pointer moves 3 ( 4 altogether) + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000A\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 4) ); + + //runs OK with NEW version of decxstr (with CODE_SWITCH pointer moves 2 ( 3 altogether) + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000C\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + // runs OK with NEW version of decxstr (with CODE_CLEARCONTEXT pointer moves 0 ( 1 altogether) + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000E\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + // runs OK with OLD version and NEW version of decxstr + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000E\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 2) ); + + // --------------------------------------------------------------------------------------- + // ---- character + // --------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"abcd"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + + assert(q == (p - 1) ); + + // --------------------------------------------------------------------------------------- + // ---- p <= pstart + // --------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"abc"; + p = find_ptr_to_last_character(p_start)-5; + q = decxstr(p, p_start); + assert(q == (NULL) ); + + p_start = (PKMX_WCHAR)u"a"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (NULL) ); + + // --------------------------------------------------------------------------------------- + // ---- p= UC_SENTINEL_EXTENDED + // --------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000A\u0001\u0002\u0003\u0004\u0010"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0010"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0010d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 8) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0010d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 11) ); + + // --------------------------------------------------------------------------------------- + // ---- Surrogate Pair + // --------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"abc\U0001F609"; + p = find_ptr_to_last_character(p_start)-1; + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\U0001F609"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\U0001F609d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 2) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\U0001F609"; + p = find_ptr_to_last_character(p_start)-1; + q = decxstr(p, p_start); + assert(q == (p-1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\U0001F609"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p-1)); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\U0001F609d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p-2)); + // --------------------------------------------------------------------------------------- + // ---- CODE_ + // --------------------------------------------------------------------------------------- + +#define C_UC_SENTINEL u"\uFFFF" + + for (auto code = 1 /* CODE_ANY */; code <= CODE_LASTCODE; code++) { + if (code == UC_SENTINEL_EXTENDEDEND) { + // We won't try and test that here as it won't assert in the + // same way + continue; + } + auto size = CODE__SIZE[code]; + std::u16string str(u"abc" C_UC_SENTINEL); + str.append(1, code); + str.append(size < 0 ? 0 : size, u'\u0001'); + + p_start = (PKMX_WCHAR) str.c_str(); + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1)); // this is in the middle of the sequence so it should always go back a single unit + + str.append(u"a"); + p_start = (PKMX_WCHAR)str.c_str(); + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + if (size < 0) { + assert(q == (p - 1)); // this is in the middle of the sequence so it should always go back a single unit + } else { + assert(q == (p - size - 2)); /* UC_SENTINEL + code + (CODE__SIZE = number of params) */ + } + } + + // --------------------------------------------------------------------------------------- + // ---- CODE_ followed by letter + // --------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0002\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 4) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0003\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0004\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0005\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0006\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0007\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0008\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u000F\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0011\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0012\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0013\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 4) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0014\u0001\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 5) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0015\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0016\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 3) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0017\u0001\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 5) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0018\u0001\u0001d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 4) ); + + // --------------------------------------------------------------------------------------- + // ---- other + // --------------------------------------------------------------------------------------- + + p_start = (PKMX_WCHAR)u"abc\uFFFF\u0002def\u0001"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"abc\uFFFF"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + // pointer in the middle of a surrogate pair, so beware! + p_start = (PKMX_WCHAR)u"\uFFFF\U0001F609"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"\u0014d"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"\uFFFF"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == NULL ); + + p_start = (PKMX_WCHAR)u"\uFFFFd"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == (p - 1) ); + + p_start = (PKMX_WCHAR)u"\uFFFF\uFFFF"; + p = find_ptr_to_last_character(p_start); + q = decxstr(p, p_start); + assert(q == p - 1 ); +} + + + + void test_incxstr() { + + PKMX_WCHAR p; // pointer input to incxstr() + PKMX_WCHAR q; // pointer output to incxstr() + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- NULL, SURROGATE PAIRS, NON_UC_SENTINEL, ONE CHARACTER --------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for empty string ------------------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR) u"\0"; + q = incxstr(p); + assert(q == p); + + // --- Test for character --------------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR) u"\u1234"; + q = incxstr(p); + assert(q == p+1); + + // --- Test for surrogate pair ---------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR) u"\U0001F609"; + q = incxstr(p); + assert(q == p+2); + + // --- Test for one ----------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\u0012"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF only ------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF"; + q = incxstr(p); + assert(q == p + 1); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL WITHOUT \0 ---------------------------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF +CODE_INDEX -------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0002\u0002\u0001"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_USE ---------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0005\u0001"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_DEADKEY ------------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0008\u0001"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF CODE_EXTENDED -------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0010"; + q = incxstr(p); + assert(q == p + 9); + + // --- Test for FFFF +CODE_CLEARCONTEXT ------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u000E\u0001"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF +CODE_CALL ---------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000F\u0001"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_CONTEXTEX --------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0011\u0001"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_IFOPT ------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0014\u0002\u0002\u0001"; + q = incxstr(p); + assert(q == p + 5); + + // --- Test for FFFF +CODE_IFSYSTEMSTORE ------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0017\u0002\u0002\u0001"; + q = incxstr(p); + assert(q == p + 5); + + // --- Test for FFFF +CODE_SETOPT ---------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0013\u0002\u0001"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_SETSYSTEMRESTORE --------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0018\u0002\u0001"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_RESETOPT ----------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0016\u0001"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_SAVEOPT ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0015\u0001"; + q = incxstr(p); + assert(q == p + 3 ); + + // --- Test for FFFF +default ---------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF + CODE_ANY ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0001\u0001"; + q = incxstr(p); + assert(q == p + 3 ); + + // --- Test for FFFF + CODE_CONTEXT ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0003\u0001"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_RETURN ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0006\u0001"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_BEEP ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0007\u0001"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_SWITCH ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000C\u0001"; + q = incxstr(p); + assert(q == p + 3 ); + + // --- Test for FFFF + NOTANY ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0012\u0001"; + q = incxstr(p); + assert(q == p + 3 ); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL WITH \0 AT DIFFERENT POSITIONS -------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF + control (earlier p+1) with \0 after first position --------------- unit test failed with old version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\0\u0008\u0001"; + q = incxstr(p); + assert(q == p+1); + + // --- Test for FFFF +control (earlier p+1) with \0 after second position --------- unit test failed with old version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\u0008\0\u0001"; + q = incxstr(p); + assert(q == p+2); + + // --- Test for FFFF +control (earlier p+1) with \0 after third position ----- unit test failed with old version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\u0008\u0001\0"; + q = incxstr(p); + assert(q == p+3) + + // --- Test for FFFF +control (earlier p+2) with \0 after fourth position ----- unit test failed with old version of incxstr() ---- + p = (PKMX_WCHAR)u"\uFFFF\u0002\u0001\u0001\0"; + q = incxstr(p); + assert(q == p+4); + + // --- Test for FFFF +control (earlier p+3) with \0 after fifth position ----- unit test failed with old version of incxstr() --------- + p = (PKMX_WCHAR)u"\uFFFF\u0014\u0001\u0001\u0001\0"; + q = incxstr(p); + assert(q == p+5); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 6. position ----- unit test failed with old version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\0\u0005\u0006\u0007\u0010"; + q = incxstr(p); + assert(q == p + 6); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 7. position ----- unit test failed with old version of incxstr() + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\0\u0006\u0007\u0010"; + q = incxstr(p); + assert(q == p + 7); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 8. position ----- unit test failed with old version of incxstr() ---------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\0\u0007\u0010"; + q = incxstr(p); + assert(q == p + 8); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 9. position ----- unit test failed with old version of incxstr() --- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0007\0\u0010"; + q = incxstr(p); + assert(q == p + 9); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 10. position ----- unit test failed with old version of incxstr() ----------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0010\0"; + q = incxstr(p); + assert(q == p + 10); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL, INCOMPLETE & UNUSUAL SEQUENCES-------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF + \0 -------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\0"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF +one character ------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0062"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF +one ----------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF + one + character ------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004\u0062"; + q = incxstr(p); + assert(q == p + 2); + +//------------------------------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------------------------------- +// + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- NULL, SURROGATE PAIRS, NON_UC_SENTINEL, ONE CHARACTER WITH \u1234\u2468 At END + // --------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for empty string + //--------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR) u"\0\u1234\u2468"; + q = incxstr(p); + assert(q == p); + + // --- Test for character + // --------------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\u1234\u1234\u2468"; + q = incxstr(p); + assert(q == p + 1);; + + // --- Test for surrogate pair + // ---------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\U0001F609\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for one + // ----------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\u0012\u1234\u2468"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF only + // ------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u1234\u2468"; + q = incxstr(p); + assert(q == p + 1); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL WITHOUT \0 + // ---------------------------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF +CODE_INDEX + // -------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0002\u0002\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_USE + // ---------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0005\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_DEADKEY + // ------------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0008\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF CODE_EXTENDED + // -------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0010\u1234\u2468"; + q = incxstr(p); + assert(q == p + 9); + + // --- Test for FFFF +CODE_CLEARCONTEXT + // ------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u000E\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF +CODE_CALL + // ---------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000F\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_CONTEXTEX + // --------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0011\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_IFOPT + // ------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0014\u0002\u0002\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 5); + + // --- Test for FFFF +CODE_IFSYSTEMSTORE + // ------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0017\u0002\u0002\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 5); + + // --- Test for FFFF +CODE_SETOPT + // ---------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0013\u0002\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_SETSYSTEMRESTORE + // --------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0018\u0002\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_RESETOPT + // ----------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0016\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_SAVEOPT + // ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0015\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +default + // ---------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF + CODE_ANY ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0001\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3 ); + + // --- Test for FFFF + CODE_CONTEXT ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0003\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_RETURN ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0006\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_BEEP ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0007\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_SWITCH ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000C\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3 ); + + // --- Test for FFFF + NOTANY ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0012\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3 ); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL WITH \0 AT DIFFERENT POSITIONS + // -------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF + control (earlier p+1) with \0 after first position --------------- unit test failed with old version of + // incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\0\u0008\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF +control (earlier p+1) with \0 after second position --------- unit test failed with old version of + // incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\u0008\0\u0001\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF +control (earlier p+1) with \0 after third position ----- unit test failed with old version of incxstr() + // ----- + p = (PKMX_WCHAR)u"\uFFFF\u0008\u0001\0\u1234\u2468"; + q = incxstr(p); + assert(q == p + 3) + + // --- Test for FFFF +control (earlier p+2) with \0 after fourth position ----- unit test failed with old version of + // incxstr() ---- + p = (PKMX_WCHAR)u"\uFFFF\u0002\u0001\u0001\0\u1234\u2468"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +control (earlier p+3) with \0 after fifth position ----- unit test failed with old version of incxstr() + // --------- + p = (PKMX_WCHAR)u"\uFFFF\u0014\u0001\u0001\u0001\0\u1234\u2468"; + q = incxstr(p); + assert(q == p + 5); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 6. position ----- unit test failed with old + // version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\0\u0005\u0006\u0007\u0010\u1234\u2468"; + q = incxstr(p); + assert(q == p + 6); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 7. position ----- unit test failed with old + // version of incxstr() + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\0\u0006\u0007\u0010\u1234\u2468"; + q = incxstr(p); + assert(q == p + 7); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 8. position ----- unit test failed with old + // version of incxstr() ---------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\0\u0007\u0010\u1234\u2468"; + q = incxstr(p); + assert(q == p + 8); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 9. position ----- unit test failed with old + // version of incxstr() --- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0007\0\u0010\u1234\u2468"; + q = incxstr(p); + assert(q == p + 9); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 10. position ----- unit test failed with old + // version of incxstr() ----------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0010\0\u1234\u2468"; + q = incxstr(p); + assert(q == p + 10); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL, INCOMPLETE & UNUSUAL SEQUENCES-------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF + \0 + // -------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\0\u1234\u2468"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF +one character + // ------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0062\u1234\u2468"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF +one + // ----------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF + one + character + // ------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004\u0062\u1234\u2468"; + q = incxstr(p); + assert(q == p + 2); + +//------------------------------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- NULL, SURROGATE PAIRS, NON_UC_SENTINEL, ONE CHARACTER WITH SURROGATE PAIR \U0001F609 At END + // --------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for empty string + //------------------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\0\U0001F609"; + q = incxstr(p); + assert(q == p); + + // --- Test for character + // --------------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\u1234\U0001F609"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for surrogate pair + // ---------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\U0001F609\U0001F609"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for one + // ----------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\u0012\U0001F609"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF only + // ------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\U0001F609"; + q = incxstr(p); + assert(q == p + 1); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL WITHOUT \0 + // ---------------------------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF +CODE_INDEX + // -------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0002\u0002\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_USE + // ---------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0005\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_DEADKEY + // ------------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0008\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF CODE_EXTENDED + // -------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0010\U0001F609"; + q = incxstr(p); + assert(q == p + 9); + + // --- Test for FFFF +CODE_CLEARCONTEXT + // ------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u000E\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF +CODE_CALL + // ---------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000F\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_CONTEXTEX + // --------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0011\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_IFOPT + // ------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0014\u0002\u0002\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 5); + + // --- Test for FFFF +CODE_IFSYSTEMSTORE + // ------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0017\u0002\u0002\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 5); + + // --- Test for FFFF +CODE_SETOPT + // ---------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0013\u0002\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_SETSYSTEMRESTORE + // --------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0018\u0002\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +CODE_RESETOPT + // ----------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0016\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +CODE_SAVEOPT + // ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0015\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3); + + // --- Test for FFFF +default + // ---------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004\U0001F609"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF + CODE_ANY ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0001\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3 ); + + // --- Test for FFFF + CODE_CONTEXT ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0003\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_RETURN ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0006\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_BEEP ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0007\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 2 ); + + // --- Test for FFFF + CODE_SWITCH ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u000C\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3 ); + + // --- Test for FFFF + NOTANY ----------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0012\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 3 ); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL WITH \0 AT DIFFERENT POSITIONS + // -------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF + control (earlier p+1) with \0 after first position --------------- unit test failed + // with old version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\0\u0008\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF +control (earlier p+1) with \0 after second position --------- unit test failed with + // old version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\u0008\0\u0001\U0001F609"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF +control (earlier p+1) with \0 after third position ----- unit test failed with old + // version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\u0008\u0001\0\U0001F609"; + q = incxstr(p); + assert(q == p + 3) + + // --- Test for FFFF +control (earlier p+2) with \0 after fourth position ----- unit test failed with + // old version of incxstr() ---- + p = (PKMX_WCHAR)u"\uFFFF\u0002\u0001\u0001\0\U0001F609"; + q = incxstr(p); + assert(q == p + 4); + + // --- Test for FFFF +control (earlier p+3) with \0 after fifth position ----- unit test failed with old + // version of incxstr() --------- + p = (PKMX_WCHAR)u"\uFFFF\u0014\u0001\u0001\u0001\0\U0001F609"; + q = incxstr(p); + assert(q == p + 5); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 6. position ----- unit test + // failed with old version of incxstr() ----- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\0\u0005\u0006\u0007\u0010\U0001F609"; + q = incxstr(p); + assert(q == p + 6); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 7. position ----- unit test + // failed with old version of incxstr() + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\0\u0006\u0007\u0010\U0001F609"; + q = incxstr(p); + assert(q == p + 7); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 8. position ----- unit test + // failed with old version of incxstr() ---------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\0\u0007\u0010\U0001F609"; + q = incxstr(p); + assert(q == p + 8); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 9. position ----- unit test + // failed with old version of incxstr() --- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0007\0\u0010\U0001F609"; + q = incxstr(p); + assert(q == p + 9); + + // --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 10. position ----- unit test + // failed with old version of incxstr() ----------- + p = (PKMX_WCHAR)u"\uFFFF\u000A\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0010\0\U0001F609"; + q = incxstr(p); + assert(q == p + 10); + + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // ---- UC_SENTINEL, INCOMPLETE & UNUSUAL SEQUENCES-------------------------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------------------------------------------------------------------------- + + // --- Test for FFFF + \0 + // -------------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\0\U0001F609"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF +one character + // ------------------------------------------------------------------------------------------------------------ + p = (PKMX_WCHAR)u"\uFFFF\u0062\U0001F609"; + q = incxstr(p); + assert(q == p + 1); + + // --- Test for FFFF +one + // ----------------------------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004\U0001F609"; + q = incxstr(p); + assert(q == p + 2); + + // --- Test for FFFF + one + character + // ------------------------------------------------------------------------------------------- + p = (PKMX_WCHAR)u"\uFFFF\u0004\u0062\U0001F609"; + q = incxstr(p); + assert(q == p + 2); +} + +constexpr const auto help_str = "\ +test_kmx_xstring [--color]\n\ +\n\ + --color Force color output\n"; + +int error_args() { + std::cerr << "test_kmx_xstring: Invalid arguments." << std::endl; + std::cout << help_str; + return 1; +} + +int main(int argc, char *argv []) { + + auto arg_color = argc > 1 && std::string(argv[1]) == "--color"; + console_color::enabled = console_color::isaterminal() || arg_color; + + test_incxstr(); + test_decxstr(); + + return 0; +} diff --git a/common/core/desktop/tests/unit/kmx/020 - deadkeys and backspace.kmn b/common/core/desktop/tests/unit/kmx/020 - deadkeys and backspace.kmn index b5d34a1624..0a3f97f461 100644 --- a/common/core/desktop/tests/unit/kmx/020 - deadkeys and backspace.kmn +++ b/common/core/desktop/tests/unit/kmx/020 - deadkeys and backspace.kmn @@ -6,14 +6,14 @@ c 4. Two deadkeys in a row in context dk(4a) dk(4b) + BKSP = nul c 5. One char and two deadkeys in context 'a' dk(5a) dk(5b) 'b' + BKSP = 'a' c 6. One char and two deadkeys and one char and two deadkeys in context 'a' dk(6a) dk(6b) 'b' dk(6c) dk(6d) + BKSP = 'a' c keys: [K_1][K_BKSP][K_2][K_BKSP][K_3][K_BKSP][K_4][K_BKSP][K_5][K_BKSP][K_6][K_BKSP] -c expected: 12aa -c context: 1234 +c expected: 78aa +c context: 7890 store(&VERSION) '9.0' begin unicode > use(main) -group(main) using keys +group(main) using keys + '1' > dk(1) + '2' > 'a' dk(2) diff --git a/common/core/desktop/tests/unit/kmx/kmp.json b/common/core/desktop/tests/unit/kmx/kmp.json deleted file mode 100644 index 29237b2de3..0000000000 --- a/common/core/desktop/tests/unit/kmx/kmp.json +++ /dev/null @@ -1,416 +0,0 @@ -{ - "system": { - "keymanDeveloperVersion": "10.0.1099.0", - "fileVersion": "7.0" - }, - "options": { - "readmeFile": "kmx.cpp" - }, - "info": { - "name": { - "description": "Test Keyboards" - }, - "version": { - "description": "0.0" - }, - "copyright": { - "description": "\u00A9 2018 SIL International" - }, - "author": { - "description": "" - } - }, - "files": [ - { - "name": "kmp.json", - "description": "Package information (JSON)" - } - ], - "keyboards": [ - { - "name": "000 - null keyboard", - "id": "000 - null keyboard", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "001 - basic input UnicodeI", - "id": "001 - basic input UnicodeI", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "002 - basic input Unicode", - "id": "002 - basic input Unicode", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "003 - nul", - "id": "003 - nul", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "004 - basic input (shift 2)", - "id": "004 - basic input (shift 2)", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "005 - nul with initial context", - "id": "005 - nul with initial context", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "006 - vkey input (shift ctrl)", - "id": "006 - vkey input (shift ctrl)", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "007 - vkey input (ctrl alt)", - "id": "007 - vkey input (ctrl alt)", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "008 - vkey input (ctrl alt 2)", - "id": "008 - vkey input (ctrl alt 2)", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "012 - ralt", - "id": "012 - ralt", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "013 - deadkeys", - "id": "013 - deadkeys", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "014 - groups and virtual keys", - "id": "014 - groups and virtual keys", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "015 - ralt 2", - "id": "015 - ralt 2", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "017 - space mnemonic kbd", - "id": "017 - space mnemonic kbd", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "018 - nul testing", - "id": "018 - nul testing", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "019 - multiple deadkeys", - "id": "019 - multiple deadkeys", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "020 - deadkeys and backspace", - "id": "020 - deadkeys and backspace", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "021 - options", - "id": "021 - options", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "022 - options with preset", - "id": "022 - options with preset", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "023 - options with save", - "id": "023 - options with save", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "024 - options with save and preset", - "id": "024 - options with save and preset", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "025 - options with reset", - "id": "025 - options with reset", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "026 - system stores", - "id": "026 - system stores", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "027 - system stores 2", - "id": "027 - system stores 2", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "028 - smp", - "id": "028 - smp", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "029 - beep", - "id": "029 - beep", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "030 - multiple groups", - "id": "030 - multiple groups", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "031 - caps lock", - "id": "031 - caps lock", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "032 - caps control", - "id": "032 - caps control", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "033 - caps always off", - "id": "033 - caps always off", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "038 - punctkeys", - "id": "038 - punctkeys", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "039 - generic ctrlalt", - "id": "039 - generic ctrlalt", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "040 - long context", - "id": "040 - long context", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "041 - long context and deadkeys", - "id": "041 - long context and deadkeys", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - }, - { - "name": "042 - long context and split deadkeys", - "id": "042 - long context and split deadkeys", - "version": "0.0", - "languages": [ - { - "name": "Undetermined", - "id": "und" - } - ] - } - ] -} diff --git a/common/core/desktop/tests/unit/kmx/kmp.json.in b/common/core/desktop/tests/unit/kmx/kmp.json.in new file mode 100644 index 0000000000..896b276184 --- /dev/null +++ b/common/core/desktop/tests/unit/kmx/kmp.json.in @@ -0,0 +1,32 @@ +{ + "system": { + "keymanDeveloperVersion": "10.0.1099.0", + "fileVersion": "7.0" + }, + "options": { + "readmeFile": "kmx.cpp" + }, + "info": { + "name": { + "description": "Test Keyboards" + }, + "version": { + "description": "0.0" + }, + "copyright": { + "description": "\u00A9 2018 SIL International" + }, + "author": { + "description": "" + } + }, + "files": [ + { + "name": "kmp.json", + "description": "Package information (JSON)" + } + ], + "keyboards": [ + @keyboards@ + ] +} diff --git a/common/core/desktop/tests/unit/kmx/kmx.cpp b/common/core/desktop/tests/unit/kmx/kmx.cpp index 503547c99c..f3d124b645 100644 --- a/common/core/desktop/tests/unit/kmx/kmx.cpp +++ b/common/core/desktop/tests/unit/kmx/kmx.cpp @@ -27,159 +27,73 @@ #include "../test_assert.h" #include "../test_color.h" -namespace -{ +#include "kmx_test_source.hpp" + +namespace { + bool g_beep_found = false; -bool g_caps_lock_on = false; - -struct key_event { - km_kbp_virtual_key vk; - uint16_t modifier_state; -}; - -typedef enum { - KOT_INPUT, - KOT_OUTPUT, - KOT_SAVED -} kmx_option_type; - -struct kmx_option { - kmx_option_type type; - std::u16string key, value, saved_value; -}; - -using kmx_options = std::vector; - -int load_source(const km::kbp::path &, std::string &, std::u16string &, - std::u16string &, kmx_options &, bool &); -std::string string_to_hex(const std::u16string& input); km_kbp_option_item test_env_opts[] = { KM_KBP_OPTIONS_END }; -// String trim functions from https://stackoverflow.com/a/217605/1836776 -// trim from start (in place) -static inline void ltrim(std::string &s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { - return !std::isspace(ch); - })); -} +std::string +string_to_hex(const std::u16string &input) { + std::ostringstream result; + result << std::setfill('0') << std::hex << std::uppercase; -// trim from end (in place) -static inline void rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { - return !std::isspace(ch); - }).base(), s.end()); -} - -// trim from both ends (in place) -static inline void trim(std::string &s) { - ltrim(s); - rtrim(s); -} - -key_event char_to_event(char ch) { - assert(ch >= 32 && ch <= 127); - return { - km::kbp::kmx::s_char_to_vkey[(int)ch - 32].vk, - (uint16_t)(km::kbp::kmx::s_char_to_vkey[(int)ch - 32].shifted ? KM_KBP_MODIFIER_SHIFT : 0) - }; -} - -uint16_t get_modifier(std::string const m) { - for (int i = 0; km::kbp::kmx::s_modifier_names[i].name; i++) { - if (m == km::kbp::kmx::s_modifier_names[i].name) { - return km::kbp::kmx::s_modifier_names[i].modifier; + for (size_t i = 0; i < input.length(); i++) { + unsigned int ch = input[i]; + if (i < input.length() - 1 && Uni_IsSurrogate1(input[i]) && Uni_IsSurrogate2(input[i + 1])) { + ch = Uni_SurrogateToUTF32(input[i], input[i + 1]); + i++; } + + result << "U+" << std::setw(4) << ch << " "; } - return 0; + return result.str(); } -km_kbp_virtual_key get_vk(std::string const & vk) { - for (int i = 1; i < 256; i++) { - if (vk == km::kbp::kmx::s_key_names[i]) { - return i; - } - } - return 0; -} - -key_event vkey_to_event(std::string const & vk_event) { - // vkey format is MODIFIER MODIFIER K_NAME - //std::cout << "VK=" << vk_event << std::endl; - - std::stringstream f(vk_event); - std::string s; - uint16_t modifier_state = 0; - km_kbp_virtual_key vk = 0; - while(std::getline(f, s, ' ')) { - uint16_t modifier = get_modifier(s); - if (modifier != 0) { - modifier_state |= modifier; - } - else { - vk = get_vk(s); - break; - } - } - - // The string should be empty at this point - assert(!std::getline(f, s, ' ')); - assert(vk != 0); - - return { - vk, - modifier_state - }; -} - -key_event next_key(std::string &keys) { - // Parse the next element of the string, chop it off, and return it - if (keys.length() == 0) return { 0, 0 }; - char ch = keys[0]; - if (ch == '[') { - if (keys.length() > 1 && keys[1] == '[') { - keys.erase(0, 2); - return char_to_event(ch); - } - auto n = keys.find(']'); - assert(n != std::string::npos); - auto vkey = keys.substr(1, n - 1); - keys.erase(0, n+1); - return vkey_to_event(vkey); - } - else { - keys.erase(0, 1); - return char_to_event(ch); - } -} - -void apply_action(km_kbp_state const *, km_kbp_action_item const & act, std::u16string & text_store, std::vector & context, kmx_options &options) { - switch (act.type) - { +void +apply_action( + km_kbp_state const *, + km_kbp_action_item const &act, + std::u16string &text_store, + std::vector &context, + km::tests::kmx_options &options, + km::tests::KmxTestSource &test_source) { + switch (act.type) { case KM_KBP_IT_END: assert(false); break; case KM_KBP_IT_ALERT: g_beep_found = true; - //std::cout << "beep" << std::endl; + // std::cout << "beep" << std::endl; break; case KM_KBP_IT_CHAR: - context.push_back(km_kbp_context_item{KM_KBP_CT_CHAR, {0,}, {act.character}}); + context.push_back(km_kbp_context_item{ + KM_KBP_CT_CHAR, + { + 0, + }, + {act.character}}); if (Uni_IsSMP(act.character)) { text_store.push_back(Uni_UTF32ToSurrogate1(act.character)); text_store.push_back(Uni_UTF32ToSurrogate2(act.character)); - } - else { + } else { text_store.push_back(act.character); } - //std::cout << "char(" << act.character << ") size=" << cp->size() << std::endl; + // std::cout << "char(" << act.character << ") size=" << cp->size() << std::endl; break; case KM_KBP_IT_MARKER: - //std::cout << "deadkey(" << act.marker << ")" << std::endl; - context.push_back(km_kbp_context_item{KM_KBP_CT_MARKER, {0,}, {(uint32_t)act.marker}}); + // std::cout << "deadkey(" << act.marker << ")" << std::endl; + context.push_back(km_kbp_context_item{ + KM_KBP_CT_MARKER, + { + 0, + }, + {(uint32_t)act.marker}}); break; case KM_KBP_IT_BACK: // It is valid for a backspace to be received with an empty text store @@ -188,18 +102,17 @@ void apply_action(km_kbp_state const *, km_kbp_action_item const & act, std::u16 // processing at start of a text store, e.g. delete from a previous cell // in a table. Or, if Keyman has a cached context, then there may be // additional text in the text store that Keyman can't see. - if(act.backspace.expected_type == KM_KBP_BT_MARKER) { + if (act.backspace.expected_type == KM_KBP_BT_MARKER) { assert(!context.empty()); assert(context.back().type == KM_KBP_CT_MARKER); context.pop_back(); - } - else if (text_store.length() > 0) { + } else if (text_store.length() > 0) { assert(!context.empty() && !text_store.empty()); km_kbp_usv ch = text_store.back(); text_store.pop_back(); - if(text_store.length() > 0 && Uni_IsSurrogate2(ch)) { + if (text_store.length() > 0 && Uni_IsSurrogate2(ch)) { auto ch1 = text_store.back(); - if(Uni_IsSurrogate1(ch1)) { + if (Uni_IsSurrogate1(ch1)) { // We'll only pop the next character off it is actually a // surrogate pair ch = Uni_SurrogateToUTF32(ch1, ch); @@ -213,30 +126,24 @@ void apply_action(km_kbp_state const *, km_kbp_action_item const & act, std::u16 context.pop_back(); } break; - case KM_KBP_IT_PERSIST_OPT: - { - bool found = false; - for (auto it = options.begin(); it != options.end(); it++) { - if (it->type == KOT_SAVED) { - if (it->key.compare(act.option->key) == 0) { - found = true; - it->saved_value = act.option->value; - break; - } + case KM_KBP_IT_PERSIST_OPT: { + bool found = false; + for (auto it = options.begin(); it != options.end(); it++) { + if (it->type == km::tests::KOT_SAVED) { + if (it->key.compare(act.option->key) == 0) { + found = true; + it->saved_value = act.option->value; + break; } } - std::cout << "action: option " - << (act.option->scope == KM_KBP_OPT_ENVIRONMENT ? "environment " : "keyboard ") - << act.option->key - << "=" << act.option->value - << " persistence requested" << std::endl; - if (!found) { - std::cout << "option " - << act.option->key - << " saved but no expected output found. Suggestion: update test to include saved option value." << std::endl; - } } - break; + std::cout << "action: option " << (act.option->scope == KM_KBP_OPT_ENVIRONMENT ? "environment " : "keyboard ") + << act.option->key << "=" << act.option->value << " persistence requested" << std::endl; + if (!found) { + std::cout << "option " << act.option->key + << " saved but no expected output found. Suggestion: update test to include saved option value." << std::endl; + } + } break; case KM_KBP_IT_INVALIDATE_CONTEXT: std::cout << "action: context invalidated (markers cleared)" << std::endl; break; @@ -245,67 +152,23 @@ void apply_action(km_kbp_state const *, km_kbp_action_item const & act, std::u16 break; case KM_KBP_IT_CAPSLOCK: std::cout << "action: capsLock " << act.capsLock << std::endl; - g_caps_lock_on = act.capsLock; + test_source.set_caps_lock_on(act.capsLock); break; default: - assert(false); // NOT SUPPORTED + assert(false); // NOT SUPPORTED break; } } -int caps_lock_state() { - return g_caps_lock_on ? KM_KBP_MODIFIER_CAPS : 0; -} - -void toggle_caps_lock_state() { - g_caps_lock_on = !g_caps_lock_on; -} - -km_kbp_option_item *get_keyboard_options(kmx_options options) { - km_kbp_option_item *keyboard_opts = new km_kbp_option_item[options.size() + 1]; - - int i = 0; - for (auto it = options.begin(); it != options.end(); it++) { - if (it->type != KOT_INPUT) continue; - - std::cout << "input option-key: " << it->key << std::endl; - - std::u16string key = it->key; - if (key[0] == u'&') { - // environment value (aka system store) - key.erase(0, 1); - keyboard_opts[i].scope = KM_KBP_OPT_ENVIRONMENT; - } - else { - keyboard_opts[i].scope = KM_KBP_OPT_KEYBOARD; - } - - km_kbp_cp *cp = new km_kbp_cp[key.length() + 1]; - key.copy(cp, key.length()); - cp[key.length()] = 0; - - keyboard_opts[i].key = cp; - - cp = new km_kbp_cp[it->value.length() + 1]; - it->value.copy(cp, it->value.length()); - cp[it->value.length()] = 0; - - keyboard_opts[i].value = cp; - - i++; - } - - keyboard_opts[i] = KM_KBP_OPTIONS_END; - return keyboard_opts; -} - -int run_test(const km::kbp::path &source, const km::kbp::path &compiled) { +int +run_test(const km::kbp::path &source, const km::kbp::path &compiled) { std::string keys = ""; std::u16string expected = u"", context = u""; - kmx_options options; + km::tests::kmx_options options; bool expected_beep = false; + km::tests::KmxTestSource test_source; - int result = load_source(source, keys, expected, context, options, expected_beep); + int result = test_source.load_source(source, keys, expected, context, options, expected_beep); if (result != 0) return result; std::cout << "source file = " << source << std::endl @@ -321,7 +184,7 @@ int run_test(const km::kbp::path &source, const km::kbp::path &compiled) { // Setup keyboard options if (options.size() > 0) { - km_kbp_option_item *keyboard_opts = get_keyboard_options(options); + km_kbp_option_item *keyboard_opts = test_source.get_keyboard_options(options); try_status(km_kbp_state_options_update(test_state, keyboard_opts)); @@ -345,20 +208,20 @@ int run_test(const km::kbp::path &source, const km::kbp::path &compiled) { std::u16string text_store = context; // Run through key events, applying output for each event - for (auto p = next_key(keys); p.vk != 0; p = next_key(keys)) { + for (auto p = test_source.next_key(keys); p.vk != 0; p = test_source.next_key(keys)) { // Because a normal system tracks caps lock state itself, // we mimic that in the tests. We assume caps lock state is // updated on key_down before the processor receives the // event. if (p.vk == KM_KBP_VKEY_CAPS) { - toggle_caps_lock_state(); + test_source.toggle_caps_lock_state(); } for (auto key_down = 1; key_down >= 0; key_down--) { - try_status(km_kbp_process_event(test_state, p.vk, p.modifier_state | caps_lock_state(), key_down)); + try_status(km_kbp_process_event(test_state, p.vk, p.modifier_state | test_source.caps_lock_state(), key_down)); for (auto act = km_kbp_state_action_items(test_state, nullptr); act->type != KM_KBP_IT_END; act++) { - apply_action(test_state, *act, text_store, test_context, options); + apply_action(test_state, *act, text_store, test_context, options, test_source); } } @@ -387,7 +250,8 @@ int run_test(const km::kbp::path &source, const km::kbp::path &compiled) { } // Test if the beep action was as expected - if (g_beep_found != expected_beep) return __LINE__; + if (g_beep_found != expected_beep) + return __LINE__; // Compare final output - retrieve internal context size_t n = 0; @@ -418,14 +282,13 @@ int run_test(const km::kbp::path &source, const km::kbp::path &compiled) { // Test resultant options for (auto it = options.begin(); it != options.end(); it++) { - if (it->type == KOT_OUTPUT) { + if (it->type == km::tests::KOT_OUTPUT) { std::cout << "output option-key: " << it->key << " expected: " << it->value; km_kbp_cp const *value; try_status(km_kbp_state_option_lookup(test_state, KM_KBP_OPT_KEYBOARD, it->key.c_str(), &value)); std::cout << " actual: " << value << std::endl; if (it->value.compare(value) != 0) return __LINE__; - } - else if (it->type == KOT_SAVED) { + } else if (it->type == km::tests::KOT_SAVED) { std::cout << "persisted option-key: " << it->key << " expected: " << it->value << " actual: " << it->saved_value << std::endl; if (it->value.compare(it->saved_value) != 0) return __LINE__; } @@ -438,145 +301,14 @@ int run_test(const km::kbp::path &source, const km::kbp::path &compiled) { return 0; } -std::string string_to_hex(const std::u16string& input) { - std::ostringstream result; - result << std::setfill('0') << std::hex << std::uppercase; - - for (size_t i = 0; i < input.length(); i++) { - unsigned int ch = input[i]; - if(i < input.length() - 1 && Uni_IsSurrogate1(input[i]) && Uni_IsSurrogate2(input[i+1])) { - ch = Uni_SurrogateToUTF32(input[i], input[i+1]); - i++; - } - - result << "U+" << std::setw(4) << ch << " "; - } - return result.str(); -} - -std::u16string parse_source_string(std::string const & s) { - std::u16string t; - for (auto p = s.begin(); p != s.end(); p++) { - if (*p == '\\') { - p++; - km_kbp_usv v; - assert(p != s.end()); - if (*p == 'u' || *p == 'U') { - // Unicode value - p++; - size_t n; - std::string s1 = s.substr(p - s.begin(), 8); - v = std::stoul(s1, &n, 16); - // Allow deadkey_number (U+0001) characters and onward - assert(v >= 0x0001 && v <= 0x10FFFF); - p += n-1; - if (v < 0x10000) { - t += km_kbp_cp(v); - } - else { - t += km_kbp_cp(Uni_UTF32ToSurrogate1(v)); - t += km_kbp_cp(Uni_UTF32ToSurrogate2(v)); - } - } - else if (*p == 'd') { - // Deadkey - // TODO, not yet supported - assert(false); - } - } - else { - t += *p; - } - } - return t; -} - -bool parse_option_string(std::string line, kmx_options &options, kmx_option_type type) { - auto x = line.find('='); - if (x == std::string::npos) return false; - - kmx_option o; - - o.type = type; - o.key = parse_source_string(line.substr(0, x)); - o.value = parse_source_string(line.substr(x + 1)); - - options.emplace_back(o); - return true; -} - -bool is_token(const std::string token, std::string &line) { - if (line.compare(0, token.length(), token) == 0) { - line = line.substr(token.length()); - trim(line); - return true; - } - return false; -} - -int load_source(const km::kbp::path & path, std::string & keys, std::u16string & expected, std::u16string & context, kmx_options &options, bool &expected_beep) { - const std::string s_keys = "c keys: ", - s_expected = "c expected: ", - s_context = "c context: ", - s_option = "c option: ", - s_option_expected = "c expected option: ", - s_option_saved = "c saved option: ", - s_capsLock = "c capsLock: "; - - // Parse out the header statements in file.kmn that tell us (a) environment, (b) key sequence, (c) start context, (d) expected result - std::ifstream kmn(path.native()); - if (!kmn.good()) { - std::cerr << "could not open file: " << path << std::endl; - return __LINE__; - } - std::string line; - while (std::getline(kmn, line)) { - trim(line); - - if (!line.length()) continue; - if (line.compare(0, s_keys.length(), s_keys) == 0) { - keys = line.substr(s_keys.length()); - trim(keys); - } - else if(is_token(s_expected, line)) { - if (line == "\\b") { - expected_beep = true; - } - else { - expected = parse_source_string(line); - } - } - else if (is_token(s_context, line)) { - context = parse_source_string(line); - } - else if (is_token(s_option, line)) { - if (!parse_option_string(line, options, KOT_INPUT)) return __LINE__; - } - else if (is_token(s_option_expected, line)) { - if (!parse_option_string(line, options, KOT_OUTPUT)) return __LINE__; - } - else if (is_token(s_option_saved, line)) { - if (!parse_option_string(line, options, KOT_SAVED)) return __LINE__; - } - else if (is_token(s_capsLock, line)) { - g_caps_lock_on = parse_source_string(line).compare(u"1") == 0; - } - } - - if (keys == "") { - // We must at least have a key sequence to run the test - return __LINE__; - } - - return 0; -} - -constexpr const auto help_str = "\ +constexpr const auto help_str = + "\ kmx [--color] \n\ help:\n\ \tKMN_FILE:\tThe source file for the keyboard under test.\n\ \tKMX_FILE:\tThe corresponding compiled kmx file produced from KMN_FILE.\n"; -} // namespace + +} // namespace int error_args() { std::cerr << "kmx: Not enough arguments." << std::endl; @@ -601,5 +333,5 @@ int main(int argc, char *argv[]) { console_color::enabled = console_color::isaterminal() || arg_color; km::kbp::kmx::g_debug_ToConsole = TRUE; - return run_test(argv[first_arg], argv[first_arg+1]); + return run_test(argv[first_arg], argv[first_arg + 1]); } diff --git a/common/core/desktop/tests/unit/kmx/kmx_key_list.cpp b/common/core/desktop/tests/unit/kmx/kmx_key_list.cpp new file mode 100644 index 0000000000..0bd2bce248 --- /dev/null +++ b/common/core/desktop/tests/unit/kmx/kmx_key_list.cpp @@ -0,0 +1,113 @@ +/* + Copyright: © 2021 SIL International. + Description: Tests for key rules list generation in the kmx processor + Create Date: 15 Nov 2021 + Authors: Ross Cruickshank (RC) Marc Durdin (MD), Tim Eves (TSE) + +*/ + +#include + +#include "path.hpp" +#include "state.hpp" +#include + +#include "../test_assert.h" +#include "../test_color.h" + +using namespace km::kbp::kmx; + +km_kbp_option_item test_env_opts[] = +{ + KM_KBP_OPTIONS_END +}; + +int error_args() { + std::cerr << "kmx: Not enough arguments." << std::endl; + return 1; +} + +#define NO_MODIFIER_FLAGS 0 +#define CHAR_CODE_LOWER_A 97 +#define CHAR_CODE_LOWER_B 98 + + +std::map, uint32_t> kb_key_expected_key_list {{{KM_KBP_VKEY_1, 0},0}, + {{KM_KBP_VKEY_2, 0},0}, + {{KM_KBP_VKEY_A, 0},0}, + {{KM_KBP_VKEY_B, 0},0}, + {{KM_KBP_VKEY_B, KM_KBP_MODIFIER_CTRL},KM_KBP_MODIFIER_CTRL}, + {{KM_KBP_VKEY_C, 0},0}, + {{KM_KBP_VKEY_C, KM_KBP_MODIFIER_ALT},KM_KBP_MODIFIER_ALT}, + }; + +/** + * The purpose of this test is to verify that `km_kbp_keyboard_get_key_list` + * returns list of all the keys used for a keyboard that core as loaded. + * + * @param source_file Path to kmx keyboard file + */ +void test_key_list(const km::kbp::path &source_file){ + + km_kbp_keyboard * test_kb = nullptr; + km_kbp_state * test_state = nullptr; + km_kbp_keyboard_key * kb_key_list; + + km::kbp::path full_path = source_file; + + try_status(km_kbp_keyboard_load(full_path.native().c_str(), &test_kb)); + + // Setup state, environment + try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state)); + + try_status(km_kbp_keyboard_get_key_list(test_kb,&kb_key_list)); + + km_kbp_keyboard_key *key_rule_it = kb_key_list; + std::map, uint32_t> map_key_list; + + auto n = 0; + // Internally the list is created using a map so we should not rely on order elemnts + // in the returned array. Therefore we will put the array items back into a map to verify + // against our expected map. + for (; key_rule_it->key; ++key_rule_it) { + std::cout << "key:" << key_rule_it->key << " modifer: " << key_rule_it->modifier_flag << std::endl; + map_key_list[std::make_pair(key_rule_it->key, key_rule_it->modifier_flag)] = key_rule_it->modifier_flag; + ++n; + } + assert(n==7); + + std::map, uint32_t>::iterator it_expected; + std::map, uint32_t>::iterator it_key_list = map_key_list.begin(); + while (it_key_list != map_key_list.end()){ + + it_expected = kb_key_expected_key_list.find(it_key_list->first); + assert (it_expected != kb_key_expected_key_list.end()); + it_key_list++; + } + + km_kbp_keyboard_key_list_dispose(kb_key_list); + km_kbp_state_dispose(test_state); + km_kbp_keyboard_dispose(test_kb); + +} + +int main(int argc, char *argv []) { + int first_arg = 1; + + if (argc < 2) { + return error_args(); + } + + auto arg_color = std::string(argv[1]) == "--color"; + if(arg_color) { + first_arg++; + if(argc < 3) { + return error_args(); + } + } + console_color::enabled = console_color::isaterminal() || arg_color; + + test_key_list(argv[first_arg]); + + return 0; +} diff --git a/common/core/desktop/tests/unit/kmx/kmx_key_list.kmn b/common/core/desktop/tests/unit/kmx/kmx_key_list.kmn new file mode 100644 index 0000000000..cd55d2a40d --- /dev/null +++ b/common/core/desktop/tests/unit/kmx/kmx_key_list.kmn @@ -0,0 +1,28 @@ +c Description: Tests multiple groups. This keyboard is used to test km_kbp_keyboard_get_key_list API call +c keys: [K_1][K_2] +c expected: abc +c context: + +store(&version) '10.0' + +begin Unicode > use(Main) + +group(Main) using keys + ++ [K_1] > 'a' +'b' + [K_2] > context use(b) +'a' + [CTRL K_B] > 'b' +'a' + [ALT K_C] > 'c' + +match > use(a) + +group(a) using keys + ++ 'a' > 'c' ++ [CTRL K_B] > 'b' ++ [K_C] > 'c' +group(b) using keys + ++ 'b' > 'abc' ++ [ALT K_C] > 'e' + diff --git a/common/core/desktop/tests/unit/kmx/kmx_key_list.kmx b/common/core/desktop/tests/unit/kmx/kmx_key_list.kmx new file mode 100644 index 0000000000..68d44f583d Binary files /dev/null and b/common/core/desktop/tests/unit/kmx/kmx_key_list.kmx differ diff --git a/common/core/desktop/tests/unit/kmx/kmx_test_source.cpp b/common/core/desktop/tests/unit/kmx/kmx_test_source.cpp new file mode 100644 index 0000000000..40f91659c3 --- /dev/null +++ b/common/core/desktop/tests/unit/kmx/kmx_test_source.cpp @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "path.hpp" +#include "state.hpp" +#include "utfcodec.hpp" + +#include "kmx_test_source.hpp" + +namespace km { +namespace tests { + +KmxTestSource::KmxTestSource() { +} + +// String trim functions from https://stackoverflow.com/a/217605/1836776 +// trim from start (in place) +static inline void +ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); +} + +// trim from end (in place) +static inline void +rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); +} + +// trim from both ends (in place) +static inline void +trim(std::string &s) { + ltrim(s); + rtrim(s); +} + +std::u16string +KmxTestSource::parse_source_string(std::string const &s) { + std::u16string t; + for (auto p = s.begin(); p != s.end(); p++) { + if (*p == '\\') { + p++; + km_kbp_usv v; + assert(p != s.end()); + if (*p == 'u' || *p == 'U') { + // Unicode value + p++; + size_t n; + std::string s1 = s.substr(p - s.begin(), 8); + v = std::stoul(s1, &n, 16); + // Allow deadkey_number (U+0001) characters and onward + assert(v >= 0x0001 && v <= 0x10FFFF); + p += n - 1; + if (v < 0x10000) { + t += km_kbp_cp(v); + } else { + t += km_kbp_cp(Uni_UTF32ToSurrogate1(v)); + t += km_kbp_cp(Uni_UTF32ToSurrogate2(v)); + } + } else if (*p == 'd') { + // Deadkey + // TODO, not yet supported + assert(false); + } + } else { + t += *p; + } + } + return t; +} + +bool +KmxTestSource::parse_option_string(std::string line, kmx_options &options, kmx_option_type type) { + auto x = line.find('='); + if (x == std::string::npos) + return false; + + kmx_option o; + + o.type = type; + o.key = parse_source_string(line.substr(0, x)); + o.value = parse_source_string(line.substr(x + 1)); + + options.emplace_back(o); + return true; +} + +bool +KmxTestSource::is_token(const std::string token, std::string &line) { + if (line.compare(0, token.length(), token) == 0) { + line = line.substr(token.length()); + trim(line); + return true; + } + return false; +} + +int +KmxTestSource::load_source( + const km::kbp::path &path, + std::string &keys, + std::u16string &expected, + std::u16string &context, + kmx_options &options, + bool &expected_beep) { + const std::string s_keys = "c keys: "; + const std::string s_expected = "c expected: "; + const std::string s_context = "c context: "; + const std::string s_option = "c option: "; + const std::string s_option_expected = "c expected option: "; + const std::string s_option_saved = "c saved option: "; + const std::string s_capsLock = "c capsLock: "; + + // Parse out the header statements in file.kmn that tell us (a) environment, (b) key sequence, (c) start context, (d) expected + // result + std::ifstream kmn(path.native()); + if (!kmn.good()) { + std::cerr << "could not open file: " << path << std::endl; + return __LINE__; + } + std::string line; + while (std::getline(kmn, line)) { + trim(line); + + if (!line.length()) + continue; + if (line.compare(0, s_keys.length(), s_keys) == 0) { + keys = line.substr(s_keys.length()); + trim(keys); + } else if (is_token(s_expected, line)) { + if (line == "\\b") { + expected_beep = true; + } else { + expected = parse_source_string(line); + } + } else if (is_token(s_context, line)) { + context = parse_source_string(line); + } else if (is_token(s_option, line)) { + if (!parse_option_string(line, options, KOT_INPUT)) + return __LINE__; + } else if (is_token(s_option_expected, line)) { + if (!parse_option_string(line, options, KOT_OUTPUT)) + return __LINE__; + } else if (is_token(s_option_saved, line)) { + if (!parse_option_string(line, options, KOT_SAVED)) + return __LINE__; + } else if (is_token(s_capsLock, line)) { + _caps_lock_on = parse_source_string(line).compare(u"1") == 0; + } + } + + if (keys == "") { + // We must at least have a key sequence to run the test + return __LINE__; + } + + return 0; +} + +km_kbp_option_item * +KmxTestSource::get_keyboard_options(kmx_options options) { + km_kbp_option_item *keyboard_opts = new km_kbp_option_item[options.size() + 1]; + + int i = 0; + for (auto it = options.begin(); it != options.end(); it++) { + if (it->type != KOT_INPUT) + continue; + + std::cout << "input option-key: " << it->key << std::endl; + + std::u16string key = it->key; + if (key[0] == u'&') { + // environment value (aka system store) + key.erase(0, 1); + keyboard_opts[i].scope = KM_KBP_OPT_ENVIRONMENT; + } else { + keyboard_opts[i].scope = KM_KBP_OPT_KEYBOARD; + } + + km_kbp_cp *cp = new km_kbp_cp[key.length() + 1]; + key.copy(cp, key.length()); + cp[key.length()] = 0; + + keyboard_opts[i].key = cp; + + cp = new km_kbp_cp[it->value.length() + 1]; + it->value.copy(cp, it->value.length()); + cp[it->value.length()] = 0; + + keyboard_opts[i].value = cp; + + i++; + } + + keyboard_opts[i] = KM_KBP_OPTIONS_END; + return keyboard_opts; +} + +key_event +KmxTestSource::char_to_event(char ch) { + assert(ch >= 32 && ch <= 127); + return { + km::kbp::kmx::s_char_to_vkey[(int)ch - 32].vk, + (uint16_t)(km::kbp::kmx::s_char_to_vkey[(int)ch - 32].shifted ? KM_KBP_MODIFIER_SHIFT : 0)}; +} + +uint16_t +KmxTestSource::get_modifier(std::string const m) { + for (int i = 0; km::kbp::kmx::s_modifier_names[i].name; i++) { + if (m == km::kbp::kmx::s_modifier_names[i].name) { + return km::kbp::kmx::s_modifier_names[i].modifier; + } + } + return 0; +} + +km_kbp_virtual_key +KmxTestSource::get_vk(std::string const &vk) { + for (int i = 1; i < 256; i++) { + if (vk == km::kbp::kmx::s_key_names[i]) { + return i; + } + } + return 0; +} + +key_event +KmxTestSource::vkey_to_event(std::string const &vk_event) { + // vkey format is MODIFIER MODIFIER K_NAME + // std::cout << "VK=" << vk_event << std::endl; + + std::stringstream f(vk_event); + std::string s; + uint16_t modifier_state = 0; + km_kbp_virtual_key vk = 0; + while (std::getline(f, s, ' ')) { + uint16_t modifier = get_modifier(s); + if (modifier != 0) { + modifier_state |= modifier; + } else { + vk = get_vk(s); + break; + } + } + + // The string should be empty at this point + assert(!std::getline(f, s, ' ')); + assert(vk != 0); + + return {vk, modifier_state}; +} + +key_event +KmxTestSource::next_key(std::string &keys) { + // Parse the next element of the string, chop it off, and return it + if (keys.length() == 0) + return {0, 0}; + char ch = keys[0]; + if (ch == '[') { + if (keys.length() > 1 && keys[1] == '[') { + keys.erase(0, 2); + return char_to_event(ch); + } + auto n = keys.find(']'); + assert(n != std::string::npos); + auto vkey = keys.substr(1, n - 1); + keys.erase(0, n + 1); + return vkey_to_event(vkey); + } else { + keys.erase(0, 1); + return char_to_event(ch); + } +} + +} // namespace tests +} // namespace km diff --git a/common/core/desktop/tests/unit/kmx/kmx_test_source.hpp b/common/core/desktop/tests/unit/kmx/kmx_test_source.hpp new file mode 100644 index 0000000000..2472ffdee5 --- /dev/null +++ b/common/core/desktop/tests/unit/kmx/kmx_test_source.hpp @@ -0,0 +1,68 @@ +#ifndef __KMX_TEST_SOURCE_HPP__ +#define __KMX_TEST_SOURCE_HPP__ + +#include "path.hpp" + +namespace km { +namespace tests { + +struct key_event { + km_kbp_virtual_key vk; + uint16_t modifier_state; +}; + +typedef enum { KOT_INPUT, KOT_OUTPUT, KOT_SAVED } kmx_option_type; + +struct kmx_option { + kmx_option_type type; + std::u16string key, value, saved_value; +}; + +using kmx_options = std::vector; + +class KmxTestSource { +public: + KmxTestSource(); + + int load_source( + const km::kbp::path &path, + std::string &keys, + std::u16string &expected, + std::u16string &context, + kmx_options &options, + bool &expected_beep); + + km_kbp_option_item *get_keyboard_options(kmx_options options); + + key_event next_key(std::string &keys); + + int + caps_lock_state() { + return _caps_lock_on ? KM_KBP_MODIFIER_CAPS : 0; + } + + void + toggle_caps_lock_state() { + _caps_lock_on = !_caps_lock_on; + } + + void set_caps_lock_on(bool caps_lock_on) { + _caps_lock_on = caps_lock_on; + } + +private: + bool _caps_lock_on = false; + + std::u16string parse_source_string(std::string const &s); + bool parse_option_string(std::string line, kmx_options &options, kmx_option_type type); + bool is_token(const std::string token, std::string &line); + key_event char_to_event(char ch); + uint16_t get_modifier(std::string const m); + km_kbp_virtual_key get_vk(std::string const &vk); + key_event vkey_to_event(std::string const &vk_event); +}; + +} // namespace tests +} // namespace km + +#endif // __KMX_TEST_SOURCE_HPP__ diff --git a/common/core/desktop/tests/unit/kmx/meson.build b/common/core/desktop/tests/unit/kmx/meson.build index 28b53051bd..c535d1f517 100644 --- a/common/core/desktop/tests/unit/kmx/meson.build +++ b/common/core/desktop/tests/unit/kmx/meson.build @@ -7,13 +7,17 @@ if compiler.get_id() == 'emscripten' tests_flags = ['--embed-file', join_paths(meson.current_source_dir(),'@/')] + source_path = '/' test_path = '/' else tests_flags = [] - test_path = meson.current_source_dir() + source_path = meson.current_source_dir() + test_path = meson.current_build_dir() endif -kmx = executable('kmx', 'kmx.cpp', +kmx = executable('kmx', + 'kmx.cpp', + 'kmx_test_source.cpp', cpp_args: defns, include_directories: [inc, libsrc], link_args: links + tests_flags, @@ -76,6 +80,8 @@ tests = [ if build_machine.system() == 'windows' kmcomp = find_program('kmcomp.exe', required: false) kmcomp_cmd = [kmcomp] + copy_cmd = [find_program('cmd.exe', required: true), '/c', 'copy'] + cat_cmd = [find_program('cmd.exe', required: true), '/c', 'type'] else kmcomp = find_program('kmcomp', required: false) kmcomp_cmd = [kmcomp] @@ -87,6 +93,8 @@ else kmcomp = disabler() endif endif + copy_cmd = [find_program('cp', required: true)] + cat_cmd = [find_program('cat', required: true)] endif if compiler.get_id() == 'emscripten' @@ -95,23 +103,126 @@ if compiler.get_id() == 'emscripten' kmcomp = disabler() endif +test_keyboards = '' +foreach kbd : tests + if test_keyboards.startswith('{') + test_keyboards += ''', + ''' + endif + kbd_basename = 'k_' + kbd.underscorify().to_lower() + test_keyboards += '''{ + "name": "''' + kbd_basename + '''", + "id": "''' + kbd_basename + '''", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }''' + if not kmcomp.found() + configure_file( + command: copy_cmd + ['@INPUT@', '@OUTPUT@'], + input: kbd + '.kmn', + output: kbd_basename + '.kmn' + ) + configure_file( + command: copy_cmd + ['@INPUT@', '@OUTPUT@'], + input: kbd + '.kmx', + output: kbd_basename + '.kmx' + ) + endif +endforeach + +cfg = configuration_data() +cfg.set('keyboards', test_keyboards) +configure_file( + configuration: cfg, + input: 'kmp.json.in', + output: 'kmp.json' +) + if kmcomp.found() foreach kbd : tests - kbd_src = files(kbd + '.kmn') - kbd_obj = join_paths(meson.current_source_dir(), kbd) + '.kmx' - kbd_log = custom_target(kbd + '.kmx'.underscorify(), - output: kbd + '.log', - input: kbd_src, - command: kmcomp_cmd + ['-s', '-d', '@INPUT@', kbd_obj, '@OUTPUT@'] - ) + kbd_basename = 'k_' + kbd.underscorify().to_lower() - test(kbd, kmx, depends: kbd_log, args: [kbd_src, kbd_obj]) + content = run_command( + cat_cmd, files(kbd + '.kmn'), + ).stdout().strip() + + cfg = configuration_data() + cfg.set('keyboard', kbd) + cfg.set('keyboard_underscore', kbd_basename) + cfg.set('id1', 'id_' + kbd_basename + '_1') + cfg.set('id2', 'id_' + kbd_basename + '_2') + cfg.set('id3', 'id_' + kbd_basename + '_3') + cfg.set('build_dir', meson.current_build_dir()) + cfg.set('NAME', kbd) + cfg.set('CONTENT', content) + + kbd_kmn = configure_file( + configuration: cfg, + input: join_paths('template', 'keyboard.kmn.in'), + output: kbd_basename + '.kmn' + ) + kbd_kpj = configure_file( + configuration: cfg, + input: join_paths('template', 'keyboard.kpj.in'), + output: kbd_basename + '.kpj' + ) + kbd_kps = configure_file( + configuration: cfg, + input: join_paths('template', 'keyboard.kps.in'), + output: kbd_basename + '.kps' + ) + kbd_kmp = custom_target(kbd_basename + '.kmp', + input: kbd_kpj, + output: [kbd_basename + '.kmp', kbd_basename + '.kmx'], + depend_files: [kbd_kps, kbd_kmn], + depfile: kbd_basename + '.dep', + command: kmcomp_cmd + ['-s', '-d', '@INPUT@']) + + kbd_src = files(join_paths(test_path, kbd_basename) + '.kmn') + kbd_obj = join_paths(test_path, kbd_basename) + '.kmx' + # kbd_log = custom_target(kbd.underscorify() + '.kmx', + # output: kbd + '.log', + # input: kbd_src, + # command: kmcomp_cmd + ['-s', '-d', '@INPUT@', kbd_obj, '@OUTPUT@'] + # ) + + test(kbd, kmx, depends: [kbd_kmp], args: [kbd_src, kbd_obj]) endforeach else foreach kbd : tests - kbd_src = join_paths(test_path, kbd) + '.kmn' - kbd_obj = join_paths(test_path, kbd) + '.kmx' + kbd_src = join_paths(test_path, kbd_basename) + '.kmn' + kbd_obj = join_paths(test_path, kbd_basename) + '.kmx' test(kbd, kmx, args: [kbd_src, kbd_obj]) endforeach endif + + +# test for km_kbp_keyboard_get_key_list + +key_e = executable('key_list', 'kmx_key_list.cpp', + cpp_args: defns,include_directories: [inc, libsrc], + link_args: links, + dependencies: [rust_mock_processor], + objects: lib.extract_all_objects()) +test_kbd = 'kmx_key_list' +if kmcomp.found() + kbd_src = files(test_kbd + '.kmn') + kbd_obj = join_paths(meson.current_source_dir(), test_kbd) + '.kmx' + kbd_log = custom_target(test_kbd + '.kmx'.underscorify(), + output: test_kbd + '.log', + input: kbd_src, + command: kmcomp_cmd + ['-s', '-d', '@INPUT@', kbd_obj, '@OUTPUT@'] + ) + test('key_list', key_e, depends: kbd_log, args: [kbd_obj] ) +else + kbd_obj = join_paths(meson.current_source_dir(), test_kbd) + '.kmx' + test('key_list', key_e, args: [kbd_obj] ) +endif + + diff --git a/common/core/desktop/tests/unit/kmx/template/keyboard.kmn.in b/common/core/desktop/tests/unit/kmx/template/keyboard.kmn.in new file mode 100644 index 0000000000..2e5826ef3c --- /dev/null +++ b/common/core/desktop/tests/unit/kmx/template/keyboard.kmn.in @@ -0,0 +1,2 @@ +store(&NAME) '@NAME@' +@CONTENT@ diff --git a/common/core/desktop/tests/unit/kmx/template/keyboard.kpj.in b/common/core/desktop/tests/unit/kmx/template/keyboard.kpj.in new file mode 100644 index 0000000000..ba612eccaa --- /dev/null +++ b/common/core/desktop/tests/unit/kmx/template/keyboard.kpj.in @@ -0,0 +1,42 @@ + + + + $PROJECTPATH + false + false + false + keyboard + + + + @id1@ + @keyboard_underscore@.kmn + @build_dir@/@keyboard_underscore@.kmn + 1.0 + .kmn +
+ @keyboard@ + Copyright (C) Keyman Team +
+
+ + @id2@ + @keyboard_underscore@.kps + @build_dir@/@keyboard_underscore@.kps + + .kps +
+ @keyboard@ + Copyright (C) Keyman Team +
+
+ + @id3@ + @keyboard_underscore@.kmx + @build_dir@/@keyboard_underscore@.kmx + + .kmx + @id2@ + +
+
diff --git a/common/core/desktop/tests/unit/kmx/template/keyboard.kps.in b/common/core/desktop/tests/unit/kmx/template/keyboard.kps.in new file mode 100644 index 0000000000..fceae55377 --- /dev/null +++ b/common/core/desktop/tests/unit/kmx/template/keyboard.kps.in @@ -0,0 +1,43 @@ + + + + 15.0 + 7.0 + + + + + + + + + + + + + + @keyboard@ + Copyright (C) Keyman Team + Keyman Team + + + + + @keyboard_underscore@.kmx + + 0 + .kmx + + + + + @keyboard@ + @keyboard_underscore@ + 1.0 + + English + + + + + diff --git a/common/core/desktop/tests/unit/meson.build b/common/core/desktop/tests/unit/meson.build new file mode 100644 index 0000000000..f905a957f4 --- /dev/null +++ b/common/core/desktop/tests/unit/meson.build @@ -0,0 +1,5 @@ +subdir('json') +subdir('utftest') +subdir('kmnkbd') +subdir('kmx') +subdir('rust_mock') diff --git a/common/core/web/input-processor/package-lock.json b/common/core/web/input-processor/package-lock.json index 6a98c82319..403d2465d3 100644 --- a/common/core/web/input-processor/package-lock.json +++ b/common/core/web/input-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/input-processor/package.json b/common/core/web/input-processor/package.json index b7317375a4..df2aa1a9ab 100644 --- a/common/core/web/input-processor/package.json +++ b/common/core/web/input-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.129", + "version": "15.0.157", "description": "The core text and prediction processing engine for KeymanWeb", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/lexical-model-compiler": "^15.0.129", - "@keymanapp/resources-gosh": "^15.0.129", + "@keymanapp/lexical-model-compiler": "^15.0.157", + "@keymanapp/resources-gosh": "^15.0.157", "@types/node": "^11.9.4", "chai": "^4.3.4", "mocha": "^8.4.0", @@ -34,11 +34,11 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.129", - "@keymanapp/lexical-model-layer": "^15.0.129", - "@keymanapp/models-types": "^15.0.129", - "@keymanapp/web-environment": "^15.0.129", - "@keymanapp/web-utils": "^15.0.129", + "@keymanapp/keyboard-processor": "^15.0.157", + "@keymanapp/lexical-model-layer": "^15.0.157", + "@keymanapp/models-types": "^15.0.157", + "@keymanapp/web-environment": "^15.0.157", + "@keymanapp/web-utils": "^15.0.157", "eventemitter3": "^4.0.0" } } diff --git a/common/core/web/keyboard-processor/package-lock.json b/common/core/web/keyboard-processor/package-lock.json index 224eb43832..8d9d004a91 100644 --- a/common/core/web/keyboard-processor/package-lock.json +++ b/common/core/web/keyboard-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/keyboard-processor/package.json b/common/core/web/keyboard-processor/package.json index a39aa9a455..7c331d57fd 100644 --- a/common/core/web/keyboard-processor/package.json +++ b/common/core/web/keyboard-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.129", + "version": "15.0.157", "description": "Core module for Keyman keyboard support in KeymanWeb.", "repository": { "type": "git", @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.129", + "@keymanapp/resources-gosh": "^15.0.157", "chai": "^4.3.4", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", @@ -32,9 +32,9 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/models-types": "^15.0.129", - "@keymanapp/web-environment": "^15.0.129", - "@keymanapp/web-utils": "^15.0.129", + "@keymanapp/models-types": "^15.0.157", + "@keymanapp/web-environment": "^15.0.157", + "@keymanapp/web-utils": "^15.0.157", "@types/node": "^11.9.4" } } diff --git a/common/core/web/keyboard-processor/src/text/defaultOutput.ts b/common/core/web/keyboard-processor/src/text/defaultOutput.ts index 5fd515280c..daf8ec0f13 100644 --- a/common/core/web/keyboard-processor/src/text/defaultOutput.ts +++ b/common/core/web/keyboard-processor/src/text/defaultOutput.ts @@ -70,15 +70,15 @@ namespace com.keyman.text { } /** - * Used when a RuleBehavior represents a non-text "command" within the Engine. This will generally + * Used when a RuleBehavior represents a non-text "command" within the Engine. This will generally * trigger events that require context reset - often by moving the caret or by moving what OutputTarget * the caret is in. However, we let those events perform the actual context reset. - * + * * Note: is extended by DOM-aware KeymanWeb code. */ public static applyCommand(Lkc: KeyEvent, outputTarget: OutputTarget): void { // Notes for potential default-handling extensions: - // + // // switch(code) { // // Problem: clusters, and doing them right. // // The commented-out code below should be a decent starting point, but clusters make it complex. @@ -142,27 +142,33 @@ namespace com.keyman.text { // Test for fall back to U_xxxxxx key id // For this first test, we ignore the keyCode and use the keyName public static forUnicodeKeynames(Lkc: KeyEvent, ruleBehavior?: RuleBehavior) { - let keyName = Lkc.kName; + const keyName = Lkc.kName; // Test for fall back to U_xxxxxx key id // For this first test, we ignore the keyCode and use the keyName if(!keyName || keyName.substr(0,2) != 'U_') { return null; } - - var codePoint = parseInt(keyName.substr(2,6), 16); - if (((0x0 <= codePoint) && (codePoint <= 0x1F)) || ((0x80 <= codePoint) && (codePoint <= 0x9F))) { - // Code points [U_0000 - U_001F] and [U_0080 - U_009F] refer to Unicode C0 and C1 control codes. - // Check the codePoint number and do not allow output of these codes via U_xxxxxx shortcuts. - if(ruleBehavior) { - ruleBehavior.errorLog = ("Suppressing Unicode control code: U_00" + codePoint.toString(16)); + + let result = ''; + const codePoints = keyName.substr(2).split('_'); + for(let codePoint of codePoints) { + const codePointValue = parseInt(codePoint, 16); + if (((0x0 <= codePointValue) && (codePointValue <= 0x1F)) || ((0x80 <= codePointValue) && (codePointValue <= 0x9F))) { + // Code points [U_0000 - U_001F] and [U_0080 - U_009F] refer to Unicode C0 and C1 control codes. + // Check the codePoint number and do not allow output of these codes via U_xxxxxx shortcuts. + if(ruleBehavior) { + ruleBehavior.errorLog = ("Suppressing Unicode control code in " + keyName); + } + // We'll attempt to add valid chars + continue; + } else { + // String.fromCharCode() is inadequate to handle the entire range of Unicode + // Someday after upgrading to ES2015, can use String.fromCodePoint() + result += String.kmwFromCharCode(codePointValue); } - return null; - } else { - // String.fromCharCode() is inadequate to handle the entire range of Unicode - // Someday after upgrading to ES2015, can use String.fromCodePoint() - return String.kmwFromCharCode(codePoint); } + return result ? result : null; } // Test for otherwise unimplemented keys on the the base default & shift layers. diff --git a/common/core/web/tools/recorder/package-lock.json b/common/core/web/tools/recorder/package-lock.json index e1603cb48e..30e1db2eb9 100644 --- a/common/core/web/tools/recorder/package-lock.json +++ b/common/core/web/tools/recorder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/tools/recorder/package.json b/common/core/web/tools/recorder/package.json index 2f7c2a0b29..f5ab9c16f0 100644 --- a/common/core/web/tools/recorder/package.json +++ b/common/core/web/tools/recorder/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.129", + "version": "15.0.157", "description": "Core classes used to develop KeymanWeb test cases based on keystrokes", "main": "index.js", "scripts": { @@ -17,10 +17,10 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.129", - "@keymanapp/models-types": "^15.0.129", - "@keymanapp/web-environment": "^15.0.129", - "@keymanapp/web-utils": "^15.0.129", + "@keymanapp/keyboard-processor": "^15.0.157", + "@keymanapp/models-types": "^15.0.157", + "@keymanapp/web-environment": "^15.0.157", + "@keymanapp/web-utils": "^15.0.157", "@types/node": "^11.9.4" }, "devDependencies": { diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json index 4fe55838eb..bc01211ad5 100644 --- a/common/core/web/tools/sentry-manager/package-lock.json +++ b/common/core/web/tools/sentry-manager/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json index edf5ba520a..067febe8ca 100644 --- a/common/core/web/tools/sentry-manager/package.json +++ b/common/core/web/tools/sentry-manager/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.129", + "version": "15.0.157", "description": "Common setup for KeymanWeb's Sentry-based error reporting", "main": "dist/index.js", "scripts": { @@ -20,7 +20,7 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/web-environment": "^15.0.129", + "@keymanapp/web-environment": "^15.0.157", "@sentry/browser": "^5.27.4" } } diff --git a/common/core/web/utils/package-lock.json b/common/core/web/utils/package-lock.json index 765f063753..28773940b0 100644 --- a/common/core/web/utils/package-lock.json +++ b/common/core/web/utils/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/utils/package.json b/common/core/web/utils/package.json index 6a3328ba13..82b6ba3cad 100644 --- a/common/core/web/utils/package.json +++ b/common/core/web/utils/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.129", + "version": "15.0.157", "description": "Common utility functions used throughout other Keyman packages", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,8 +20,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.129", - "@keymanapp/web-environment": "^15.0.129", + "@keymanapp/resources-gosh": "^15.0.157", + "@keymanapp/web-environment": "^15.0.157", "@types/node": "^14.0.5", "typescript": "^3.8.3" } diff --git a/common/models/templates/package-lock.json b/common/models/templates/package-lock.json index 8947213ecd..e1ca01585a 100644 --- a/common/models/templates/package-lock.json +++ b/common/models/templates/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/templates/package.json b/common/models/templates/package.json index b1f959a46f..8b3873c929 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.129", + "version": "15.0.157", "description": "Backing model implementations (templates) for Keyman's modelling layer", "keywords": [ "lm", @@ -42,8 +42,8 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.129", - "@keymanapp/web-utils": "^15.0.129", + "@keymanapp/models-types": "^15.0.157", + "@keymanapp/web-utils": "^15.0.157", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/node": "^14.0.4", @@ -52,6 +52,6 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-wordbreakers": "^15.0.129" + "@keymanapp/models-wordbreakers": "^15.0.157" } } diff --git a/common/models/types/package-lock.json b/common/models/types/package-lock.json index f022c3a6b9..9e3a35de44 100644 --- a/common/models/types/package-lock.json +++ b/common/models/types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/models/types/package.json b/common/models/types/package.json index b4c5a2462f..a80ba99874 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.129", + "version": "15.0.157", "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.", "types": "./index.d.ts", "scripts": { diff --git a/common/models/wordbreakers/package-lock.json b/common/models/wordbreakers/package-lock.json index 52f7050946..4d20b2847c 100644 --- a/common/models/wordbreakers/package-lock.json +++ b/common/models/wordbreakers/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index 884b4c8f6c..a6aa0209b6 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.129", + "version": "15.0.157", "description": "Word breakers bundled in the modeling layer", "keywords": [ "lexical model", @@ -40,7 +40,7 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.129", + "@keymanapp/models-types": "^15.0.157", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "chai": "^4.3.4", diff --git a/common/predictive-text/package-lock.json b/common/predictive-text/package-lock.json index 58f7f52c2e..d2c8b344fa 100644 --- a/common/predictive-text/package-lock.json +++ b/common/predictive-text/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index f151a92b7a..c03185b880 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.129", + "version": "15.0.157", "description": "Keyman/Predictive Text integration layer", "main": "build/index.js", "scripts": { @@ -21,8 +21,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/models-types": "^15.0.129", - "@keymanapp/resources-gosh": "^15.0.129", + "@keymanapp/models-types": "^15.0.157", + "@keymanapp/resources-gosh": "^15.0.157", "@types/node": "^10.17.21", "chai": "^4.3.4", "karma": "^6.3.4", @@ -46,10 +46,10 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-templates": "^15.0.129", - "@keymanapp/models-wordbreakers": "^15.0.129", - "@keymanapp/web-environment": "^15.0.129", - "@keymanapp/web-utils": "^15.0.129", + "@keymanapp/models-templates": "^15.0.157", + "@keymanapp/models-wordbreakers": "^15.0.157", + "@keymanapp/web-environment": "^15.0.157", + "@keymanapp/web-utils": "^15.0.157", "es6-shim": "^0.35.5", "string.prototype.codepointat": "^0.2.1", "string.prototype.startswith": "^0.2.0" diff --git a/common/schemas/kps/README.md b/common/schemas/kps/README.md new file mode 100644 index 0000000000..4322a28bf6 --- /dev/null +++ b/common/schemas/kps/README.md @@ -0,0 +1,6 @@ +# kps.xsd + +Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/kps/7.0/kps.xsd + +## 2021-07-19 7.0 +* Initial version 7.0 diff --git a/common/schemas/kps/kps.xsd b/common/schemas/kps/kps.xsd new file mode 100644 index 0000000000..adbdc36178 --- /dev/null +++ b/common/schemas/kps/kps.xsd @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/test/keyboards/.gitignore b/common/test/keyboards/.gitignore new file mode 100644 index 0000000000..ef4be49c85 --- /dev/null +++ b/common/test/keyboards/.gitignore @@ -0,0 +1 @@ +*/build/ \ No newline at end of file diff --git a/common/test/keyboards/u_xxxx_yyyy_2858/HISTORY.md b/common/test/keyboards/u_xxxx_yyyy_2858/HISTORY.md new file mode 100644 index 0000000000..fa38145cc5 --- /dev/null +++ b/common/test/keyboards/u_xxxx_yyyy_2858/HISTORY.md @@ -0,0 +1,6 @@ +U_xxxx_yyyy_2858 Change History +==================== + +1.0 (2021-11-11) +---------------- +* Created by diff --git a/windows/src/test/manual-tests/caps_lock_headers/LICENSE.md b/common/test/keyboards/u_xxxx_yyyy_2858/LICENSE.md similarity index 99% rename from windows/src/test/manual-tests/caps_lock_headers/LICENSE.md rename to common/test/keyboards/u_xxxx_yyyy_2858/LICENSE.md index 714cb32594..77a9531492 100644 --- a/windows/src/test/manual-tests/caps_lock_headers/LICENSE.md +++ b/common/test/keyboards/u_xxxx_yyyy_2858/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -© +© 2021 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/windows/src/test/manual-tests/caps_lock_headers/README.md b/common/test/keyboards/u_xxxx_yyyy_2858/README.md similarity index 60% rename from windows/src/test/manual-tests/caps_lock_headers/README.md rename to common/test/keyboards/u_xxxx_yyyy_2858/README.md index bc3b3b42ea..1fe1e53fe5 100644 --- a/windows/src/test/manual-tests/caps_lock_headers/README.md +++ b/common/test/keyboards/u_xxxx_yyyy_2858/README.md @@ -1,17 +1,19 @@ -caps_lock_shift_frees_caps keyboard +U_xxxx_yyyy_2858 keyboard ============== -© - Version 1.0 Description ----------- - -caps_lock_shift_frees_caps generated from template +U_xxxx_yyyy_2858 generated from template Links ----- +https://github.com/keymanapp/keyman/issues/2858 + +Copyright +--------- +See [LICENSE.md](LICENSE.md) Supported Platforms ------------------- diff --git a/common/test/keyboards/u_xxxx_yyyy_2858/source/NotoSansOldItalic-Regular.ttf b/common/test/keyboards/u_xxxx_yyyy_2858/source/NotoSansOldItalic-Regular.ttf new file mode 100644 index 0000000000..97b184c228 Binary files /dev/null and b/common/test/keyboards/u_xxxx_yyyy_2858/source/NotoSansOldItalic-Regular.ttf differ diff --git a/common/test/keyboards/u_xxxx_yyyy_2858/source/OFL.txt b/common/test/keyboards/u_xxxx_yyyy_2858/source/OFL.txt new file mode 100644 index 0000000000..c9857270cc --- /dev/null +++ b/common/test/keyboards/u_xxxx_yyyy_2858/source/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2012 Google Inc. All Rights Reserved. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/readme.htm b/common/test/keyboards/u_xxxx_yyyy_2858/source/readme.htm similarity index 62% rename from windows/src/test/manual-tests/caps_lock_headers/source/readme.htm rename to common/test/keyboards/u_xxxx_yyyy_2858/source/readme.htm index 3e2679017c..642ac5d815 100644 --- a/windows/src/test/manual-tests/caps_lock_headers/source/readme.htm +++ b/common/test/keyboards/u_xxxx_yyyy_2858/source/readme.htm @@ -2,7 +2,8 @@ - caps_lock_shift_frees_caps + + U_xxxx_yyyy_2858 + + + +

Start Using U_xxxx_yyyy_2858

+ +

+ U_xxxx_yyyy_2858 1.0 generated from template. +

+ +

Keyboard Layout

+ + + + + \ No newline at end of file diff --git a/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.keyboard_info b/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.keyboard_info new file mode 100644 index 0000000000..03c280d309 --- /dev/null +++ b/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.keyboard_info @@ -0,0 +1,7 @@ +{ + "license": "mit", + "languages": [ + + ], + "description": "U_xxxx_yyyy_2858 generated from template" +} diff --git a/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.kpj b/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.kpj new file mode 100644 index 0000000000..f2b44703b1 --- /dev/null +++ b/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.kpj @@ -0,0 +1,102 @@ + + + + $PROJECTPATH\build + True + True + True + keyboard + + + + id_7e810b4833243057b00c8d803899b99f + u_xxxx_yyyy_2858.kmn + source\u_xxxx_yyyy_2858.kmn + 1.0 + .kmn +
+ U_xxxx_yyyy_2858 + © +
+
+ + id_e45c659d6b3f6aa669e8d9488bf2602d + u_xxxx_yyyy_2858.kps + source\u_xxxx_yyyy_2858.kps + + .kps +
+ U_xxxx_yyyy_2858 + © +
+
+ + id_ede98e4633e239f933cbfd1f4e1b766c + HISTORY.md + HISTORY.md + + .md + + + id_53e892b8b41cc4caece1cfd5ef21d6e7 + LICENSE.md + LICENSE.md + + .md + + + id_0730bb7c2e8f9ea2438b52e419dd86c9 + README.md + README.md + + .md + + + id_10a5cd9d0c42cd03b9838916f6f0bd3d + u_xxxx_yyyy_2858.keyboard_info + u_xxxx_yyyy_2858.keyboard_info + + .keyboard_info + + + id_a1200478ad69b7f058fdce369fb1d960 + u_xxxx_yyyy_2858.kmx + source\..\build\u_xxxx_yyyy_2858.kmx + + .kmx + id_e45c659d6b3f6aa669e8d9488bf2602d + + + id_e3aacd883345f12f694ac409407de6da + u_xxxx_yyyy_2858.js + source\..\build\u_xxxx_yyyy_2858.js + + .js + id_e45c659d6b3f6aa669e8d9488bf2602d + + + id_6c1c142b76341666d0679b0979b76793 + u_xxxx_yyyy_2858.kvk + source\..\build\u_xxxx_yyyy_2858.kvk + + .kvk + id_e45c659d6b3f6aa669e8d9488bf2602d + + + id_356e5d149c1e539356d72698c1e401a6 + welcome.htm + source\welcome.htm + + .htm + id_e45c659d6b3f6aa669e8d9488bf2602d + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_e45c659d6b3f6aa669e8d9488bf2602d + +
+
diff --git a/crowdin.yml b/crowdin.yml index 08cdd98927..fcfdbc77bc 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -95,6 +95,31 @@ files: type: gettext update_option: update_as_unapproved + # macOS files + + - source: /mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/en.lproj/KMAboutWindowController.strings + dest: /mac/app/KMAboutWindowController.strings + translation: /mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/%osx_code%/%original_file_name% + + - source: /mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/en.lproj/preferences.strings + dest: /mac/app/preferences.strings + translation: /mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/%osx_code%/%original_file_name% + + - source: /mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/en.lproj/KMInfoWindowController.strings + dest: /mac/app/KMInfoWindowController.strings + translation: /mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/%osx_code%/%original_file_name% + + - source: /mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/en.lproj/KMKeyboardHelpWindowController.strings + dest: /mac/app/KMKeyboardHelpWindowController.strings + translation: /mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/%osx_code%/%original_file_name% + + - source: /mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings + dest: /mac/app/Localizable.strings + translation: /mac/Keyman4MacIM/Keyman4MacIM/%osx_code%/%original_file_name% + + - source: /mac/Keyman4MacIM/Keyman4MacIM/en.lproj/MainMenu.strings + dest: /mac/app/MainMenu.strings + translation: /mac/Keyman4MacIM/Keyman4MacIM/%osx_code%/%original_file_name% # crowdin parameters descriptions: diff --git a/developer/js/package-lock.json b/developer/js/package-lock.json index eb40320893..d923b69ab1 100644 --- a/developer/js/package-lock.json +++ b/developer/js/package-lock.json @@ -1,12 +1,12 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.50", + "version": "15.0.121", "license": "MIT", "dependencies": { "commander": "^3.0.0", @@ -23,7 +23,7 @@ "@types/node": "^10.14.6", "@types/xml2js": "^0.4.5", "chalk": "^2.4.2", - "jszip": "^2.5.0" + "jszip": "^3.7.0" }, "engines": { "node": ">=12.0.0" @@ -227,14 +227,107 @@ } }, "node_modules/jszip": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz", - "integrity": "sha1-dET9hVHd8+XacZj+oMkbyDCMwnQ=", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", "dev": true, "dependencies": { - "pako": "~0.2.5" + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" } }, + "node_modules/jszip/node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/jszip/node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "node_modules/jszip/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/jszip/node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/jszip/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/jszip/node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/jszip/node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/jszip/node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "node_modules/object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", @@ -260,12 +353,6 @@ "node": ">= 0.8" } }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", - "dev": true - }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -498,12 +585,104 @@ } }, "jszip": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz", - "integrity": "sha1-dET9hVHd8+XacZj+oMkbyDCMwnQ=", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", "dev": true, "requires": { - "pako": "~0.2.5" + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + } } }, "object-inspect": { @@ -525,12 +704,6 @@ "es-abstract": "^1.5.1" } }, - "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", - "dev": true - }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", diff --git a/developer/js/package.json b/developer/js/package.json index dcb75bc0c4..115d2e60c6 100644 --- a/developer/js/package.json +++ b/developer/js/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.129", + "version": "15.0.157", "description": "Keyman Developer lexical model compiler", "keywords": [ "keyboard", @@ -37,21 +37,21 @@ "node": ">=12.0.0" }, "dependencies": { - "@keymanapp/models-types": "^15.0.129", + "@keymanapp/models-types": "^15.0.157", "commander": "^3.0.0", "typescript": "^3.8.3", "xml2js": "^0.4.19" }, "devDependencies": { - "@keymanapp/models-templates": "^15.0.129", - "@keymanapp/models-wordbreakers": "^15.0.129", + "@keymanapp/models-templates": "^15.0.157", + "@keymanapp/models-wordbreakers": "^15.0.157", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", "@types/xml2js": "^0.4.5", "chai": "^4.3.4", "chalk": "^2.4.2", - "jszip": "^2.5.0", + "jszip": "^3.7.0", "mocha": "^8.4.0", "ts-node": "^9.1.1" }, diff --git a/developer/js/source/jszip.d.ts b/developer/js/source/jszip.d.ts deleted file mode 100644 index 64735dada5..0000000000 --- a/developer/js/source/jszip.d.ts +++ /dev/null @@ -1,262 +0,0 @@ -// Definitions by: mzeiher , forabi , eddieantonio -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.3 - -/// - -interface JSZipSupport { - arraybuffer: boolean; - uint8array: boolean; - blob: boolean; - nodebuffer: boolean; -} - -type Compression = 'STORE' | 'DEFLATE'; - -interface Metadata { - percent: number; - currentFile: string; -} - -type OnUpdateCallback = (metadata: Metadata) => void; - -interface InputByType { - base64: string; - string: string; - text: string; - binarystring: string; - array: number[]; - uint8array: Uint8Array; - arraybuffer: ArrayBuffer; - blob: Blob; - stream: NodeJS.ReadableStream; -} - -interface OutputByType { - base64: string; - text: string; - binarystring: string; - array: number[]; - uint8array: Uint8Array; - arraybuffer: ArrayBuffer; - blob: Blob; - nodebuffer: Buffer; -} - -type InputFileFormat = InputByType[keyof InputByType]; - -type InputType = keyof InputByType; - -type OutputType = keyof OutputByType; - -interface JSZipObject { - name: string; - dir: boolean; - date: Date; - comment: string; - /** The UNIX permissions of the file, if any. */ - unixPermissions: number | string | null; - /** The UNIX permissions of the file, if any. */ - dosPermissions: number | null; - options: JSZipObjectOptions; - - /** - * Prepare the content in the asked type. - * @param type the type of the result. - * @param onUpdate a function to call on each internal update. - * @return Promise the promise of the result. - */ - async(type: T, onUpdate?: OnUpdateCallback): Promise; - nodeStream(type?: 'nodestream', onUpdate?: OnUpdateCallback): NodeJS.ReadableStream; -} - -interface JSZipFileOptions { - /** Set to `true` if the data is `base64` encoded. For example image data from a `` element. Plain text and HTML do not need this option. */ - base64?: boolean; - /** - * Set to `true` if the data should be treated as raw content, `false` if this is a text. If `base64` is used, - * this defaults to `true`, if the data is not a `string`, this will be set to `true`. - */ - binary?: boolean; - /** - * The last modification date, defaults to the current date. - */ - date?: Date; - compression?: string; - comment?: string; - /** Set to `true` if (and only if) the input is a "binary string" and has already been prepared with a `0xFF` mask. */ - optimizedBinaryString?: boolean; - /** Set to `true` if folders in the file path should be automatically created, otherwise there will only be virtual folders that represent the path to the file. */ - createFolders?: boolean; - /** Set to `true` if this is a directory and content should be ignored. */ - dir?: boolean; - - /** 6 bits number. The DOS permissions of the file, if any. */ - dosPermissions?: number | null; - /** - * 16 bits number. The UNIX permissions of the file, if any. - * Also accepts a `string` representing the octal value: `"644"`, `"755"`, etc. - */ - unixPermissions?: number | string | null; -} - -interface JSZipObjectOptions { - compression: Compression; -} - -interface JSZipGeneratorOptions { - /** - * @deprecated https://github.com/Stuk/jszip/blob/9ab3ed85da96700f32f50e01b87f2a4bde010390/lib/object.js#L738 - */ - base64?: boolean; - compression?: Compression; - compressionOptions?: null | { - level: number; - }; - type?: T; - comment?: string; - /** - * mime-type for the generated file. - * Useful when you need to generate a file with a different extension, ie: “.ods”. - * @default 'application/zip' - */ - mimeType?: string; - encodeFileName?(filename: string): string; - /** Stream the files and create file descriptors */ - streamFiles?: boolean; - /** DOS (default) or UNIX */ - platform?: 'DOS' | 'UNIX'; -} - -interface JSZipLoadOptions { - base64?: boolean; - checkCRC32?: boolean; - optimizedBinaryString?: boolean; - createFolders?: boolean; - decodeFileName?(filenameBytes: Uint8Array): string; -} - -interface JSZip { - files: {[key: string]: JSZipObject}; - - /** - * Get a file from the archive - * - * @param Path relative path to file - * @return File matching path, null if no file found - */ - file(path: string): JSZipObject; - - /** - * Get files matching a RegExp from archive - * - * @param path RegExp to match - * @return Return all matching files or an empty array - */ - file(path: RegExp): JSZipObject[]; - - /** - * Add a file to the archive - * - * @param path Relative path to file - * @param data Content of the file - * @param options Optional information about the file - * @return JSZip object - */ - file(path: string, data: InputByType[T] | Promise, options?: JSZipFileOptions): this; - file(path: string, data: null, options?: JSZipFileOptions & { dir: true }): this; - - /** - * Returns an new JSZip instance with the given folder as root - * - * @param name Name of the folder - * @return New JSZip object with the given folder as root or null - */ - folder(name: string): JSZip; - - /** - * Returns new JSZip instances with the matching folders as root - * - * @param name RegExp to match - * @return New array of JSZipFile objects which match the RegExp - */ - folder(name: RegExp): JSZipObject[]; - - /** - * Call a callback function for each entry at this folder level. - * - * @param callback function - */ - forEach(callback: (relativePath: string, file: JSZipObject) => void): void; - - /** - * Get all files which match the given filter function - * - * @param predicate Filter function - * @return Array of matched elements - */ - filter(predicate: (relativePath: string, file: JSZipObject) => boolean): JSZipObject[]; - - /** - * Removes the file or folder from the archive - * - * @param path Relative path of file or folder - * @return Returns the JSZip instance - */ - remove(path: string): JSZip; - - /** - * Generate the complete zip file - * @param {Object} options the options to generate the zip file : - * - base64, (deprecated, use type instead) true to generate base64. - * - compression, "STORE" by default. - * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. - * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file - */ - generate(options?: JSZipGeneratorOptions, onUpdate?: OnUpdateCallback): Promise; - - /** - * Generates a new archive asynchronously - * - * @param options Optional options for the generator - * @param onUpdate The optional function called on each internal update with the metadata. - * @return A Node.js `ReadableStream` - */ - generateNodeStream(options?: JSZipGeneratorOptions<'nodebuffer'>, onUpdate?: OnUpdateCallback): NodeJS.ReadableStream; - - /** - * Deserialize zip file asynchronously - * - * @param data Serialized zip file - * @param options Options for deserializing - * @return Returns promise - */ - loadAsync(data: InputFileFormat, options?: JSZipLoadOptions): Promise; - - support: JSZipSupport; - external: { - Promise: PromiseConstructorLike; - }; - version: string; -} - -interface JSZipStatic extends JSZip { - /** - * Create JSZip instance - */ - (): JSZip; - - /** - * Create JSZip instance - * If no parameters given an empty zip archive will be created - * - * @param data Serialized zip archive - * @param options Description of the serialized zip archive - */ - new (data?: InputFileFormat, options?: JSZipLoadOptions): this; -} - -declare let JSZipModuleObject: JSZipStatic; -declare module "jszip" { - export = JSZipModuleObject; -} \ No newline at end of file diff --git a/developer/js/source/kmlmp.ts b/developer/js/source/kmlmp.ts index 2012dc2906..729043236c 100644 --- a/developer/js/source/kmlmp.ts +++ b/developer/js/source/kmlmp.ts @@ -40,7 +40,13 @@ let kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString); // Build the .kmp package file // -kmpCompiler.buildKmpFile(kmpJsonData, outputFilename); +let promise = kmpCompiler.buildKmpFile(kmpJsonData); +promise.then(data => { + fs.writeFileSync(outputFilename, data, 'binary'); +}).catch(error => { + // Consumer decides how to handle errors + console.error('Failed to write kmp file'); +}); function exitDueToUsageError(message: string): never { console.error(`${program._name}: ${message}`); diff --git a/developer/js/source/package-compiler/kmp-compiler.ts b/developer/js/source/package-compiler/kmp-compiler.ts index 5a16738397..60fe6797f7 100644 --- a/developer/js/source/package-compiler/kmp-compiler.ts +++ b/developer/js/source/package-compiler/kmp-compiler.ts @@ -124,7 +124,12 @@ export default class KmpCompiler { return kmp; } - public buildKmpFile(kmpJsonData: KmpJsonFile, kmpFileName: string) { + /** + * Returns a Promise to the serialized data which can then be written to a .kmp file. + * + * @param kmpJsonData - The kmp.json Object + */ + public buildKmpFile(kmpJsonData: KmpJsonFile): Promise { const kmpJsonFileName = 'kmp.json'; if(!kmpJsonData.files) { @@ -145,8 +150,6 @@ export default class KmpCompiler { zip.file(kmpJsonFileName, JSON.stringify(kmpJsonData, null, 2)); // Generate kmp file - var data = zip.generate({base64:false,compression:'DEFLATE'}); - fs.writeFileSync(kmpFileName, data, 'binary'); - + return zip.generateAsync({type: 'binarystring', compression:'DEFLATE'}); } } diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj index b90d0a1c34..f6ce0d637a 100644 --- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj +++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj @@ -302,6 +302,12 @@ 1645D5942036C6FF0076C51B /* KeymanPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeymanPackage.swift; sourceTree = ""; }; 1645D5962036C9F80076C51B /* KMPKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPKeyboard.swift; sourceTree = ""; }; 165EB3A02098993900040A69 /* KeyboardError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardError.swift; sourceTree = ""; }; + 293EA3DB2705955300545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ha; path = ha.lproj/ResourceInfoView.strings; sourceTree = ""; }; + 293EA3DC2705964200545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ha; path = ha.lproj/Localizable.strings; sourceTree = ""; }; + 293EA3DD270596B700545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ha; path = ha.lproj/Localizable.stringsdict; sourceTree = ""; }; + 2949146F2738DA6700400732 /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/ResourceInfoView.strings"; sourceTree = ""; }; + 294914702738DD7400400732 /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/Localizable.strings"; sourceTree = ""; }; + 294914712738DD9700400732 /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "ff-NG"; path = "ff-NG.lproj/Localizable.stringsdict"; sourceTree = ""; }; 377D10DD26846B8900467431 /* SpacebarTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacebarTextViewController.swift; sourceTree = ""; }; 6C0A140E151EA930007FA4AD /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 6C0A140F151EA930007FA4AD /* Keyman.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; lineEnding = 0; path = Keyman.xcconfig; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.xcconfig; }; @@ -1171,6 +1177,8 @@ am, "es-419", "zh-Hans", + ha, + "ff-NG", ); mainGroup = F243887314BBD43000A3E055; productRefGroup = F243887F14BBD43000A3E055 /* Products */; @@ -1556,6 +1564,8 @@ CEBD34552655117800EB2EA8 /* am */, CEEF81B82673016E00EE6A07 /* es-419 */, CEEC467E26F2F777009A5B7D /* zh-Hans */, + 293EA3DD270596B700545EED /* ha */, + 294914712738DD9700400732 /* ff-NG */, ); name = Localizable.stringsdict; sourceTree = ""; @@ -1572,6 +1582,8 @@ CEBD34542655117400EB2EA8 /* am */, CEEF81B72673016900EE6A07 /* es-419 */, CEEC467D26F2F76E009A5B7D /* zh-Hans */, + 293EA3DC2705964200545EED /* ha */, + 294914702738DD7400400732 /* ff-NG */, ); name = Localizable.strings; sourceTree = ""; @@ -1589,6 +1601,8 @@ CEBD34532655115800EB2EA8 /* am */, CEEF81AD2672FBE900EE6A07 /* es-419 */, CEEC467526F2F5EE009A5B7D /* zh-Hans */, + 293EA3DB2705955300545EED /* ha */, + 2949146F2738DA6700400732 /* ff-NG */, ); name = ResourceInfoView.xib; sourceTree = ""; diff --git a/ios/engine/KMEI/KeymanEngine/Classes/bwr-ng.lproj/ResourceInfoView.strings b/ios/engine/KMEI/KeymanEngine/Classes/bwr-ng.lproj/ResourceInfoView.strings new file mode 100644 index 0000000000..4dae2c4642 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Classes/bwr-ng.lproj/ResourceInfoView.strings @@ -0,0 +1,2 @@ +/* Class = "UILabel"; text = "Scan this code to load this keyboard on another device"; ObjectID = "z2O-MT-IoV"; */ +"z2O-MT-IoV.text" = "Kǝtǝ fwator kod ngǝni kamnyar hara lodi ar keyboard ata na'ura laga"; diff --git a/ios/engine/KMEI/KeymanEngine/Classes/ff-ng.lproj/ResourceInfoView.strings b/ios/engine/KMEI/KeymanEngine/Classes/ff-ng.lproj/ResourceInfoView.strings new file mode 100644 index 0000000000..f873efdd49 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Classes/ff-ng.lproj/ResourceInfoView.strings @@ -0,0 +1,2 @@ +/* Class = "UILabel"; text = "Scan this code to load this keyboard on another device"; ObjectID = "z2O-MT-IoV"; */ +"z2O-MT-IoV.text" = "Waɗu sikanin lamba ɗo ngam a` wa`ina kibod ɗo dow na`ura feere"; diff --git a/ios/engine/KMEI/KeymanEngine/Classes/ha.lproj/ResourceInfoView.strings b/ios/engine/KMEI/KeymanEngine/Classes/ha.lproj/ResourceInfoView.strings new file mode 100644 index 0000000000..62109d40ef --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Classes/ha.lproj/ResourceInfoView.strings @@ -0,0 +1,2 @@ +/* Class = "UILabel"; text = "Scan this code to load this keyboard on another device"; ObjectID = "z2O-MT-IoV"; */ +"z2O-MT-IoV.text" = "Duba wannan code domin loda wannan madanni akan wata na'urar"; diff --git a/ios/engine/KMEI/KeymanEngine/Classes/mfi.lproj/ResourceInfoView.strings b/ios/engine/KMEI/KeymanEngine/Classes/mfi.lproj/ResourceInfoView.strings new file mode 100644 index 0000000000..8c6037b9d2 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Classes/mfi.lproj/ResourceInfoView.strings @@ -0,0 +1,2 @@ +/* Class = "UILabel"; text = "Scan this code to load this keyboard on another device"; ObjectID = "z2O-MT-IoV"; */ +"z2O-MT-IoV.text" = "Ksa fatawe una gé code gé femhe keyboard am nawura umbulé"; diff --git a/ios/engine/KMEI/KeymanEngine/bwr-ng.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/bwr-ng.lproj/Localizable.strings new file mode 100644 index 0000000000..09e2f411ea --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/bwr-ng.lproj/Localizable.strings @@ -0,0 +1,254 @@ +/* A descriptive message used for errors when the app is already busy downloading */ +"alert-download-error-busy" = "Akwa hara dawunlodin."; + +/* A descriptive message used when a download fails */ +"alert-download-error-detail" = "Su laga na aɗi kalkal wa ku hara saka tǝ akwa hara dawunlodi wala instal ni."; + +/* Title for a "download failed" alert */ +"alert-download-error-title" = "Aɗi hara dawunlod kalkal wa"; + +/* Title for a general alert about errors */ +"alert-error-title" = "Aɗi kalkal wa"; + +/* A descriptive message used when no internet connection is detected */ +"alert-no-connection-detail" = "Aɗi wuta ka mzhitǝ server na ar Keyman wa. Usa, jaktǝ ngkǝrta ka damwa ɗanzi."; + +/* Title for a "no connection" alert */ +"alert-no-connection-title" = "Aɗi kǝɓa kalkal wa"; + +/* Short text for a 'back' navigational command. Used to return to the previous screen */ +"command-back" = "Bwahili"; + +/* Short text for a 'cancel' command. Used to back out of a menu or install process without making changes */ +"command-cancel" = "Awo"; + +/* Short text for a 'done' command. Used to back out of settings menus after changes have been made */ +"command-done" = "Kǝ hara"; + +/* Short text for an 'install' command. Used to confirm installation of a package. */ +"command-install" = "Hara instal"; + +/* Short text for a 'next' navigational command. Used to advance to the next screen */ +"command-next" = "Lǝkǝma"; + +/* Short text for an 'OK' command. Used for some error alerts */ +"command-ok" = "OK"; + +/* Short text for confirmining 'uninstall' commands. */ +"command-uninstall" = "Kǝlɓǝla"; + +/* Text for the command to uninstall a keyboard */ +"command-uninstall-keyboard" = "Kǝlɓǝla keyboard"; + +/* Confirmation text to display before uninstalling a keyboard */ +"command-uninstall-keyboard-confirm" = "Ga ata bara kara kǝlɓǝla keyboard ngǝni ya?"; + +/* Text for the command to uninstall a lexical model */ +"command-uninstall-lexical-model" = "Kǝlɓǝla dictionary"; + +/* Confirmation text to display before uninstalling a lexical model */ +"command-uninstall-lexical-model-confirm" = "Ga ata bara kara kǝlɓǝla dictionary ngǝni ya?"; + +/* Text for error when a keyboard cannot load properly */ +"error-loading-keyboard" = "Aɗi wuta ka hara lodi ar keyboard natǝ mdǝ bara ni wa"; + +/* Text for error when a lexical model cannot load properly */ +"error-loading-lexical-model" = "Aɗi wuta ka hara lodi ar dictionary natǝ mdǝ bara ni wa"; + +/* Text for error when an installed file is unexpectedly missing */ +"error-missing-file" = "Fayil natǝ natǝ mda akwa bara aɗi wutǝdzi wa."; + +/* Text for error when an installed file is unexpectedly missing */ +"error-missing-file-critical" = "Fayil laga naka duna aɗi wutǝdzi wa. Usa, jaktǝ ngkǝrtǝ hara instal app ni."; + +/* Text for errors in data received from search queries */ +"error-query-decoding" = "Sava ni kulini akwa wutǝ dametǝdziyeri"; + +/* A descriptive message used when a download fails */ +"error-query-general" = "Dametǝdzi aɗi na ar lakur hamta ka wutǝ labar akwa sava ni"; + +/* Text for error when a search query is unexpectedly empty */ +"error-query-no-data" = "Sava ni ngǝla ta kalaktǝ labar"; + +/* Text for general errors where not much information is known */ +"error-unknown" = "Dametǝdzi natǝ mda aɗi tamata wa ku hara"; + +/* Text for error when updating a resource: cannot download because no source is available */ +"error-update-no-link" = "Mda aɗi wutǝ su laga na bǝlin wa na ar package ngǝni wa"; + +/* Text for error when updating a resource: Keyman does not know how to update the resource */ +"error-update-not-managed" = "Aɗa tsoktǝ wutǝ kariyeri na bǝlin wa natǝ aɗi akira injin ar Keyman wa"; + +/* Text for the command to open the help page of a keyboard, as shown on resource information views */ +"info-command-help-keyboard" = "Dlara ar Keyboard"; + +/* Text for the command to open the help page of a lexical model, as shown on resource information views */ +"info-command-help-lexical-model" = "Dlara ar dictionary"; + +/* Text label for displaying a keyboard's version, as shown on resource information views */ +"info-label-version-keyboard" = "Jili keyboard"; + +/* Text label for displaying a lexical model's version, as shown on resource information views */ +"info-label-version-lexical-model" = "Jili dictionary"; + +/* Label used for the "package info" / readme tab with the package installation prompt on phones. */ +"installer-label-package-info" = "Labar ar package"; + +/* Label used for the language-selection tab with the package installation prompt on phones. */ +"installer-label-select-languages" = "Carɓǝla hulfur mnya(yeri)"; + +/* Label used with a package's version, as seen within the package installation prompt. Example: "Version: 14.0.0" */ +"installer-label-version" = "Jili: %@"; + +/* Section header for languages supported by a package, as seen within the package installation prompt */ +"installer-section-available-languages" = "Hulfur mnyayeri na aɗi"; + +/* Text for the help popup for changing keyboards with the globe key */ +"keyboard-help-change" = "Tsukwa vi ngǝni kamnyar pali keyboard ni"; + +/* Text for the command to exit the Keyman keyboard in favor of other keyboards installed on the system */ +"keyboard-menu-exit" = "Harta %@"; + +/* Error installing a Keyman package - could not allocate a location to install it */ +"kmp-error-file-system" = "Dametǝdzi akwa instal ar package ni - dametǝdzi ar fayile-system"; + +/* Error installing a Keyman package - could not copy package files */ +"kmp-error-file-copying" = "Dametǝdzi akwa instal ar package ni - aɗa tsoktǝ hara kopi fayilyeri natǝ mda akwa bara wa"; + +/* Error opening a Keyman package - package is not valid */ +"kmp-error-invalid" = "Fayil ar package ni ku hili."; + +/* Error opening a Keyman package - it does not exist / the specified location is wrong */ +"kmp-error-missing" = "Package natǝ mdǝ ngkǝrɓǝlani ni aɗiwa."; + +/* Error installing a Keyman package - expected resource (keyboard or dictionary) is missing */ +"kmp-error-missing-resource" = "Package ngǝni aɗika keyboard wala dictionary natǝ mdǝ bara wa."; + +/* Error opening a Keyman package - cannot parse contents */ +"kmp-error-no-metadata" = "Package ngǝni mda aɗi namni ɓɗakǝlaga wa - mda aɗi sǝndǝɓǝla suayeri na akwa wa."; + +/* Error opening a Keyman package - package's contents are for desktop platforms only */ +"kmp-error-unsupported" = "Package ngǝni aɗi paktǝ ka dlara na kalkal na ar na'ura ngǝ ni wa."; + +/* Error opening a Keyman package - package contains unexpected resource (keyboard or dictionary) type */ +"kmp-error-wrong-type" = "Package ndǝni aɗika jili kariyeri natǝ mdǝ tamata abbǝr wa tsakari wa."; + +/* Title for the Installed Languages menu */ +"menu-installed-languages-title" = "Hulfur mnyayeri natǝ mdǝkǝ hara installari"; + +/* Section header for lexical models within a language-specific settings menu */ +"menu-langsettings-label-lexical-models" = "Dictionariyeri"; + +/* Section header for keyboards within a language-specific settings menu */ +"menu-langsettings-section-keyboards" = "Keyboardyeri"; + +/* Section header for the settings toggles within a language-specific settings menu */ +"menu-langsettings-section-settings" = "Setiyeri ar Hulfur mnya"; + +/* Title for the language-specific settings menus */ +"menu-langsettings-title" = "%@ Setiyeri"; + +/* Label for the toggle that enables corrections that is displayed within a language-specific settings menu */ +"menu-langsettings-toggle-correct" = "Vu ka tsa namtǝdzi"; + +/* Label for the toggle that enables predictions that is displayed within a language-specific settings menu */ +"menu-langsettings-toggle-predict" = "Vu katsa hamta sauri su natǝ asi"; + +/* Help message for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */ +"menu-lexical-model-install-message" = "Ga ata bara kara hara instal ar dictionary ngǝni ya?"; + +/* Title for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */ +"menu-lexical-model-install-title" = "%1$@: %2$@"; + +/* Text for an info alert indicating that no lexical models are available */ +"menu-lexical-model-none-message" = "Dictionariyeri aɗiwa"; + +/* Title for the lexical model menu, a submenu of the language-specific settings menus */ +"menu-lexical-model-title" = "%@ Dictionariyeri"; + +/* Title for the keyboard picker */ +"menu-picker-title" = "Keyboardyeri"; + +/* Primary text for the Settings menu option to report keyboard crashes */ +"menu-settings-error-report" = "Zhara katsa ana hamta ndǝr ar dametǝdzi"; + +/* Secondary text for the Settings menu option to report keyboard crashes */ +"menu-settings-error-report-description" = "Nughum ata bara \"wutǝ shanga lakuyeri ni\""; + +/* Primary text for the Settings menu local-file package installation option */ +"menu-settings-install-from-file" = "Hara instal atar fayil"; + +/* Secondary text for the Settings menu local-file package installation option */ +"menu-settings-install-from-file-description" = "Pakǝ fayilyeri na jili ar .kmp"; + +/* Primary text for the Settings menu option that displays the iOS system menu options for the app's keyboard */ +"menu-settings-system-keyboard-menu" = "Setiyeri na ar Keyboard ar system ni"; + +/* Label for the "Show Banner" toggle on the main settings screen */ +"menu-settings-show-banner" = "Ncangtǝ Banner"; + +/* Label for the "Get Started" automatic display toggle seen in the Settings menu */ +"menu-settings-startup-get-started" = "Ncangtǝ ʻBaɗita Kuliniʻ saka tǝ mdǝ baɗita"; + +/* Title for the main Settings menu */ +"menu-settings-title" = "Setiyeri na ar Keyman"; + +/* Secondary text showing current setting for spacebar caption - blank */ +"menu-settings-spacebar-hint-blank" = "Aɗe ncangtǝ rubutǝni ata spesba wa"; + +/* Secondary text showing current setting for spacebar caption - keyboard */ +"menu-settings-spacebar-hint-keyboard" = "Ncangtǝ thlǝma keyboard ni ata spesba ni"; + +/* Secondary text showing current setting for spacebar caption - language */ +"menu-settings-spacebar-hint-language" = "Ncangtǝ thlǝma hulfur mnya ni ata spesba ni"; + +/* Secondary text showing current setting for spacebar caption - language + keyboard */ +"menu-settings-spacebar-hint-languageKeyboard" = "Ncantǝ thlǝma hulfur mnya ka keyboard ni ata spesba ni"; + +/* Label for the "Spacebar Caption" item on the main settings screen */ +"menu-settings-spacebar-text" = "Rubutu na ar Spesba"; + +/* Title for the "Spacebar Caption" settings screen */ +"menu-settings-spacebar-title" = "Rubutu na ar Spesba"; + +/* Text showing name of spacebar caption - blank */ +"menu-settings-spacebar-item-blank" = "Kula rubutu ata"; + +/* Text showing name of spacebar caption - keyboard */ +"menu-settings-spacebar-item-keyboard" = "Keyboard"; + +/* Text showing name of spacebar caption - language */ +"menu-settings-spacebar-item-language" = "Hulfur mnya"; + +/* Text showing name of spacebar caption - language + keyboard */ +"menu-settings-spacebar-item-languageKeyboard" = "Hulfur mnya ka keyboard"; + +/* Short text for notification: download failure for keyboard */ +"notification-download-failure-keyboard" = "Aɗi hara dawunlodi ar keyboard wa"; + +/* Short text for notification: download failure for lexical model */ +"notification-download-failure-lexical-model" = "Aɗi hara dawunlodi ar dictionary wa"; + +/* Short text for notification: download success for keyboard */ +"notification-download-success-keyboard" = "Kǝ hara dawunlodi ar keyboard ɓɗakǝlaka"; + +/* Short text for notification: download success for lexical model */ +"notification-download-success-lexical-model" = "Kǝ hara dawunlodi ar lexical model ɓɗakǝlaka"; + +/* Short text for notification: downloading a keyboard */ +"notification-downloading-keyboard" = "Akwa hara dawunlodi ar keyboard\U2026"; + +/* Short text for notification: downloading a lexical model */ +"notification-downloading-lexical-model" = "Akwa hara dawunlodi ar dictionary\U2026"; + +/* Short text for notification: an update is available */ +"notification-update-available" = "Su na bǝlin aɗi"; + +/* Short text for notification: currently updating */ +"notification-update-processing" = "Updating\U2026"; + +/* Text indicating success at installing new keyboards or dictionaries */ +"success-install" = "Kǝ hara instal ɓɗakǝlaka."; + +/* A title to use in alerts indicating 'success' at whatever task the user requested */ +"success-title" = "Kǝ hara ɓɗaku"; diff --git a/ios/engine/KMEI/KeymanEngine/bwr-ng.lproj/Localizable.stringsdict b/ios/engine/KMEI/KeymanEngine/bwr-ng.lproj/Localizable.stringsdict new file mode 100644 index 0000000000..4395a4d729 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/bwr-ng.lproj/Localizable.stringsdict @@ -0,0 +1,118 @@ + + + + + menu-langsettings-lexical-model-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u dictionari natǝ mdǝku hara instal ni + other + %u dictionariyeri natǝ mdǝku hara instal ɗa + + + notification-update-failed + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u update ni aɗi hara wa + other + %u updatyeri ni aɗi hara wa + + + notification-update-success + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u update ku hara ɓɗaku + other + %u updatyeri ku hara ɓɗaku + + + settings-keyboards-installed-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u keyboard ku hara instal + other + %u keyboardyeri ku hara instal + + + settings-languages-installed-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u hulfur mnya ku hara instal + other + %u hulfur mnyayeri ku hara instal + + + package-default-found-keyboards + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + Ku wutǝ %u keyboard akwa package: + other + Ku wutǝ %u keyboardyeri akwa package: + + + package-default-found-lexical-models + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + Ku wutǝ %u dictionary akwa package: + other + Ku wutǝ %u dictionariyeri akwa package: + + + + diff --git a/ios/engine/KMEI/KeymanEngine/ff-ng.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/ff-ng.lproj/Localizable.strings new file mode 100644 index 0000000000..172c65ac55 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/ff-ng.lproj/Localizable.strings @@ -0,0 +1,254 @@ +/* A descriptive message used for errors when the app is already busy downloading */ +"alert-download-error-busy" = "Ɗo arti sukli jippingo."; + +/* A descriptive message used when a download fails */ +"alert-download-error-detail" = "Wodi masala wakkati jippingo ko wa`inngo."; + +/* Title for a "download failed" alert */ +"alert-download-error-title" = "Jippingo ɗon masala"; + +/* Title for a general alert about errors */ +"alert-error-title" = "Masala"; + +/* A descriptive message used when no internet connection is detected */ +"alert-no-connection-detail" = "Yahata ha sava Keyman. Usseni a` foondita yeeso."; + +/* Title for a "no connection" alert */ +"alert-no-connection-title" = "Nastugo man ɗon masala"; + +/* Short text for a 'back' navigational command. Used to return to the previous screen */ +"command-back" = "Ɓaawo"; + +/* Short text for a 'cancel' command. Used to back out of a menu or install process without making changes */ +"command-cancel" = "Wurtu"; + +/* Short text for a 'done' command. Used to back out of settings menus after changes have been made */ +"command-done" = "Waɗi"; + +/* Short text for an 'install' command. Used to confirm installation of a package. */ +"command-install" = "Wa`unu"; + +/* Short text for a 'next' navigational command. Used to advance to the next screen */ +"command-next" = "Yeeso"; + +/* Short text for an 'OK' command. Used for some error alerts */ +"command-ok" = "Toh"; + +/* Short text for confirmining 'uninstall' commands. */ +"command-uninstall" = "A` itta"; + +/* Text for the command to uninstall a keyboard */ +"command-uninstall-keyboard" = "A` itta kibod"; + +/* Confirmation text to display before uninstalling a keyboard */ +"command-uninstall-keyboard-confirm" = "A` yiɗan ittugo kibod ɗo na?"; + +/* Text for the command to uninstall a lexical model */ +"command-uninstall-lexical-model" = "A` itta deftere ma`ana kalimaji"; + +/* Confirmation text to display before uninstalling a lexical model */ +"command-uninstall-lexical-model-confirm" = "A` yiɗan ittugo deftere ma`ana kalimaji ɗo na?"; + +/* Text for error when a keyboard cannot load properly */ +"error-loading-keyboard" = "Sali nastugo nder kibod ko mari haaje man."; + +/* Text for error when a lexical model cannot load properly */ +"error-loading-lexical-model" = "Sali nastugo nder deftere ma`ana kalimaji ko mari haaje man"; + +/* Text for error when an installed file is unexpectedly missing */ +"error-missing-file" = "Fayel ko a` mari haaje man keptataako."; + +/* Text for error when an installed file is unexpectedly missing */ +"error-missing-file-critical" = "Fayel ko mari haaje man keptataako. Useni a` foonda lorugo wa`inna app man."; + +/* Text for errors in data received from search queries */ +"error-query-decoding" = "Sava man ɗo kabda bee masala feere"; + +/* A descriptive message used when a download fails */ +"error-query-general" = "Masala haa famtindirgo bee sava man"; + +/* Text for error when a search query is unexpectedly empty */ +"error-query-no-data" = "Sava man sali nootugo"; + +/* Text for general errors where not much information is known */ +"error-unknown" = "Masala ko tammay fe`i"; + +/* Text for error when updating a resource: cannot download because no source is available */ +"error-update-no-link" = "Walaa kare kuuɗe jee pakej ɗo ko ɓe kesinta"; + +/* Text for error when updating a resource: Keyman does not know how to update the resource */ +"error-update-not-managed" = "Waɗataa kesitol kare kuuɗe ko naa Inji Keyman ba mari"; + +/* Text for the command to open the help page of a keyboard, as shown on resource information views */ +"info-command-help-keyboard" = "Mballa Kibod"; + +/* Text for the command to open the help page of a lexical model, as shown on resource information views */ +"info-command-help-lexical-model" = "Mballa deftere ma`ana kalimaji"; + +/* Text label for displaying a keyboard's version, as shown on resource information views */ +"info-label-version-keyboard" = "Nonde Kibod"; + +/* Text label for displaying a lexical model's version, as shown on resource information views */ +"info-label-version-lexical-model" = "Nonde deftere ma`ana kalimaji"; + +/* Label used for the "package info" / readme tab with the package installation prompt on phones. */ +"installer-label-package-info" = "Bayanu pakej"; + +/* Label used for the language-selection tab with the package installation prompt on phones. */ +"installer-label-select-languages" = "Suptu ɗemngal (ɗe)"; + +/* Label used with a package's version, as seen within the package installation prompt. Example: "Version: 14.0.0" */ +"installer-label-version" = "Nonde: %@"; + +/* Section header for languages supported by a package, as seen within the package installation prompt */ +"installer-section-available-languages" = "Ɗemngal ko ɗo feesi"; + +/* Text for the help popup for changing keyboards with the globe key */ +"keyboard-help-change" = "A` meema haa ɗow ngam a` wayla kibod"; + +/* Text for the command to exit the Keyman keyboard in favor of other keyboards installed on the system */ +"keyboard-menu-exit" = "Wurtu %@"; + +/* Error installing a Keyman package - could not allocate a location to install it */ +"kmp-error-file-system" = "Masala haa wa`inngo pakej - masala ha fayel komputa"; + +/* Error installing a Keyman package - could not copy package files */ +"kmp-error-file-copying" = "Masala ha wa`ingo pakej - tongata fayel ko mari haaje"; + +/* Error opening a Keyman package - package is not valid */ +"kmp-error-invalid" = "Fayel pakej man wonni."; + +/* Error opening a Keyman package - it does not exist / the specified location is wrong */ +"kmp-error-missing" = "Pakej ko suɓi man wala."; + +/* Error installing a Keyman package - expected resource (keyboard or dictionary) is missing */ +"kmp-error-missing-resource" = "Pakej ɗo walaa kibod ko deftere ma`ana kalimaji ko mari haaje."; + +/* Error opening a Keyman package - cannot parse contents */ +"kmp-error-no-metadata" = "Pakej ɗo ɓe waɗay ɗum no haandi - ko ɗum koosi andaaka."; + +/* Error opening a Keyman package - package's contents are for desktop platforms only */ +"kmp-error-unsupported" = "Pakej ɗo wa`ata dow na`ura ma."; + +/* Error opening a Keyman package - package contains unexpected resource (keyboard or dictionary) type */ +"kmp-error-wrong-type" = "Pakej ɗo walaa kare kuuɗe ko tammi."; + +/* Title for the Installed Languages menu */ +"menu-installed-languages-title" = "Ɗemngal ko wa`inni"; + +/* Section header for lexical models within a language-specific settings menu */ +"menu-langsettings-label-lexical-models" = "Deftere ma`ana kalimaji"; + +/* Section header for keyboards within a language-specific settings menu */ +"menu-langsettings-section-keyboards" = "Kibod"; + +/* Section header for the settings toggles within a language-specific settings menu */ +"menu-langsettings-section-settings" = "Setin ɗemngal"; + +/* Title for the language-specific settings menus */ +"menu-langsettings-title" = "%@ Setin"; + +/* Label for the toggle that enables corrections that is displayed within a language-specific settings menu */ +"menu-langsettings-toggle-correct" = "Accu wo`inningo"; + +/* Label for the toggle that enables predictions that is displayed within a language-specific settings menu */ +"menu-langsettings-toggle-predict" = "Accu tammunde"; + +/* Help message for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */ +"menu-lexical-model-install-message" = "A` yiɗan wa`inngo deftere ma`ana kalimaji ɗo na?"; + +/* Title for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */ +"menu-lexical-model-install-title" = "%1$@: %2$@"; + +/* Text for an info alert indicating that no lexical models are available */ +"menu-lexical-model-none-message" = "Walaa deftere ma`ana kalimaji ko ɗo feesi"; + +/* Title for the lexical model menu, a submenu of the language-specific settings menus */ +"menu-lexical-model-title" = "%@ Deftere ma`ana kalimaji"; + +/* Title for the keyboard picker */ +"menu-picker-title" = "Kibod"; + +/* Primary text for the Settings menu option to report keyboard crashes */ +"menu-settings-error-report" = "Accu wullugo masala"; + +/* Secondary text for the Settings menu option to report keyboard crashes */ +"menu-settings-error-report-description" = "Maran haaje \"nastugo fuu\""; + +/* Primary text for the Settings menu local-file package installation option */ +"menu-settings-install-from-file" = "Wa`unu diga fayel"; + +/* Secondary text for the Settings menu local-file package installation option */ +"menu-settings-install-from-file-description" = "Waɗu burawzin fayel kmp"; + +/* Primary text for the Settings menu option that displays the iOS system menu options for the app's keyboard */ +"menu-settings-system-keyboard-menu" = "Setin kibod na`ura"; + +/* Label for the "Show Banner" toggle on the main settings screen */ +"menu-settings-show-banner" = "Hollu alaama"; + +/* Label for the "Get Started" automatic display toggle seen in the Settings menu */ +"menu-settings-startup-get-started" = "Hollu ʻFuɗɗutuʻ haa dow fuɗɗol"; + +/* Title for the main Settings menu */ +"menu-settings-title" = "Setin Keyman"; + +/* Secondary text showing current setting for spacebar caption - blank */ +"menu-settings-spacebar-hint-blank" = "Taa hollu kapshon dow tappirɗe babal"; + +/* Secondary text showing current setting for spacebar caption - keyboard */ +"menu-settings-spacebar-hint-keyboard" = "Hollu inde kobod dow tappirɗe babal"; + +/* Secondary text showing current setting for spacebar caption - language */ +"menu-settings-spacebar-hint-language" = "Hollu inde ɗemngal dow tappirɗe babal"; + +/* Secondary text showing current setting for spacebar caption - language + keyboard */ +"menu-settings-spacebar-hint-languageKeyboard" = "Hollu inde ɗemngal bee inde kibod dow tappirɗe babal"; + +/* Label for the "Spacebar Caption" item on the main settings screen */ +"menu-settings-spacebar-text" = "Kapshon tappirɗe babal"; + +/* Title for the "Spacebar Caption" settings screen */ +"menu-settings-spacebar-title" = "Kapshon tappirɗe babal"; + +/* Text showing name of spacebar caption - blank */ +"menu-settings-spacebar-item-blank" = "Walaa kooɗume"; + +/* Text showing name of spacebar caption - keyboard */ +"menu-settings-spacebar-item-keyboard" = "Kibod"; + +/* Text showing name of spacebar caption - language */ +"menu-settings-spacebar-item-language" = "Ɗemngal"; + +/* Text showing name of spacebar caption - language + keyboard */ +"menu-settings-spacebar-item-languageKeyboard" = "Ɗemngal bee Kibod"; + +/* Short text for notification: download failure for keyboard */ +"notification-download-failure-keyboard" = "Jippingo kibod sali"; + +/* Short text for notification: download failure for lexical model */ +"notification-download-failure-lexical-model" = "Jippingo deftere ma`ana kalimaji man sali"; + +/* Short text for notification: download success for keyboard */ +"notification-download-success-keyboard" = "Kibod man timmi jippugo"; + +/* Short text for notification: download success for lexical model */ +"notification-download-success-lexical-model" = "Nonde lezikal timmini jippugo"; + +/* Short text for notification: downloading a keyboard */ +"notification-downloading-keyboard" = "Ɗo jippina kibod\U2026"; + +/* Short text for notification: downloading a lexical model */ +"notification-downloading-lexical-model" = "Deftere ma`ana kalimaji ɗo jippa\U2026"; + +/* Short text for notification: an update is available */ +"notification-update-available" = "Kesitiggo ɗo feesi"; + +/* Short text for notification: currently updating */ +"notification-update-processing" = "Ɗo kesita\U2026"; + +/* Text indicating success at installing new keyboards or dictionaries */ +"success-install" = "Wa`ungo timmini."; + +/* A title to use in alerts indicating 'success' at whatever task the user requested */ +"success-title" = "Waɗi"; diff --git a/ios/engine/KMEI/KeymanEngine/ff-ng.lproj/Localizable.stringsdict b/ios/engine/KMEI/KeymanEngine/ff-ng.lproj/Localizable.stringsdict new file mode 100644 index 0000000000..4e8b393538 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/ff-ng.lproj/Localizable.stringsdict @@ -0,0 +1,118 @@ + + + + + menu-langsettings-lexical-model-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u Deftere ma`ana kalimaji man wa`ii + other + %u Deftere ma`ana kalimaji man wa`i + + + notification-update-failed + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u update failed + other + %u updates failed + + + notification-update-success + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u update successful + other + %u updates successful + + + settings-keyboards-installed-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u keyboard installed + other + %u keyboards installed + + + settings-languages-installed-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u language installed + other + %u languages installed + + + package-default-found-keyboards + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + Found %u keyboard in package: + other + Found %u keyboards in package: + + + package-default-found-lexical-models + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + Found %u dictionary in package: + other + Found %u dictionaries in package: + + + + diff --git a/ios/engine/KMEI/KeymanEngine/ha.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/ha.lproj/Localizable.strings new file mode 100644 index 0000000000..d1535e2bcf --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/ha.lproj/Localizable.strings @@ -0,0 +1,248 @@ +/* A descriptive message used for errors when the app is already busy downloading */ +"alert-download-error-busy" = "Tuni ana kan saukewa."; + +/* A descriptive message used when a download fails */ +"alert-download-error-detail" = "An sami matsala yayin saukewa ko shigarwa."; + +/* Title for a "download failed" alert */ +"alert-download-error-title" = "Akwai matsalar saukewa"; + +/* Title for a general alert about errors */ +"alert-error-title" = "Matsala"; + +/* A descriptive message used when no internet connection is detected */ +"alert-no-connection-detail" = "An kasa kaiwa ga rumbun bayanan Keyman. Fatan za a sake gwadawa daga baya."; + +/* Title for a "no connection" alert */ +"alert-no-connection-title" = "Matsalar Haɗawa"; + +/* Short text for a 'back' navigational command. Used to return to the previous screen */ +"command-back" = "Baya"; + +/* Short text for a 'cancel' command. Used to back out of a menu or install process without making changes */ +"command-cancel" = "Sokewa"; + +/* Short text for a 'done' command. Used to back out of settings menus after changes have been made */ +"command-done" = "An kammala"; + +/* Short text for an 'install' command. Used to confirm installation of a package. */ +"command-install" = "Shigar"; + +/* Short text for a 'next' navigational command. Used to advance to the next screen */ +"command-next" = "Gaba"; + +/* Short text for an 'OK' command. Used for some error alerts */ +"command-ok" = "OK"; + +/* Short text for confirmining 'uninstall' commands. */ +"command-uninstall" = "Cirewa"; + +/* Text for the command to uninstall a keyboard */ +"command-uninstall-keyboard" = "Madannin da aka cire"; + +/* Confirmation text to display before uninstalling a keyboard */ +"command-uninstall-keyboard-confirm" = "Ana son a cire wannan madannin?"; + +/* Text for the command to uninstall a lexical model */ +"command-uninstall-lexical-model" = "Ƙamus ɗin da aka cire"; + +/* Confirmation text to display before uninstalling a lexical model */ +"command-uninstall-lexical-model-confirm" = "Ana son a cire wannan ƙamus?"; + +/* Text for error when a keyboard cannot load properly */ +"error-loading-keyboard" = "An kasa loda madannin da aka nema"; + +/* Text for error when a lexical model cannot load properly */ +"error-loading-lexical-model" = "An kasa loda ƙamus ɗin da aka nema"; + +/* Text for error when an installed file is unexpectedly missing */ +"error-missing-file" = "An kasa samun fayil ɗin da aka nema."; + +/* Text for error when an installed file is unexpectedly missing */ +"error-missing-file-critical" = "An kasa samun file ɗin da ake so. A yi haƙuri a ƙara shigar da app ɗin."; + +/* Text for errors in data received from search queries */ +"error-query-decoding" = "A yanzu haka rumbun bayanan na fuskantar matsaloli"; + +/* A descriptive message used when a download fails */ +"error-query-general" = "Akwai matsalar sadarwa tsakani da rumbun bayanan"; + +/* Text for error when a search query is unexpectedly empty */ +"error-query-no-data" = "Rumbun bayanan yaƙi aiki"; + +/* Text for general errors where not much information is known */ +"error-unknown" = "Matsalar da ba a yi tsammani ba ta afku"; + +/* Text for error when updating a resource: cannot download because no source is available */ +"error-update-no-link" = "Ba a sami tushen sabuntawa ba domin wannan ƙunshin"; + +/* Text for error when updating a resource: Keyman does not know how to update the resource */ +"error-update-not-managed" = "Ba za a iya sabunta abubuwan da ba injin Keyman ne ke kulawa da shi ba"; + +/* Text for the command to open the help page of a keyboard, as shown on resource information views */ +"info-command-help-keyboard" = "Taimakon Madanni"; + +/* Text for the command to open the help page of a lexical model, as shown on resource information views */ +"info-command-help-lexical-model" = "Taimakon ƙamus"; + +/* Text label for displaying a keyboard's version, as shown on resource information views */ +"info-label-version-keyboard" = "Nau'in Madanni"; + +/* Text label for displaying a lexical model's version, as shown on resource information views */ +"info-label-version-lexical-model" = "Nau'in ƙamus"; + +/* Label used for the "package info" / readme tab with the package installation prompt on phones. */ +"installer-label-package-info" = "Ƙunshin bayanai"; + +/* Label used for the language-selection tab with the package installation prompt on phones. */ +"installer-label-select-languages" = "Zaɓi yare/yaruka"; + +/* Label used with a package's version, as seen within the package installation prompt. Example: "Version: 14.0.0" */ +"installer-label-version" = "Nau'in: %@"; + +/* Section header for languages supported by a package, as seen within the package installation prompt */ +"installer-section-available-languages" = "Yarukan da ke da akwai"; + +/* Text for the help popup for changing keyboards with the globe key */ +"keyboard-help-change" = "Danna nan wajen domin canza madanni"; + +/* Text for the command to exit the Keyman keyboard in favor of other keyboards installed on the system */ +"keyboard-menu-exit" = "Rufe %@"; + +/* Error installing a Keyman package - could not allocate a location to install it */ +"kmp-error-file-system" = "Matsalar shigar da ƙunshin - file-system na da matsala"; + +/* Error installing a Keyman package - could not copy package files */ +"kmp-error-file-copying" = "Matsalar sauke ƙunshin - ba za a iya kwafar fayilolin da ake buƙata ba"; + +/* Error opening a Keyman package - package is not valid */ +"kmp-error-invalid" = "Ƙunshin fayil ɗin ya lalace."; + +/* Error installing a Keyman package - expected resource (keyboard or dictionary) is missing */ +"kmp-error-missing-resource" = "Wannan ƙunshin bai ƙunshi madannai ko ƙamus ɗin da aka nema ba."; + +/* Error opening a Keyman package - cannot parse contents */ +"kmp-error-no-metadata" = "Ba a samar da wannan ƙunshin yadda ya kamata ba - ba a san abubuwan ba."; + +/* Error opening a Keyman package - package contains unexpected resource (keyboard or dictionary) type */ +"kmp-error-wrong-type" = "Wannan ƙunshin bai ƙunshi nau'in abubuwan da ake tsammani ba."; + +/* Title for the Installed Languages menu */ +"menu-installed-languages-title" = "Harsunan da aka shigar"; + +/* Section header for lexical models within a language-specific settings menu */ +"menu-langsettings-label-lexical-models" = "Ƙamus"; + +/* Section header for keyboards within a language-specific settings menu */ +"menu-langsettings-section-keyboards" = "Madannai"; + +/* Section header for the settings toggles within a language-specific settings menu */ +"menu-langsettings-section-settings" = "Saitin Yaruka"; + +/* Title for the language-specific settings menus */ +"menu-langsettings-title" = "Saiti %@"; + +/* Label for the toggle that enables corrections that is displayed within a language-specific settings menu */ +"menu-langsettings-toggle-correct" = "Damar gyare-gyare"; + +/* Label for the toggle that enables predictions that is displayed within a language-specific settings menu */ +"menu-langsettings-toggle-predict" = "Damar hasashe"; + +/* Help message for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */ +"menu-lexical-model-install-message" = "Ana son shigar da wannan ƙamus?"; + +/* Title for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */ +"menu-lexical-model-install-title" = "%1$@: %2$@"; + +/* Text for an info alert indicating that no lexical models are available */ +"menu-lexical-model-none-message" = "Babu ƙamus"; + +/* Title for the lexical model menu, a submenu of the language-specific settings menus */ +"menu-lexical-model-title" = "%@ Ƙamus"; + +/* Title for the keyboard picker */ +"menu-picker-title" = "Madannai"; + +/* Primary text for the Settings menu option to report keyboard crashes */ +"menu-settings-error-report" = "Damar bada rahoton matsala"; + +/* Secondary text for the Settings menu option to report keyboard crashes */ +"menu-settings-error-report-description" = "Ana iya buƙatar \"cikakkiyar dama\""; + +/* Primary text for the Settings menu local-file package installation option */ +"menu-settings-install-from-file" = "Shigarwa Daga Fayil"; + +/* Secondary text for the Settings menu local-file package installation option */ +"menu-settings-install-from-file-description" = "Nemowa domin .fayil ɗin kmp"; + +/* Primary text for the Settings menu option that displays the iOS system menu options for the app's keyboard */ +"menu-settings-system-keyboard-menu" = "Saitin Madannan Na'ura"; + +/* Label for the "Show Banner" toggle on the main settings screen */ +"menu-settings-show-banner" = "Nuna Banner"; + +/* Label for the "Get Started" automatic display toggle seen in the Settings menu */ +"menu-settings-startup-get-started" = "Nuna ʻFarawaʻ daga farko"; + +/* Title for the main Settings menu */ +"menu-settings-title" = "Saitin Keyman"; + +/* Secondary text showing current setting for spacebar caption - blank */ +"menu-settings-spacebar-hint-blank" = "Kada a nuna komai akan madannin bada tazara"; + +/* Secondary text showing current setting for spacebar caption - keyboard */ +"menu-settings-spacebar-hint-keyboard" = "Nuna sunan madanni akan madannin bada tazara"; + +/* Secondary text showing current setting for spacebar caption - language */ +"menu-settings-spacebar-hint-language" = "Nuna sunan yare akan madannin bada tazara"; + +/* Secondary text showing current setting for spacebar caption - language + keyboard */ +"menu-settings-spacebar-hint-languageKeyboard" = "Nuna sunan yare da madanni akan madannin bada tazara"; + +/* Label for the "Spacebar Caption" item on the main settings screen */ +"menu-settings-spacebar-text" = "Madannin bada Tazara"; + +/* Title for the "Spacebar Caption" settings screen */ +"menu-settings-spacebar-title" = "Madannin bada Tazara"; + +/* Text showing name of spacebar caption - blank */ +"menu-settings-spacebar-item-blank" = "Babu komai"; + +/* Text showing name of spacebar caption - keyboard */ +"menu-settings-spacebar-item-keyboard" = "Madanni"; + +/* Text showing name of spacebar caption - language */ +"menu-settings-spacebar-item-language" = "Yare"; + +/* Text showing name of spacebar caption - language + keyboard */ +"menu-settings-spacebar-item-languageKeyboard" = "Yare da Madanni"; + +/* Short text for notification: download failure for keyboard */ +"notification-download-failure-keyboard" = "An kasa sauke madanni"; + +/* Short text for notification: download failure for lexical model */ +"notification-download-failure-lexical-model" = "An kasa sauke ƙamus"; + +/* Short text for notification: download success for keyboard */ +"notification-download-success-keyboard" = "An yi nasarar sauke madanni"; + +/* Short text for notification: download success for lexical model */ +"notification-download-success-lexical-model" = "An yi nasarar sauke Lexical model"; + +/* Short text for notification: downloading a keyboard */ +"notification-downloading-keyboard" = "Ana kan sauke madannai\U2026"; + +/* Short text for notification: downloading a lexical model */ +"notification-downloading-lexical-model" = "Ana kan sauke ƙamus\U2026"; + +/* Short text for notification: an update is available */ +"notification-update-available" = "An sami sabuntawa"; + +/* Short text for notification: currently updating */ +"notification-update-processing" = "Sabuntawar\U2026"; + +/* Text indicating success at installing new keyboards or dictionaries */ +"success-install" = "An yi nasarar shigarwa."; + +/* A title to use in alerts indicating 'success' at whatever task the user requested */ +"success-title" = "Nasara"; diff --git a/ios/engine/KMEI/KeymanEngine/ha.lproj/Localizable.stringsdict b/ios/engine/KMEI/KeymanEngine/ha.lproj/Localizable.stringsdict new file mode 100644 index 0000000000..a02297649c --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/ha.lproj/Localizable.stringsdict @@ -0,0 +1,118 @@ + + + + + menu-langsettings-lexical-model-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + An shigar da ƙamus %u + other + An shigar da duk ƙamus %u + + + notification-update-failed + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + an kasa sabunta %u + other + an kasa sabuntawar %u + + + notification-update-success + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + An yi nasarar sabunta %u + other + An sami nasarar sabunta %u + + + settings-keyboards-installed-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + An shigar da madannin %u + other + An shigar da madannan %u + + + settings-languages-installed-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + An shigar da yaren %u + other + An shigar da yarukan %u + + + package-default-found-keyboards + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + An samo madanni %u a ƙunshin: + other + An samo madannai %u a ƙunshin: + + + package-default-found-lexical-models + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + An samo ƙamus %u a ƙunshin: + other + An sami ƙamus %u a ƙunshin: + + + + diff --git a/ios/engine/KMEI/KeymanEngine/mfi.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/mfi.lproj/Localizable.strings new file mode 100644 index 0000000000..411a9b427e --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/mfi.lproj/Localizable.strings @@ -0,0 +1,254 @@ +/* A descriptive message used for errors when the app is already busy downloading */ +"alert-download-error-busy" = "Aŋkwa ŋane a tsukwane."; + +/* A descriptive message used when a download fails */ +"alert-download-error-detail" = "Aŋkwa kuskure a magave am sarta tsukwane mtu femhe."; + +/* Title for a "download failed" alert */ +"alert-download-error-title" = "Kuskure tsukwanahe"; + +/* Title for a general alert about errors */ +"alert-error-title" = "Kuskure"; + +/* A descriptive message used when no internet connection is detected */ +"alert-no-connection-detail" = "Magavka shanahe Keyman server. Tazlaŋa ká naba gwada adaliya."; + +/* Title for a "no connection" alert */ +"alert-no-connection-title" = "Kuskure Connection"; + +/* Short text for a 'back' navigational command. Used to return to the previous screen */ +"command-back" = "Fitsanahe aliye"; + +/* Short text for a 'cancel' command. Used to back out of a menu or install process without making changes */ +"command-cancel" = "Tsanahe"; + +/* Short text for a 'done' command. Used to back out of settings menus after changes have been made */ +"command-done" = "Zlawzle"; + +/* Short text for an 'install' command. Used to confirm installation of a package. */ +"command-install" = "Femfe"; + +/* Short text for a 'next' navigational command. Used to advance to the next screen */ +"command-next" = "Dem katafke"; + +/* Short text for an 'OK' command. Used for some error alerts */ +"command-ok" = "Bastuwa"; + +/* Short text for confirmining 'uninstall' commands. */ +"command-uninstall" = "Tiɗase"; + +/* Text for the command to uninstall a keyboard */ +"command-uninstall-keyboard" = "Tiɗase keyboard"; + +/* Confirmation text to display before uninstalling a keyboard */ +"command-uninstall-keyboard-confirm" = "Ká kátáhe tiɗase una gé keyboard?"; + +/* Text for the command to uninstall a lexical model */ +"command-uninstall-lexical-model" = "Tiɗase-tiɗe dictionary"; + +/* Confirmation text to display before uninstalling a lexical model */ +"command-uninstall-lexical-model-confirm" = "Ká kátáhe tiɗase una gé dictionary?"; + +/* Text for error when a keyboard cannot load properly */ +"error-loading-keyboard" = "Ɓáaka a magave load keyboard ŋanna a ɗatsava na"; + +/* Text for error when a lexical model cannot load properly */ +"error-loading-lexical-model" = "Ɓáaka a magave load dictionary ŋanna a ɗatsava na"; + +/* Text for error when an installed file is unexpectedly missing */ +"error-missing-file" = "File na a kátáva ŋanna shavantika."; + +/* Text for error when an installed file is unexpectedly missing */ +"error-missing-file-critical" = "Mandala file ŋanna a kátáva na shavantika. Tazlaŋa gwadana-gwada femhe app ŋanna adaliya."; + +/* Text for errors in data received from search queries */ +"error-query-decoding" = "Server ŋanna ɓáaka a ga slera kalkale aɗaba damuwa technical"; + +/* A descriptive message used when a download fails */ +"error-query-general" = "Kuskure ɗamalahe kalkale an server"; + +/* Text for error when a search query is unexpectedly empty */ +"error-query-no-data" = "Kwalla-kwale server ŋanna maga slera"; + +/* Text for general errors where not much information is known */ +"error-unknown" = "Aŋkwa jare kuskure ba kwal dzama jarane"; + +/* Text for error when updating a resource: cannot download because no source is available */ +"error-update-no-link" = "Ɓáaka duksá aɗaba updates gé naza una gé pakeji"; + +/* Text for error when updating a resource: Keyman does not know how to update the resource */ +"error-update-not-managed" = "A tá magavka update nazahu ɓáaka am kazlaŋaha nijiniya Keyman"; + +/* Text for the command to open the help page of a keyboard, as shown on resource information views */ +"info-command-help-keyboard" = "Mla Keyboard"; + +/* Text for the command to open the help page of a lexical model, as shown on resource information views */ +"info-command-help-lexical-model" = "Mla Dictionary"; + +/* Text label for displaying a keyboard's version, as shown on resource information views */ +"info-label-version-keyboard" = "Keyboard version"; + +/* Text label for displaying a lexical model's version, as shown on resource information views */ +"info-label-version-lexical-model" = "Version Dictionary"; + +/* Label used for the "package info" / readme tab with the package installation prompt on phones. */ +"installer-label-package-info" = "Labare pakeji"; + +/* Label used for the language-selection tab with the package installation prompt on phones. */ +"installer-label-select-languages" = "Ksa Nare (ha)"; + +/* Label used with a package's version, as seen within the package installation prompt. Example: "Version: 14.0.0" */ +"installer-label-version" = "Version: %@"; + +/* Section header for languages supported by a package, as seen within the package installation prompt */ +"installer-section-available-languages" = "Naraha na aŋkwa na"; + +/* Text for the help popup for changing keyboards with the globe key */ +"keyboard-help-change" = "Piyana-piya mátána gé mbeɗa keyboard"; + +/* Text for the command to exit the Keyman keyboard in favor of other keyboards installed on the system */ +"keyboard-menu-exit" = "Hiɗante %@"; + +/* Error installing a Keyman package - could not allocate a location to install it */ +"kmp-error-file-system" = "Kuskure femhe pakeji- kuskure file-system"; + +/* Error installing a Keyman package - could not copy package files */ +"kmp-error-file-copying" = "Kuskure femhe pakeji - a magavka burha files na a kátáva ŋanna"; + +/* Error opening a Keyman package - package is not valid */ +"kmp-error-invalid" = "File pakeji ŋanna shanasha virus."; + +/* Error opening a Keyman package - it does not exist / the specified location is wrong */ +"kmp-error-missing" = "Pakej ŋanna a kátáva na ɓáaka."; + +/* Error installing a Keyman package - expected resource (keyboard or dictionary) is missing */ +"kmp-error-missing-resource" = "Una gé pakeji ɓáaka jiba keyboard ŋanna a kátáva ŋanna mtu dictionary."; + +/* Error opening a Keyman package - cannot parse contents */ +"kmp-error-no-metadata" = "Una gé pakeji ndza kaɓa shagera- diyakande duksáhá ŋanna amikwa."; + +/* Error opening a Keyman package - package's contents are for desktop platforms only */ +"kmp-error-unsupported" = "Una gé pakej a tá magaka slera kalkale at nawuraŋa ŋanna."; + +/* Error opening a Keyman package - package contains unexpected resource (keyboard or dictionary) type */ +"kmp-error-wrong-type" = "Una gé pakeji ɓáaka jiba duksáhá ŋanna a kátáva amikwa."; + +/* Title for the Installed Languages menu */ +"menu-installed-languages-title" = "Naraha na an fa amikwa"; + +/* Section header for lexical models within a language-specific settings menu */ +"menu-langsettings-label-lexical-models" = "Dictionaries"; + +/* Section header for keyboards within a language-specific settings menu */ +"menu-langsettings-section-keyboards" = "Keyboards"; + +/* Section header for the settings toggles within a language-specific settings menu */ +"menu-langsettings-section-settings" = "Saitine Nare"; + +/* Title for the language-specific settings menus */ +"menu-langsettings-title" = "%@ Saitin"; + +/* Label for the toggle that enables corrections that is displayed within a language-specific settings menu */ +"menu-langsettings-toggle-correct" = "Magave kaɓa kuskure"; + +/* Label for the toggle that enables predictions that is displayed within a language-specific settings menu */ +"menu-langsettings-toggle-predict" = "Magave predictions"; + +/* Help message for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */ +"menu-lexical-model-install-message" = "Ká kátáhe femhe una gé dictionary?"; + +/* Title for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */ +"menu-lexical-model-install-title" = "%1$@: %2$@"; + +/* Text for an info alert indicating that no lexical models are available */ +"menu-lexical-model-none-message" = "Ɓáaka dictionaries amikwa"; + +/* Title for the lexical model menu, a submenu of the language-specific settings menus */ +"menu-lexical-model-title" = "%@ Dictionaries"; + +/* Title for the keyboard picker */ +"menu-picker-title" = "Keyboards"; + +/* Primary text for the Settings menu option to report keyboard crashes */ +"menu-settings-error-report" = "Ɓilana-ɓila basha kuskure"; + +/* Secondary text for the Settings menu option to report keyboard crashes */ +"menu-settings-error-report-description" = "Anaba kátá \"shante magaviré\""; + +/* Primary text for the Settings menu local-file package installation option */ +"menu-settings-install-from-file" = "Femhe a sawam File"; + +/* Secondary text for the Settings menu local-file package installation option */ +"menu-settings-install-from-file-description" = "Browse gé .kmp files"; + +/* Primary text for the Settings menu option that displays the iOS system menu options for the app's keyboard */ +"menu-settings-system-keyboard-menu" = "System Keyboard Saitin"; + +/* Label for the "Show Banner" toggle on the main settings screen */ +"menu-settings-show-banner" = "Mara Banner"; + +/* Label for the "Get Started" automatic display toggle seen in the Settings menu */ +"menu-settings-startup-get-started" = "Mara 'Mátá Fantu' am tátá fantuhe"; + +/* Title for the main Settings menu */ +"menu-settings-title" = "Saitine Keyman"; + +/* Secondary text showing current setting for spacebar caption - blank */ +"menu-settings-spacebar-hint-blank" = "Ɓáaka mara caption at spacebar"; + +/* Secondary text showing current setting for spacebar caption - keyboard */ +"menu-settings-spacebar-hint-keyboard" = "Mara zhera keyboard at spacebar"; + +/* Secondary text showing current setting for spacebar caption - language */ +"menu-settings-spacebar-hint-language" = "Mara zhera nare at spacebar"; + +/* Secondary text showing current setting for spacebar caption - language + keyboard */ +"menu-settings-spacebar-hint-languageKeyboard" = "Mara nare antara zhera keyboard at spacebar"; + +/* Label for the "Spacebar Caption" item on the main settings screen */ +"menu-settings-spacebar-text" = "Spacebar Caption"; + +/* Title for the "Spacebar Caption" settings screen */ +"menu-settings-spacebar-title" = "Spacebar Caption"; + +/* Text showing name of spacebar caption - blank */ +"menu-settings-spacebar-item-blank" = "Blank"; + +/* Text showing name of spacebar caption - keyboard */ +"menu-settings-spacebar-item-keyboard" = "Keyboard"; + +/* Text showing name of spacebar caption - language */ +"menu-settings-spacebar-item-language" = "Nare"; + +/* Text showing name of spacebar caption - language + keyboard */ +"menu-settings-spacebar-item-languageKeyboard" = "Nare antara Keyboard"; + +/* Short text for notification: download failure for keyboard */ +"notification-download-failure-keyboard" = "Kwalla-kwale Keyboard tsukwa"; + +/* Short text for notification: download failure for lexical model */ +"notification-download-failure-lexical-model" = "Kwalla-kwale Dictionary tsukwa"; + +/* Short text for notification: download success for keyboard */ +"notification-download-success-keyboard" = "Tsukwafana-tsukwa Keyboard ŋanna ba kalkale"; + +/* Short text for notification: download success for lexical model */ +"notification-download-success-lexical-model" = "Tsukwafana-tsukwa Lexical model ŋanna ba kalkale"; + +/* Short text for notification: downloading a keyboard */ +"notification-downloading-keyboard" = "Tsukwanahe keyboard\U2026"; + +/* Short text for notification: downloading a lexical model */ +"notification-downloading-lexical-model" = "Tsukwanahe dictionary\U2026"; + +/* Short text for notification: an update is available */ +"notification-update-available" = "Update na aŋkwa ŋanna"; + +/* Short text for notification: currently updating */ +"notification-update-processing" = "Updating\U2026"; + +/* Text indicating success at installing new keyboards or dictionaries */ +"success-install" = "An fa ba kalkale."; + +/* A title to use in alerts indicating 'success' at whatever task the user requested */ +"success-title" = "Magavmage"; diff --git a/ios/engine/KMEI/KeymanEngine/mfi.lproj/Localizable.stringsdict b/ios/engine/KMEI/KeymanEngine/mfi.lproj/Localizable.stringsdict new file mode 100644 index 0000000000..7083d1b5c2 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/mfi.lproj/Localizable.stringsdict @@ -0,0 +1,118 @@ + + + + + menu-langsettings-lexical-model-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u dictionary an fa + other + %u dictionaries an fa + + + notification-update-failed + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u update magavka + other + %u updates magavka + + + notification-update-success + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u update magavmage ba shagera + other + %u updates magavmage ba shagera + + + settings-keyboards-installed-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u keyboard an fa + other + %u keyboards an fa + + + settings-languages-installed-count + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + %u nare an fa + other + %u naraha an fa + + + package-default-found-keyboards + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + Shavansha %u keyboard am pakeji: + other + Shavansha %u keyboards am pakeji: + + + package-default-found-lexical-models + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + u + one + Shavansha %u dictionary am pakeji: + other + Shavansha %u dictionaries am pakeji: + + + + diff --git a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj index aa0b4ff25d..692a59994b 100644 --- a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj +++ b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj @@ -203,6 +203,8 @@ 165EB3992097165B00040A69 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; 165EB39B2097181D00040A69 /* KMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMView.swift; sourceTree = ""; }; 16A9229D20325253003CC98E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Keyman/Info.plist; sourceTree = SOURCE_ROOT; }; + 293EA3E027059C6900545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ha; path = ha.lproj/Localizable.strings; sourceTree = ""; }; + 294914742738DF7700400732 /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/Localizable.strings"; sourceTree = ""; }; 37378F4222C9AA610043F22B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainViewController_iPad.xib; sourceTree = ""; }; 37378F4322C9AA630043F22B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainViewController_iPhone.xib; sourceTree = ""; }; 9815725018E4F0930014DF0C /* 702-share.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "702-share.png"; path = "Keyman Icons/702-share.png"; sourceTree = ""; }; @@ -782,6 +784,8 @@ am, "es-419", "zh-Hans", + ha, + "ff-NG", ); mainGroup = 98ABADA7176935E400B62590; productRefGroup = 98ABADB1176935E400B62590 /* Products */; @@ -1081,6 +1085,8 @@ CEBD3458265511B700EB2EA8 /* am */, CEEF81B92673019600EE6A07 /* es-419 */, CEEC468226F2F7BA009A5B7D /* zh-Hans */, + 293EA3E027059C6900545EED /* ha */, + 294914742738DF7700400732 /* ff-NG */, ); name = Localizable.strings; sourceTree = ""; diff --git a/ios/keyman/Keyman/Keyman/bwr-ng.lproj/Localizable.strings b/ios/keyman/Keyman/Keyman/bwr-ng.lproj/Localizable.strings new file mode 100644 index 0000000000..c12aba91db --- /dev/null +++ b/ios/keyman/Keyman/Keyman/bwr-ng.lproj/Localizable.strings @@ -0,0 +1,81 @@ +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-add-title" = "Jaktǝ Bukmak"; + +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-none" = "Bukmakyeri aɗiwa"; + +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-title" = "Bukmakyeri"; + +/* Used to confirm a user's desire to install a package */ +"confirm-install" = "Instal"; + +/* The label for the toggle to stop automatically showing the "Get Started" tutorial popup. */ +"disable-get-started" = "Ga aɗe jaktǝ ncangtǝ kǝra wa"; + +/* Indicates an error opening a web page requested by a user */ +"error-opening-page" = "Aɗa tsoktǝ kǝla peji ni wa"; + +/* Long-form used when installing a font in order to display a language properly. */ +"font-install-description" = "Tsukwa Instal kamnyar katsa %@ ncangtǝ kalkal akwa shanga appyeri nga"; + +/* The name of the iOS Settings menu option for keyboards */ +"ios-settings-keyboards" = "Keyboardyeri"; + +/* The name of the iOS Settings menu option for giving the keyboard full access. (The menu entry +underneath the app's name within the app-specific keyboard menu.) */ +"ios-settings-allow-full-access" = "Hamta shanga laku na aɗi"; + +/* Short-form used when installing a font in order to display a language properly. */ +"language-for-font" = "%@ Font"; + +/* Used for 'add' options within menus */ +"menu-add" = "Jakta"; + +/* Used to indicate a sequence of menu options that a user needs to copy. May be chained. Example: "Keyboards (1) > Enable Keyman (2)" */ +"menu-breadcrumbing" = "%1$@ > %2$@"; + +/* Used to exit a menu without choosing an option */ +"menu-cancel" = "Awo"; + +/* Menu option that erases all previously-typed text. */ +"menu-clear-text" = "Cicimta Rubutu"; + +/* Menu option that displays a list designed to help users start using the app */ +"menu-get-started" = "Baɗita kulini"; + +/* Menu option that displays help for the app */ +"menu-help" = "Labar"; + +/* Menu option that displays the main screen's drop-down menu */ +"menu-more" = "Na damwa"; + +/* Menu option used to edit keyboard and dictionary settings */ +"menu-settings" = "Setiyeri"; + +/* Menu option that displays the iOS Share menu */ +"menu-share" = "Tǝra"; + +/* Menu option that displays an embedded web browser. */ +"menu-show-browser" = "Brauza"; + +/* Menu option used to control in-app font size. */ +"menu-text-size" = "Olkur Rubutu"; + +/* Used to describe the current font size */ +"text-size-label" = "Olkur rubutu: %i"; + +/* Text to indicate that a user should set a toggle within iOS Settings to 'active'. */ +"toggle-to-enable" = "Katsa hara %@"; + +/* First option on the Get Started tutorial - Add a keyboard for your language */ +"tutorial-add-keyboard" = "Jaktǝ keyboard na ar hulfur mnya nga"; + +/* Third option on the Get Started tutorial - Displays app help. */ +"tutorial-show-help" = "Labar na damwa"; + +/* Second option on the Get Started tutorial - Set up Keyman as system-wide keyboard */ +"tutorial-system-keyboard" = "Hara seti ar Keyman apa keyboard ar olkur na ar sistem ni"; + +/* Used to display app version (as in \"Version: 1.0.2\" */ +"version-label" = "Jili: %@"; diff --git a/ios/keyman/Keyman/Keyman/ff-ng.lproj/Localizable.strings b/ios/keyman/Keyman/Keyman/ff-ng.lproj/Localizable.strings new file mode 100644 index 0000000000..53f6cc7652 --- /dev/null +++ b/ios/keyman/Keyman/Keyman/ff-ng.lproj/Localizable.strings @@ -0,0 +1,81 @@ +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-add-title" = "A` waata Bukmak"; + +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-none" = "Walaa bukmak"; + +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-title" = "Bukmaks"; + +/* Used to confirm a user's desire to install a package */ +"confirm-install" = "Wa`unu"; + +/* The label for the toggle to stop automatically showing the "Get Started" tutorial popup. */ +"disable-get-started" = "Taa holla fahin"; + +/* Indicates an error opening a web page requested by a user */ +"error-opening-page" = "Maɓɓititta pej man"; + +/* Long-form used when installing a font in order to display a language properly. */ +"font-install-description" = "Meemu Instal ngam a` waɗa %@ holla haa app ma"; + +/* The name of the iOS Settings menu option for keyboards */ +"ios-settings-keyboards" = "Kibod"; + +/* The name of the iOS Settings menu option for giving the keyboard full access. (The menu entry +underneath the app's name within the app-specific keyboard menu.) */ +"ios-settings-allow-full-access" = "Accu laawol nastugo fuu"; + +/* Short-form used when installing a font in order to display a language properly. */ +"language-for-font" = "%@ Maunugo bindi"; + +/* Used for 'add' options within menus */ +"menu-add" = "Ɓeddu"; + +/* Used to indicate a sequence of menu options that a user needs to copy. May be chained. Example: "Keyboards (1) > Enable Keyman (2)" */ +"menu-breadcrumbing" = "%1$@ > %2$@"; + +/* Used to exit a menu without choosing an option */ +"menu-cancel" = "Wurtu"; + +/* Menu option that erases all previously-typed text. */ +"menu-clear-text" = "A` wogga ɓaleeri"; + +/* Menu option that displays a list designed to help users start using the app */ +"menu-get-started" = "A` fuɗɗa kesum"; + +/* Menu option that displays help for the app */ +"menu-help" = "Bayanu"; + +/* Menu option that displays the main screen's drop-down menu */ +"menu-more" = "Ɓeddaari"; + +/* Menu option used to edit keyboard and dictionary settings */ +"menu-settings" = "Settinji"; + +/* Menu option that displays the iOS Share menu */ +"menu-share" = "Sendu"; + +/* Menu option that displays an embedded web browser. */ +"menu-show-browser" = "Lenguru"; + +/* Menu option used to control in-app font size. */ +"menu-text-size" = "Mawnugo ɓaleeri"; + +/* Used to describe the current font size */ +"text-size-label" = "Mawnugo ɓaleeri: %i"; + +/* Text to indicate that a user should set a toggle within iOS Settings to 'active'. */ +"toggle-to-enable" = "Accu %@"; + +/* First option on the Get Started tutorial - Add a keyboard for your language */ +"tutorial-add-keyboard" = "A` waata kibod jee ɗemngal ma"; + +/* Third option on the Get Started tutorial - Displays app help. */ +"tutorial-show-help" = "Ɓeddaari bayanu"; + +/* Second option on the Get Started tutorial - Set up Keyman as system-wide keyboard */ +"tutorial-system-keyboard" = "A` waɗa Keyman laata kibod na`ura"; + +/* Used to display app version (as in \"Version: 1.0.2\" */ +"version-label" = "Nonde: %@"; diff --git a/ios/keyman/Keyman/Keyman/ha.lproj/Localizable.strings b/ios/keyman/Keyman/Keyman/ha.lproj/Localizable.strings new file mode 100644 index 0000000000..521b1cd8a6 --- /dev/null +++ b/ios/keyman/Keyman/Keyman/ha.lproj/Localizable.strings @@ -0,0 +1,81 @@ +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-add-title" = "Sanya Alama"; + +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-none" = "Babu alama"; + +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-title" = "Alamomi"; + +/* Used to confirm a user's desire to install a package */ +"confirm-install" = "Shigar"; + +/* The label for the toggle to stop automatically showing the "Get Started" tutorial popup. */ +"disable-get-started" = "Kar a sake nunawa"; + +/* Indicates an error opening a web page requested by a user */ +"error-opening-page" = "Kasa Buɗe Shafi"; + +/* Long-form used when installing a font in order to display a language properly. */ +"font-install-description" = "Taɓa Shigarwa domin yin %@ nuni daidai a cikin dukkanin apps ɗin ku"; + +/* The name of the iOS Settings menu option for keyboards */ +"ios-settings-keyboards" = "Madannai"; + +/* The name of the iOS Settings menu option for giving the keyboard full access. (The menu entry +underneath the app's name within the app-specific keyboard menu.) */ +"ios-settings-allow-full-access" = "Bayar da Samun Cikakkiyar Dama"; + +/* Short-form used when installing a font in order to display a language properly. */ +"language-for-font" = "%@ Font"; + +/* Used for 'add' options within menus */ +"menu-add" = "Ƙara"; + +/* Used to indicate a sequence of menu options that a user needs to copy. May be chained. Example: "Keyboards (1) > Enable Keyman (2)" */ +"menu-breadcrumbing" = "%1$@ > %2$@"; + +/* Used to exit a menu without choosing an option */ +"menu-cancel" = "Sokewa"; + +/* Menu option that erases all previously-typed text. */ +"menu-clear-text" = "Goge Rubutu"; + +/* Menu option that displays a list designed to help users start using the app */ +"menu-get-started" = "Farawa"; + +/* Menu option that displays help for the app */ +"menu-help" = "Bayani"; + +/* Menu option that displays the main screen's drop-down menu */ +"menu-more" = "Ƙari"; + +/* Menu option used to edit keyboard and dictionary settings */ +"menu-settings" = "Saiti"; + +/* Menu option that displays the iOS Share menu */ +"menu-share" = "Raba"; + +/* Menu option that displays an embedded web browser. */ +"menu-show-browser" = "Abin bincike"; + +/* Menu option used to control in-app font size. */ +"menu-text-size" = "Girman Rubutu"; + +/* Used to describe the current font size */ +"text-size-label" = "Girman rubutu: %i"; + +/* Text to indicate that a user should set a toggle within iOS Settings to 'active'. */ +"toggle-to-enable" = "Bada dama %@"; + +/* First option on the Get Started tutorial - Add a keyboard for your language */ +"tutorial-add-keyboard" = "Sanya madannin yarenka"; + +/* Third option on the Get Started tutorial - Displays app help. */ +"tutorial-show-help" = "Ƙarin bayani"; + +/* Second option on the Get Started tutorial - Set up Keyman as system-wide keyboard */ +"tutorial-system-keyboard" = "Saita Keyman a matsayin madannan system-wide"; + +/* Used to display app version (as in \"Version: 1.0.2\" */ +"version-label" = "Nau'i: %@"; diff --git a/ios/keyman/Keyman/Keyman/mfi.lproj/Localizable.strings b/ios/keyman/Keyman/Keyman/mfi.lproj/Localizable.strings new file mode 100644 index 0000000000..68288fc999 --- /dev/null +++ b/ios/keyman/Keyman/Keyman/mfi.lproj/Localizable.strings @@ -0,0 +1,81 @@ +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-add-title" = "Farfe Bookmark"; + +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-none" = "Ɓáaka bookmarks"; + +/* Title for the bookmarks menu within the embedded browser */ +"browser-bookmarks-title" = "Bookmarks"; + +/* Used to confirm a user's desire to install a package */ +"confirm-install" = "Femhe"; + +/* The label for the toggle to stop automatically showing the "Get Started" tutorial popup. */ +"disable-get-started" = "Maraka adaliya"; + +/* Indicates an error opening a web page requested by a user */ +"error-opening-page" = "A tá wurantika paji ŋanna"; + +/* Long-form used when installing a font in order to display a language properly. */ +"font-install-description" = "Gejan-geja gé femhe %@ naza mara kalkale ne am baɗumma apps ŋanna"; + +/* The name of the iOS Settings menu option for keyboards */ +"ios-settings-keyboards" = "Keyboards"; + +/* The name of the iOS Settings menu option for giving the keyboard full access. (The menu entry +underneath the app's name within the app-specific keyboard menu.) */ +"ios-settings-allow-full-access" = "Ɓilana-ɓila sha uŋŋule"; + +/* Short-form used when installing a font in order to display a language properly. */ +"language-for-font" = "%@ Font"; + +/* Used for 'add' options within menus */ +"menu-add" = "Fare"; + +/* Used to indicate a sequence of menu options that a user needs to copy. May be chained. Example: "Keyboards (1) > Enable Keyman (2)" */ +"menu-breadcrumbing" = "%1$@ > %2$@"; + +/* Used to exit a menu without choosing an option */ +"menu-cancel" = "Tsanahe"; + +/* Menu option that erases all previously-typed text. */ +"menu-clear-text" = "Gaggahe Text"; + +/* Menu option that displays a list designed to help users start using the app */ +"menu-get-started" = "Fantuhe"; + +/* Menu option that displays help for the app */ +"menu-help" = "Labare"; + +/* Menu option that displays the main screen's drop-down menu */ +"menu-more" = "Farfe adaliya"; + +/* Menu option used to edit keyboard and dictionary settings */ +"menu-settings" = "Saitine"; + +/* Menu option that displays the iOS Share menu */ +"menu-share" = "Digahe"; + +/* Menu option that displays an embedded web browser. */ +"menu-show-browser" = "Browser"; + +/* Menu option used to control in-app font size. */ +"menu-text-size" = "Ƴaikire Text"; + +/* Used to describe the current font size */ +"text-size-label" = "Ƴaikire Text: %i"; + +/* Text to indicate that a user should set a toggle within iOS Settings to 'active'. */ +"toggle-to-enable" = "Magave %@"; + +/* First option on the Get Started tutorial - Add a keyboard for your language */ +"tutorial-add-keyboard" = "Farfe keyboard gé naza nare ŋa"; + +/* Third option on the Get Started tutorial - Displays app help. */ +"tutorial-show-help" = "Farfe labare adaliya"; + +/* Second option on the Get Started tutorial - Set up Keyman as system-wide keyboard */ +"tutorial-system-keyboard" = "Magahe saitine Keyman gé system-wide keyboard"; + +/* Used to display app version (as in \"Version: 1.0.2\" */ +"version-label" = "Version: %@"; diff --git a/lerna.json b/lerna.json index 2636b29da0..2d760e68e3 100644 --- a/lerna.json +++ b/lerna.json @@ -20,5 +20,5 @@ "ts-node", "typescript" ], - "version": "15.0.129" + "version": "15.0.157" } diff --git a/linux/Makefile b/linux/Makefile index ec05a40b8d..b0f3fafb39 100644 --- a/linux/Makefile +++ b/linux/Makefile @@ -51,3 +51,13 @@ releasedeb: reconf origdist cow debnocow # it will prompt for sudo if required f nightlydist: reconf sources echo "Built nightly source release in dist/" + +check: + cd keyman-config && ./run-tests.sh + cd ibus-keyman && make check + +tmpcheck: + cd keyman-config && ./run-tests.sh + cd build-ibus-keyman && make check + +tests: check diff --git a/linux/build/agent/dependencies.config b/linux/build/agent/dependencies.config index 1a6909f30b..df1e1e9b0f 100644 --- a/linux/build/agent/dependencies.config +++ b/linux/build/agent/dependencies.config @@ -20,10 +20,4 @@ # any=@precise.any [common] -any=git autotools-dev build-essential dh-autoreconf libibus-1.0-dev flex bison meson python3 python3-pip cargo - -[xenial] -any=libx11-dev - -[bionic] -any=@xenial.any +any=git autotools-dev build-essential dh-autoreconf libibus-1.0-dev flex bison meson python3 python3-pip cargo xserver-xephyr xvfb metacity libx11-dev gawk diff --git a/linux/debian/control b/linux/debian/control index b729cabe15..d85071e60d 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -33,6 +33,10 @@ Build-Depends: libibus-1.0-dev (>= 1.2), libjson-glib-dev (>= 1.4.0), pkg-config, + xserver-xephyr, + xvfb, + metacity, + gawk, Standards-Version: 4.5.1 Vcs-Git: https://github.com/keymanapp/keyman.git Vcs-Browser: https://github.com/keymanapp/keyman diff --git a/linux/debian/not-installed b/linux/debian/not-installed new file mode 100644 index 0000000000..7320a6e010 --- /dev/null +++ b/linux/debian/not-installed @@ -0,0 +1,2 @@ +usr/libexec/ibus-keyman-tests +usr/libexec/keymanutil-tests diff --git a/linux/ibus-keyman/.gitignore b/linux/ibus-keyman/.gitignore index 4fd1a0b57b..0927e6a92a 100644 --- a/linux/ibus-keyman/.gitignore +++ b/linux/ibus-keyman/.gitignore @@ -1,8 +1,11 @@ po/POTFILES src/keyman.xml src/keyman.xml.in +src/keyman-version.h src/ibus-engine-keyman src/print_kmp src/print_kmpdetails +src/test/keymanutil-tests *.log *.trs +.dirstamp diff --git a/linux/ibus-keyman/Makefile.am b/linux/ibus-keyman/Makefile.am index aee7e75b2f..d74946bed2 100644 --- a/linux/ibus-keyman/Makefile.am +++ b/linux/ibus-keyman/Makefile.am @@ -19,23 +19,26 @@ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. SUBDIRS = \ - src \ - icons \ - m4 \ - po \ - $(NULL) + src \ + tests \ + icons \ + m4 \ + po \ + $(NULL) ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = \ - autogen.sh \ - VERSION \ - $(NULL) + autogen.sh \ + VERSION \ + tap-driver.sh \ + tap-test \ + $(NULL) noinst_DIST = \ - $(NULL) + $(NULL) DISTCLEANFILES = \ - po/stamp-it \ - $(NULL) + po/stamp-it \ + $(NULL) diff --git a/linux/ibus-keyman/configure.ac b/linux/ibus-keyman/configure.ac index 9556a87146..97ab5ddc90 100644 --- a/linux/ibus-keyman/configure.ac +++ b/linux/ibus-keyman/configure.ac @@ -99,6 +99,9 @@ AC_CONFIG_FILES([ po/Makefile.in Makefile src/Makefile src/keyman.xml.in +src/keyman-version.h +src/test/Makefile +tests/Makefile icons/Makefile m4/Makefile ]) diff --git a/linux/ibus-keyman/glib-tap.mk b/linux/ibus-keyman/glib-tap.mk new file mode 100644 index 0000000000..686d94f1e5 --- /dev/null +++ b/linux/ibus-keyman/glib-tap.mk @@ -0,0 +1,137 @@ +# from https://gitlab.gnome.org/GNOME/glib/-/blob/glib-2-58/glib-tap.mk +# GLIB - Library of useful C routines + +AM_TESTS_ENVIRONMENT= \ + G_TEST_SRCDIR="$(abs_srcdir)" \ + G_TEST_BUILDDIR="$(abs_builddir)" \ + G_DEBUG=gc-friendly \ + MALLOC_CHECK_=2 \ + MALLOC_PERTURB_=$$(($${RANDOM:-256} % 256)) +LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh +LOG_COMPILER = $(top_srcdir)/tap-test + +NULL = + +# initialize variables for unconditional += appending +BUILT_SOURCES = +BUILT_EXTRA_DIST = +CLEANFILES = *.log *.trs +DISTCLEANFILES = +MAINTAINERCLEANFILES = +EXTRA_DIST = +TESTS = + +# installed_test_LTLIBRARIES = +# installed_test_PROGRAMS = +# installed_test_SCRIPTS = +# nobase_installed_test_DATA = + +noinst_LTLIBRARIES = +noinst_PROGRAMS = +noinst_SCRIPTS = +noinst_DATA = + +check_LTLIBRARIES = +check_PROGRAMS = +check_SCRIPTS = +check_DATA = + +# We support a fairly large range of possible variables. It is expected that all types of files in a test suite +# will belong in exactly one of the following variables. +# +# First, we support the usual automake suffixes, but in lowercase, with the customary meaning: +# +# test_programs, test_scripts, test_data, test_ltlibraries +# +# The above are used to list files that are involved in both uninstalled and installed testing. The +# test_programs and test_scripts are taken to be actual testcases and will be run as part of the test suite. +# Note that _data is always used with the nobase_ automake variable name to ensure that installed test data is +# installed in the same way as it appears in the package layout. +# +# In order to mark a particular file as being only for one type of testing, use 'installed' or 'uninstalled', +# like so: +# +# installed_test_programs, uninstalled_test_programs +# installed_test_scripts, uninstalled_test_scripts +# installed_test_data, uninstalled_test_data +# installed_test_ltlibraries, uninstalled_test_ltlibraries +# +# Additionally, we support 'extra' infixes for programs and scripts. This is used for support programs/scripts +# that should not themselves be run as testcases (but exist to be used from other testcases): +# +# test_extra_programs, installed_test_extra_programs, uninstalled_test_extra_programs +# test_extra_scripts, installed_test_extra_scripts, uninstalled_test_extra_scripts +# +# Additionally, for _scripts and _data, we support the customary dist_ prefix so that the named script or data +# file automatically end up in the tarball. +# +# dist_test_scripts, dist_test_data, dist_test_extra_scripts +# dist_installed_test_scripts, dist_installed_test_data, dist_installed_test_extra_scripts +# dist_uninstalled_test_scripts, dist_uninstalled_test_data, dist_uninstalled_test_extra_scripts +# +# Note that no file is automatically disted unless it appears in one of the dist_ variables. This follows the +# standard automake convention of not disting programs scripts or data by default. +# +# test_programs, test_scripts, uninstalled_test_programs and uninstalled_test_scripts (as well as their disted +# variants) will be run as part of the in-tree 'make check'. These are all assumed to be runnable under +# gtester. That's a bit strange for scripts, but it's possible. + +TESTS += $(test_programs) $(test_scripts) $(uninstalled_test_programs) $(uninstalled_test_scripts) \ + $(dist_test_scripts) $(dist_uninstalled_test_scripts) + +# Note: build even the installed-only targets during 'make check' to ensure that they still work. +# We need to do a bit of trickery here and manage disting via EXTRA_DIST instead of using dist_ prefixes to +# prevent automake from mistreating gmake functions like $(wildcard ...) and $(addprefix ...) as if they were +# filenames, including removing duplicate instances of the opening part before the space, eg. '$(addprefix'. +all_test_programs = $(test_programs) $(uninstalled_test_programs) $(installed_test_programs) \ + $(test_extra_programs) $(uninstalled_test_extra_programs) $(installed_test_extra_programs) +all_test_scripts = $(test_scripts) $(uninstalled_test_scripts) $(installed_test_scripts) \ + $(test_extra_scripts) $(uninstalled_test_extra_scripts) $(installed_test_extra_scripts) +all_dist_test_scripts = $(dist_test_scripts) $(dist_uninstalled_test_scripts) $(dist_installed_test_scripts) \ + $(dist_test_extra_scripts) $(dist_uninstalled_test_extra_scripts) $(dist_installed_test_extra_scripts) +all_test_scripts += $(all_dist_test_scripts) +EXTRA_DIST += $(all_dist_test_scripts) +all_test_data = $(test_data) $(uninstalled_test_data) $(installed_test_data) +all_dist_test_data = $(dist_test_data) $(dist_uninstalled_test_data) $(dist_installed_test_data) +all_test_data += $(all_dist_test_data) +EXTRA_DIST += $(all_dist_test_data) +all_test_ltlibs = $(test_ltlibraries) $(uninstalled_test_ltlibraries) $(installed_test_ltlibraries) + +# if ENABLE_ALWAYS_BUILD_TESTS +# noinst_LTLIBRARIES += $(all_test_ltlibs) +# noinst_PROGRAMS += $(all_test_programs) +# noinst_SCRIPTS += $(all_test_scripts) +# noinst_DATA += $(all_test_data) +# else +check_LTLIBRARIES += $(all_test_ltlibs) +check_PROGRAMS += $(all_test_programs) +check_SCRIPTS += $(all_test_scripts) +check_DATA += $(all_test_data) +# endif + +# if ENABLE_INSTALLED_TESTS +# installed_test_PROGRAMS += $(test_programs) $(installed_test_programs) \ +# $(test_extra_programs) $(installed_test_extra_programs) +# installed_test_SCRIPTS += $(test_scripts) $(installed_test_scripts) \ +# $(test_extra_scripts) $(installed_test_extra_scripts) +# installed_test_SCRIPTS += $(dist_test_scripts) $(dist_test_extra_scripts) \ +# $(dist_installed_test_scripts) $(dist_installed_test_extra_scripts) +# nobase_installed_test_DATA += $(test_data) $(installed_test_data) +# nobase_installed_test_DATA += $(dist_test_data) $(dist_installed_test_data) +# installed_test_LTLIBRARIES += $(test_ltlibraries) $(installed_test_ltlibraries) +# installed_testcases = $(test_programs) $(installed_test_programs) \ +# $(test_scripts) $(installed_test_scripts) \ +# $(dist_test_scripts) $(dist_installed_test_scripts) + +# installed_test_meta_DATA = $(installed_testcases:=.test) + +# %.test: %$(EXEEXT) Makefile +# $(AM_V_GEN) ($(MKDIR_P) $(@D); \ +# echo '[Test]' > $@.tmp; \ +# echo 'Type=session' >> $@.tmp; \ +# echo 'Exec=$(installed_testdir)/$(notdir $<) --tap' >> $@.tmp; \ +# echo 'Output=TAP' >> $@.tmp; \ +# mv $@.tmp $@) + +# CLEANFILES += $(installed_test_meta_DATA) +# endif diff --git a/linux/ibus-keyman/src/Makefile.am b/linux/ibus-keyman/src/Makefile.am index 476f1d4000..95361020c5 100644 --- a/linux/ibus-keyman/src/Makefile.am +++ b/linux/ibus-keyman/src/Makefile.am @@ -18,6 +18,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +SUBDIRS = \ + test \ + $(NULL) + AM_CFLAGS = \ @IBUS_CFLAGS@ \ @GTK_CFLAGS@ \ @@ -68,8 +72,10 @@ ibus_engine_keyman_SOURCES = \ main.c \ engine.c \ engine.h \ + keycodes.h \ keyman-service.c \ keyman-service.h \ + keyman-version.h \ $(NULL) ibus_engine_keyman_CFLAGS = \ diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index 0d895543e4..72c7a06ea0 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -38,6 +38,7 @@ #include "keymanutil.h" #include "keyman-service.h" #include "engine.h" +#include "keycodes.h" #define MAXCONTEXT_ITEMS 128 #define KEYMAN_BACKSPACE 14 @@ -161,13 +162,13 @@ static void ibus_keyman_engine_class_init (IBusKeymanEngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (klass); - IBusEngineClass *engine_class = IBUS_ENGINE_CLASS (klass); + IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (klass); + IBusEngineClass *engine_class = IBUS_ENGINE_CLASS (klass); - parent_class = (IBusEngineClass *) g_type_class_peek_parent (klass); + parent_class = (IBusEngineClass *) g_type_class_peek_parent (klass); object_class->constructor = ibus_keyman_engine_constructor; - ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_keyman_engine_destroy; + ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_keyman_engine_destroy; engine_class->process_key_event = ibus_keyman_engine_process_key_event; @@ -276,9 +277,8 @@ ibus_keyman_engine_constructor( ) { IBusKeymanEngine *keyman; IBusEngine *engine; - IBusText *text; const gchar *engine_name; - gchar *surrounding_text, *p, *abs_kmx_path; + gchar *p, *abs_kmx_path; guint cursor_pos, anchor_pos; km_kbp_context_item *context_items; @@ -487,115 +487,6 @@ static void forward_backspace(IBusKeymanEngine *keyman, unsigned int state) ibus_engine_forward_key_event((IBusEngine *)keyman, KEYMAN_BACKSPACE_KEYSYM, KEYMAN_BACKSPACE, state); } -// from android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMScanCodeMap.java -// uses kernel keycodes which are (X11 keycode - 8) -// see /usr/include/linux/input-event-codes.h -static km_kbp_virtual_key const keycode_to_vk[256] = { - 0, // KEY_RESERVED = 0x00; - KM_KBP_VKEY_ESC, // KEY_ESC = 0x01; - KM_KBP_VKEY_1, // KEY_1 = 0x02; - KM_KBP_VKEY_2, // KEY_2 = 0x03; - KM_KBP_VKEY_3, // KEY_3 = 0x04; - KM_KBP_VKEY_4, // KEY_4 = 0x05; - KM_KBP_VKEY_5, // KEY_5 = 0x06; - KM_KBP_VKEY_6, // KEY_6 = 0x07; - KM_KBP_VKEY_7, // KEY_7 = 0x08; - KM_KBP_VKEY_8, // KEY_8 = 0x09; - KM_KBP_VKEY_9, // KEY_9 = 0x0A; - KM_KBP_VKEY_0, // KEY_0 = 0x0B; - KM_KBP_VKEY_HYPHEN, // KEY_MINUS = 0x0C; - KM_KBP_VKEY_EQUAL, // KEY_EQUALS = 0x0D; - KM_KBP_VKEY_BKSP, // KEY_BACKSPACE = 0x0E; - KM_KBP_VKEY_TAB, // KEY_TAB = 0x0F; - KM_KBP_VKEY_Q, // KEY_Q = 0x10; - KM_KBP_VKEY_W, // KEY_W = 0x11; - KM_KBP_VKEY_E, // KEY_E = 0x12; - KM_KBP_VKEY_R, // KEY_R = 0x13; - KM_KBP_VKEY_T, // KEY_T = 0x14; - KM_KBP_VKEY_Y, // KEY_Y = 0x15; - KM_KBP_VKEY_U, // KEY_U = 0x16; - KM_KBP_VKEY_I, // KEY_I = 0x17; - KM_KBP_VKEY_O, // KEY_O = 0x18; - KM_KBP_VKEY_P, // KEY_P = 0x19; - KM_KBP_VKEY_LBRKT, // KEY_LEFTBRACE = 0x1A; - KM_KBP_VKEY_RBRKT, // KEY_RIGHTBRACE = 0x1B; - KM_KBP_VKEY_ENTER, // KEY_ENTER = 0x1C; - KM_KBP_VKEY_CONTROL, // KEY_LEFTCTRL = 0x1D; - KM_KBP_VKEY_A, // KEY_A = 0x1E; - KM_KBP_VKEY_S, // KEY_S = 0x1F; - KM_KBP_VKEY_D, // KEY_D = 0x20; - KM_KBP_VKEY_F, // KEY_F = 0x21; - KM_KBP_VKEY_G, // KEY_G = 0x22; - KM_KBP_VKEY_H, // KEY_H = 0x23; - KM_KBP_VKEY_J, // KEY_J = 0x24; - KM_KBP_VKEY_K, // KEY_K = 0x25; - KM_KBP_VKEY_L, // KEY_L = 0x26; - KM_KBP_VKEY_COLON, // KEY_SEMICOLON = 0x27; - KM_KBP_VKEY_QUOTE, // KEY_APOSTROPHE = 0x28; - KM_KBP_VKEY_BKQUOTE, // KEY_GRAVE = 0x29; - KM_KBP_VKEY_SHIFT, // KEY_LEFTSHIFT = 0x2A; - KM_KBP_VKEY_BKSLASH, // KEY_BACKSLASH = 0x2B; - KM_KBP_VKEY_Z, // KEY_Z = 0x2C; - KM_KBP_VKEY_X, // KEY_X = 0x2D; - KM_KBP_VKEY_C, // KEY_C = 0x2E; - KM_KBP_VKEY_V, // KEY_V = 0x2F; - KM_KBP_VKEY_B, // KEY_B = 0x30; - KM_KBP_VKEY_N, // KEY_N = 0x31; - KM_KBP_VKEY_M, // KEY_M = 0x32; - KM_KBP_VKEY_COMMA, // KEY_COMMA = 0x33; - KM_KBP_VKEY_PERIOD, // KEY_DOT = 0x34; - KM_KBP_VKEY_SLASH, // KEY_SLASH = 0x35; - KM_KBP_VKEY_SHIFT, // KEY_RIGHTSHIFT = 0x36; - KM_KBP_VKEY_NPSTAR, // KEY_KPASTERISK = 0x37; - KM_KBP_VKEY_ALT, // KEY_LEFTALT = 0x38; - KM_KBP_VKEY_SPACE, // KEY_SPACE = 0x39; - KM_KBP_VKEY_CAPS, // KEY_CAPSLOCK = 0x3A; - KM_KBP_VKEY_F1, // KEY_F1 = 0x3B; - KM_KBP_VKEY_F2, // KEY_F2 = 0x3C; - KM_KBP_VKEY_F3, // KEY_F3 = 0x3D; - KM_KBP_VKEY_F4, // KEY_F4 = 0x3E; - KM_KBP_VKEY_F5, // KEY_F5 = 0x3F; - KM_KBP_VKEY_F6, // KEY_F6 = 0x40; - KM_KBP_VKEY_F7, // KEY_F7 = 0x41; - KM_KBP_VKEY_F8, // KEY_F8 = 0x42; - KM_KBP_VKEY_F9, // KEY_F9 = 0x43; - KM_KBP_VKEY_F10, // KEY_F10 = 0x44; - 0, // KEY_NUMLOCK = 0x45; - 0, // KEY_SCROLLLOCK = 0x46; - KM_KBP_VKEY_NP7, // KEY_KP7 = 0x47; - KM_KBP_VKEY_NP8, // KEY_KP8 = 0x48; - KM_KBP_VKEY_NP9, // KEY_KP9 = 0x49; - KM_KBP_VKEY_NPMINUS, // KEY_KPMINUS = 0x4A; - KM_KBP_VKEY_NP4, // KEY_KP4 = 0x4B; - KM_KBP_VKEY_NP5, // KEY_KP5 = 0x4C; - KM_KBP_VKEY_NP6, // KEY_KP6 = 0x4D; - KM_KBP_VKEY_NPPLUS, // KEY_KPPLUS = 0x4E; - KM_KBP_VKEY_NP1, // KEY_KP1 = 0x4F; - KM_KBP_VKEY_NP2, // KEY_KP2 = 0x50; - KM_KBP_VKEY_NP3, // KEY_KP3 = 0x51; - KM_KBP_VKEY_NP0, // KEY_KP0 = 0x52; - KM_KBP_VKEY_NPDOT, // KEY_KPDOT = 0x53; - 0, // padding 0x54; - 0, // KEY_ZENKAKUHANKAKU = 0x55; - KM_KBP_VKEY_oE2, // KEY_102ND = 0x56; - KM_KBP_VKEY_F11, // KEY_F11 = 0x57; - KM_KBP_VKEY_F12, // KEY_F12 = 0x58; - 0, // KEY_RO = 0x59; - 0, // KEY_KATAKANA = 0x5a; - 0, // KEY_HIRAGANA = 0x5b; - 0, // KEY_HENKAN = 0x5c; - 0, // KEY_KATAKANAHIRAGANA = 0x5d; - 0, // KEY_MUHENKAN = 0x5e; - 0, // KEY_KPJPCOMMA = 0x5f; - 0, // KEY_KPENTER = 0x60; - KM_KBP_VKEY_CONTROL, // KEY_RIGHTCTRL = 0x61; - 0, // KEY_KPSLASH = 0x62; - 0, // KEY_SYSRQ = 0x63; - KM_KBP_VKEY_ALT // KEY_RIGHTALT = 0x64; - - // Many more KEYS currently not used by KMW... -}; - static gboolean ok_for_single_backspace(const km_kbp_action_item *action_items, int i, size_t num_actions) { for (int j=i+1; j < num_actions; j++) { @@ -695,8 +586,7 @@ process_backspace_action( return FALSE; } else { g_message( - "DAR: ibus_keyman_engine_process_key_event - client_capabilities=%x, %x", engine->client_capabilities, - IBUS_CAP_SURROUNDING_TEXT); + "DAR: process_backspace_action - client_capabilities=%x, %x", engine->client_capabilities, IBUS_CAP_SURROUNDING_TEXT); if ((engine->client_capabilities & IBUS_CAP_SURROUNDING_TEXT) != 0) { g_message("deleting surrounding text 1 char"); diff --git a/linux/ibus-keyman/src/keycodes.h b/linux/ibus-keyman/src/keycodes.h new file mode 100644 index 0000000000..2735da48cb --- /dev/null +++ b/linux/ibus-keyman/src/keycodes.h @@ -0,0 +1,115 @@ +#ifndef __KEYCODES_H__ +#define __KEYCODES_H__ + +#include + +// from android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMScanCodeMap.java +// uses kernel keycodes which are (X11 keycode - 8) +// see /usr/include/linux/input-event-codes.h +static km_kbp_virtual_key const keycode_to_vk[256] = { + 0, // KEY_RESERVED = 0x00; + KM_KBP_VKEY_ESC, // KEY_ESC = 0x01; + KM_KBP_VKEY_1, // KEY_1 = 0x02; + KM_KBP_VKEY_2, // KEY_2 = 0x03; + KM_KBP_VKEY_3, // KEY_3 = 0x04; + KM_KBP_VKEY_4, // KEY_4 = 0x05; + KM_KBP_VKEY_5, // KEY_5 = 0x06; + KM_KBP_VKEY_6, // KEY_6 = 0x07; + KM_KBP_VKEY_7, // KEY_7 = 0x08; + KM_KBP_VKEY_8, // KEY_8 = 0x09; + KM_KBP_VKEY_9, // KEY_9 = 0x0A; + KM_KBP_VKEY_0, // KEY_0 = 0x0B; + KM_KBP_VKEY_HYPHEN, // KEY_MINUS = 0x0C; + KM_KBP_VKEY_EQUAL, // KEY_EQUALS = 0x0D; + KM_KBP_VKEY_BKSP, // KEY_BACKSPACE = 0x0E; + KM_KBP_VKEY_TAB, // KEY_TAB = 0x0F; + KM_KBP_VKEY_Q, // KEY_Q = 0x10; + KM_KBP_VKEY_W, // KEY_W = 0x11; + KM_KBP_VKEY_E, // KEY_E = 0x12; + KM_KBP_VKEY_R, // KEY_R = 0x13; + KM_KBP_VKEY_T, // KEY_T = 0x14; + KM_KBP_VKEY_Y, // KEY_Y = 0x15; + KM_KBP_VKEY_U, // KEY_U = 0x16; + KM_KBP_VKEY_I, // KEY_I = 0x17; + KM_KBP_VKEY_O, // KEY_O = 0x18; + KM_KBP_VKEY_P, // KEY_P = 0x19; + KM_KBP_VKEY_LBRKT, // KEY_LEFTBRACE = 0x1A; + KM_KBP_VKEY_RBRKT, // KEY_RIGHTBRACE = 0x1B; + KM_KBP_VKEY_ENTER, // KEY_ENTER = 0x1C; + KM_KBP_VKEY_CONTROL, // KEY_LEFTCTRL = 0x1D; + KM_KBP_VKEY_A, // KEY_A = 0x1E; + KM_KBP_VKEY_S, // KEY_S = 0x1F; + KM_KBP_VKEY_D, // KEY_D = 0x20; + KM_KBP_VKEY_F, // KEY_F = 0x21; + KM_KBP_VKEY_G, // KEY_G = 0x22; + KM_KBP_VKEY_H, // KEY_H = 0x23; + KM_KBP_VKEY_J, // KEY_J = 0x24; + KM_KBP_VKEY_K, // KEY_K = 0x25; + KM_KBP_VKEY_L, // KEY_L = 0x26; + KM_KBP_VKEY_COLON, // KEY_SEMICOLON = 0x27; + KM_KBP_VKEY_QUOTE, // KEY_APOSTROPHE = 0x28; + KM_KBP_VKEY_BKQUOTE, // KEY_GRAVE = 0x29; + KM_KBP_VKEY_SHIFT, // KEY_LEFTSHIFT = 0x2A; + KM_KBP_VKEY_BKSLASH, // KEY_BACKSLASH = 0x2B; + KM_KBP_VKEY_Z, // KEY_Z = 0x2C; + KM_KBP_VKEY_X, // KEY_X = 0x2D; + KM_KBP_VKEY_C, // KEY_C = 0x2E; + KM_KBP_VKEY_V, // KEY_V = 0x2F; + KM_KBP_VKEY_B, // KEY_B = 0x30; + KM_KBP_VKEY_N, // KEY_N = 0x31; + KM_KBP_VKEY_M, // KEY_M = 0x32; + KM_KBP_VKEY_COMMA, // KEY_COMMA = 0x33; + KM_KBP_VKEY_PERIOD, // KEY_DOT = 0x34; + KM_KBP_VKEY_SLASH, // KEY_SLASH = 0x35; + KM_KBP_VKEY_SHIFT, // KEY_RIGHTSHIFT = 0x36; + KM_KBP_VKEY_NPSTAR, // KEY_KPASTERISK = 0x37; + KM_KBP_VKEY_ALT, // KEY_LEFTALT = 0x38; + KM_KBP_VKEY_SPACE, // KEY_SPACE = 0x39; + KM_KBP_VKEY_CAPS, // KEY_CAPSLOCK = 0x3A; + KM_KBP_VKEY_F1, // KEY_F1 = 0x3B; + KM_KBP_VKEY_F2, // KEY_F2 = 0x3C; + KM_KBP_VKEY_F3, // KEY_F3 = 0x3D; + KM_KBP_VKEY_F4, // KEY_F4 = 0x3E; + KM_KBP_VKEY_F5, // KEY_F5 = 0x3F; + KM_KBP_VKEY_F6, // KEY_F6 = 0x40; + KM_KBP_VKEY_F7, // KEY_F7 = 0x41; + KM_KBP_VKEY_F8, // KEY_F8 = 0x42; + KM_KBP_VKEY_F9, // KEY_F9 = 0x43; + KM_KBP_VKEY_F10, // KEY_F10 = 0x44; + 0, // KEY_NUMLOCK = 0x45; + 0, // KEY_SCROLLLOCK = 0x46; + KM_KBP_VKEY_NP7, // KEY_KP7 = 0x47; + KM_KBP_VKEY_NP8, // KEY_KP8 = 0x48; + KM_KBP_VKEY_NP9, // KEY_KP9 = 0x49; + KM_KBP_VKEY_NPMINUS, // KEY_KPMINUS = 0x4A; + KM_KBP_VKEY_NP4, // KEY_KP4 = 0x4B; + KM_KBP_VKEY_NP5, // KEY_KP5 = 0x4C; + KM_KBP_VKEY_NP6, // KEY_KP6 = 0x4D; + KM_KBP_VKEY_NPPLUS, // KEY_KPPLUS = 0x4E; + KM_KBP_VKEY_NP1, // KEY_KP1 = 0x4F; + KM_KBP_VKEY_NP2, // KEY_KP2 = 0x50; + KM_KBP_VKEY_NP3, // KEY_KP3 = 0x51; + KM_KBP_VKEY_NP0, // KEY_KP0 = 0x52; + KM_KBP_VKEY_NPDOT, // KEY_KPDOT = 0x53; + 0, // padding 0x54; + 0, // KEY_ZENKAKUHANKAKU = 0x55; + KM_KBP_VKEY_oE2, // KEY_102ND = 0x56; + KM_KBP_VKEY_F11, // KEY_F11 = 0x57; + KM_KBP_VKEY_F12, // KEY_F12 = 0x58; + 0, // KEY_RO = 0x59; + 0, // KEY_KATAKANA = 0x5a; + 0, // KEY_HIRAGANA = 0x5b; + 0, // KEY_HENKAN = 0x5c; + 0, // KEY_KATAKANAHIRAGANA = 0x5d; + 0, // KEY_MUHENKAN = 0x5e; + 0, // KEY_KPJPCOMMA = 0x5f; + 0, // KEY_KPENTER = 0x60; + KM_KBP_VKEY_CONTROL, // KEY_RIGHTCTRL = 0x61; + 0, // KEY_KPSLASH = 0x62; + 0, // KEY_SYSRQ = 0x63; + KM_KBP_VKEY_ALT // KEY_RIGHTALT = 0x64; + + // Many more KEYS currently not used by KMW... +}; + +#endif // __KEYCODES_H__ diff --git a/linux/ibus-keyman/src/keyman-version.h.in b/linux/ibus-keyman/src/keyman-version.h.in new file mode 100644 index 0000000000..c842f9557a --- /dev/null +++ b/linux/ibus-keyman/src/keyman-version.h.in @@ -0,0 +1,5 @@ +#ifndef __KEYMAN_VERSION_H__ +#define __KEYMAN_VERSION_H__ + +#define KEYMAN_VERSION "@VERSION@" +#endif diff --git a/linux/ibus-keyman/src/keyman.xml.in.in b/linux/ibus-keyman/src/keyman.xml.in.in index e2d2b5e7a2..256b032a09 100644 --- a/linux/ibus-keyman/src/keyman.xml.in.in +++ b/linux/ibus-keyman/src/keyman.xml.in.in @@ -7,7 +7,7 @@ @VERSION@ Keyman Team <support@keyman.com> GPL - http://www.keyman.com + https://keyman.com ibus-keyman /usr/share/keyman/ diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index c699b603a9..f2bf42c9a1 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -59,6 +59,7 @@ #include "keymanutil.h" #include "kmpdetails.h" +#include "keyman-version.h" #define N_(text) text @@ -150,7 +151,7 @@ ibus_keyman_engine_desc_new (gchar * file_name, return engine_desc; } -GList * +GList * ibus_keyman_add_engines(GList * engines, GList * kmpdir_list) { GList *p, *k, *l, *e; @@ -283,10 +284,10 @@ ibus_keyman_get_component (void) component = ibus_component_new ("org.freedesktop.IBus.Keyman", N_("Keyman"), - "10.99.0", + KEYMAN_VERSION, "GPL", - "Daniel Glassey ", - "http://www.keyman.com", + "Keyman team ", + "https://keyman.com", "", "ibus-keyman"); @@ -301,7 +302,7 @@ ibus_keyman_get_component (void) } -// Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item +// Obtain Keyboard Options list from DConf // DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] // // Parameters: @@ -331,7 +332,6 @@ keyman_get_options_fromdconf(gchar *package_id, } // Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item -// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] // // Parameters: // package_id (gchar *): Package ID @@ -349,7 +349,7 @@ keyman_get_options_queue_fromdconf(gchar *package_id, gchar **options = keyman_get_options_fromdconf(package_id, keyboard_id); // Parse options into queue_options - if (*options != NULL) + if (options != NULL) { int index = 0; while (options[index] != NULL) @@ -368,6 +368,7 @@ keyman_get_options_queue_fromdconf(gchar *package_id, } index++; } + g_strfreev(options); } return queue_options; @@ -375,7 +376,8 @@ keyman_get_options_queue_fromdconf(gchar *package_id, // Write new keyboard option to DConf. // DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] -// If the option key already exists, the value is updated. Otherwise a new string 'option_key=option_value' is appended. +// If the option key already exists, the value is updated. Otherwise a new string +// 'option_key=option_value' is appended. // // Parameters: // package_id (gchar *): Package ID @@ -399,7 +401,7 @@ keyman_put_options_todconf(gchar *package_id, gchar *needle = g_strdup_printf("%s=", option_key); gchar *kvp = g_strdup_printf("%s=%s", option_key, option_value); - if (*options != NULL) + if (options != NULL) { int index = 0; gboolean option_updated = FALSE; @@ -408,6 +410,7 @@ keyman_put_options_todconf(gchar *package_id, // If option_key already exists, update value with option_value if (g_strrstr(options[index], needle) != NULL) { + g_free(options[index]); options[index] = kvp; option_updated = TRUE; break; @@ -418,17 +421,20 @@ keyman_put_options_todconf(gchar *package_id, if (!option_updated) { // Resize to add new option and null-terminate - options = g_realloc(options, strlen(kvp) + 1); + int size = index + 2; // old size: index + 1, plus 1 new + options = g_renew(gchar*, options, size); options[index] = kvp; options[index+1] = NULL; } } else { - // If options don't exist, allocate space for new option and null-terminate - options = g_malloc(strlen(kvp) + 1); - options[0] = kvp; - options[1] = NULL; + // we never should come here - keyman_get_options_fromdconf will create empty + // options if they don't yet exist. + // Allocate space for new option and null-terminate + options = g_new(gchar *, 2); + options[0] = kvp; + options[1] = NULL; } // Write to DConf @@ -442,9 +448,9 @@ keyman_put_options_todconf(gchar *package_id, g_object_unref(G_OBJECT(child_settings)); g_free(path); - g_free(kvp); g_free(needle); - g_free(options); + g_strfreev(options); + // kvp got assigned to options[x] and so got freed by g_strfreev() } diff --git a/linux/ibus-keyman/src/keymanutil.h b/linux/ibus-keyman/src/keymanutil.h index 9f95a9fecd..479d89476c 100644 --- a/linux/ibus-keyman/src/keymanutil.h +++ b/linux/ibus-keyman/src/keymanutil.h @@ -66,11 +66,13 @@ #define KEYMAN_DCONF_PATH "/desktop/ibus/keyman/options/" #define KEYMAN_DCONF_OPTIONS_KEY "options" +G_BEGIN_DECLS + void ibus_keyman_init (void); GList *ibus_keyman_list_engines (void); IBusComponent *ibus_keyman_get_component (void); -// Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item +// Obtain Keyboard Options list from DConf // DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] // // Parameters: @@ -83,7 +85,6 @@ gchar** keyman_get_options_fromdconf gchar *keyboard_id); // Obtain Keyboard Options from DConf and parse into a GQueue of struct km_kbp_option_item -// DConf options are in a list of strings like ['option_key1=value1', 'option_key2=value2'] // // Parameters: // package_id (gchar *): Package ID @@ -104,9 +105,11 @@ GQueue* keyman_get_options_queue_fromdconf // option_key (gchar *): Key for the new option // option_value (gchar *): Value of the new option void keyman_put_options_todconf - (gchar *package_id, + (gchar *package_id, gchar *keyboard_id, gchar *option_key, gchar *option_value); +G_END_DECLS + #endif diff --git a/linux/ibus-keyman/src/test/Makefile.am b/linux/ibus-keyman/src/test/Makefile.am new file mode 100644 index 0000000000..239b40b7fa --- /dev/null +++ b/linux/ibus-keyman/src/test/Makefile.am @@ -0,0 +1,84 @@ +# vim:set noet ts=4: +# +# ibus-keyman - The Keyman engine for IBus +# +# Copyright (c) 2021 SIL International +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +include $(top_srcdir)/glib-tap.mk + +AUTOMAKE_OPTIONS = subdir-objects + +AM_CFLAGS = \ + @IBUS_CFLAGS@ \ + @GTK_CFLAGS@ \ + @JSON_GLIB_CFLAGS@ \ + @X11_CFLAGS@ \ + @KEYMAN_PROC_CFLAGS@ \ + -DPKGDATADIR=\"$(pkgdatadir)\" \ + $(NULL) + +AM_LDFLAGS = \ + @IBUS_LIBS@ \ + @GTK_LIBS@ \ + @X11_LIBS@ \ + @JSON_GLIB_LIBS@ \ + @KEYMAN_PROC_LIBS@ \ + $(NULL) + +uninstalled_test_scripts = run-tests.sh + +libexec_PROGRAMS = \ + keymanutil-tests \ + $(NULL) + +keymanutil_tests_SOURCES = \ + keymanutil_tests.c \ + ../keymanutil.c \ + ../keymanutil.h \ + ../kmpdetails.c \ + ../kmpdetails.h \ + $(NULL) + +keymanutil_tests_CFLAGS = \ + @IBUS_CFLAGS@ \ + @GTK_CFLAGS@ \ + @X11_CFLAGS@ \ + @JSON_GLIB_CFLAGS@ \ + @KEYMAN_PROC_CFLAGS@ \ + -DPKGDATADIR=\"$(pkgdatadir)\" \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src \ + $(NULL) + +keymanutil_tests_CPPFLAGS = \ + @IBUS_CFLAGS@ \ + @GTK_CFLAGS@ \ + @X11_CFLAGS@ \ + @JSON_GLIB_CFLAGS@ \ + @KEYMAN_PROC_CFLAGS@ \ + -DPKGDATADIR=\"$(pkgdatadir)\" \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src \ + $(NULL) + +keymanutil_tests_LDFLAGS = \ + @GTK_LIBS@ \ + @IBUS_LIBS@ \ + @X11_LIBS@ \ + @JSON_GLIB_LIBS@ \ + @KEYMAN_PROC_LIBS@ \ + $(NULL) diff --git a/linux/ibus-keyman/src/test/keymanutil_tests.c b/linux/ibus-keyman/src/test/keymanutil_tests.c new file mode 100644 index 0000000000..8b49488cf7 --- /dev/null +++ b/linux/ibus-keyman/src/test/keymanutil_tests.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include "keymanutil.h" + +#define TEST_FIXTURE "keymanutil-test" + +void +delete_key(gchar* testname) { + gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_PATH, TEST_FIXTURE, testname); + GSettings *settings = g_settings_new_with_path(KEYMAN_CHILD_DCONF_NAME, path); + g_settings_reset(settings, KEYMAN_DCONF_OPTIONS_KEY); + g_object_unref(G_OBJECT(settings)); + g_free(path); +} + +void +set_key(gchar* testname, gchar** options) { + gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_PATH, TEST_FIXTURE, testname); + GSettings *settings = g_settings_new_with_path(KEYMAN_CHILD_DCONF_NAME, path); + g_settings_set_strv(settings, KEYMAN_DCONF_OPTIONS_KEY, (const gchar* const*)options); + g_object_unref(G_OBJECT(settings)); + g_free(path); +} + +gchar** +get_key(gchar* testname) { + gchar* path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_PATH, TEST_FIXTURE, testname); + GSettings* settings = g_settings_new_with_path(KEYMAN_CHILD_DCONF_NAME, path); + gchar** result = g_settings_get_strv(settings, KEYMAN_DCONF_OPTIONS_KEY); + g_object_unref(G_OBJECT(settings)); + g_free(path); + return result; +} + +void +test_keyman_put_options_todconf__new_key() { + // Initialize + gchar* testname = "test_keyman_put_options_todconf__new_key"; + delete_key(testname); + gchar* value = g_strdup_printf("%d", g_test_rand_int()); + + // Execute + keyman_put_options_todconf(TEST_FIXTURE, testname, "new_key", value); + + // Verify + gchar** options = get_key(testname); + gchar* expected = g_strdup_printf("new_key=%s", value); + g_assert_nonnull(options); + g_assert_cmpstr(options[0], ==, expected); + g_assert_null(options[1]); + + // Cleanup + g_free(expected); + g_free(value); + g_strfreev(options); + delete_key(testname); +} + +void +test_keyman_put_options_todconf__other_keys() { + // Initialize + gchar* testname = "test_keyman_put_options_todconf__other_keys"; + delete_key(testname); + gchar* existingKeys[] = {"key1=val1", "key2=val2", NULL}; + set_key(testname, existingKeys); + gchar* value = g_strdup_printf("%d", g_test_rand_int()); + + // Execute + keyman_put_options_todconf(TEST_FIXTURE, testname, "new_key", value); + + // Verify + gchar** options = get_key(testname); + gchar* expected = g_strdup_printf("new_key=%s", value); + g_assert_nonnull(options); + g_assert_cmpstr(options[0], ==, "key1=val1"); + g_assert_cmpstr(options[1], ==, "key2=val2"); + g_assert_cmpstr(options[2], ==, expected); + g_assert_null(options[3]); + + // Cleanup + g_free(expected); + g_free(value); + g_strfreev(options); + delete_key(testname); +} + +void +test_keyman_put_options_todconf__existing_key() { + // Initialize + gchar* testname = "test_keyman_put_options_todconf__existing_key"; + delete_key(testname); + gchar* existingKeys[] = {"key1=val1", "new_key=val2", NULL}; + set_key(testname, existingKeys); + gchar* value = g_strdup_printf("%d", g_test_rand_int()); + + // Execute + keyman_put_options_todconf(TEST_FIXTURE, testname, "new_key", value); + + // Verify + gchar** options = get_key(testname); + gchar* expected = g_strdup_printf("new_key=%s", value); + g_assert_nonnull(options); + g_assert_cmpstr(options[0], ==, "key1=val1"); + g_assert_cmpstr(options[1], ==, expected); + g_assert_null(options[2]); + + // Cleanup + g_free(expected); + g_free(value); + g_strfreev(options); + delete_key(testname); +} + +int +main(int argc, char* argv[]) { + gtk_init(&argc, &argv); + g_test_init(&argc, &argv, NULL); + g_test_set_nonfatal_assertions(); + + // Add tests + g_test_add_func("/keymanutil/keyman_put_options_todconf/new_key", test_keyman_put_options_todconf__new_key); + g_test_add_func("/keymanutil/keyman_put_options_todconf/other_keys", test_keyman_put_options_todconf__other_keys); + g_test_add_func("/keymanutil/keyman_put_options_todconf/existing_key", test_keyman_put_options_todconf__existing_key); + + // Run tests + int retVal = g_test_run(); + + return retVal; +} diff --git a/linux/ibus-keyman/src/test/run-tests.sh b/linux/ibus-keyman/src/test/run-tests.sh new file mode 100755 index 0000000000..8763f1a98a --- /dev/null +++ b/linux/ibus-keyman/src/test/run-tests.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +SRCDIR=${top_srcdir:-$(realpath $(dirname $0)/../..)} +PID_FILE=/tmp/keymanutil-tests-pids + +if ! which Xvfb > /dev/null || ! which Xephyr > /dev/null || ! which metacity > /dev/null; then + echo "Please install Xvfb, Xephyr and metacity before running these tests!" + exit 1 +fi + +function cleanup() { + if [ -f $PID_FILE ]; then + echo + echo "Shutting down processes..." + bash $PID_FILE > /dev/null 2>&1 + rm $PID_FILE + rm -rf $TEMP_DATA_DIR + echo "Finished shutdown of processes." + fi +} + +echo > $PID_FILE + +trap cleanup EXIT SIGINT + +echo "Starting Xvfb..." +Xvfb -screen 0 1024x768x24 :33 &> /dev/null & +echo "kill -9 $!" >> $PID_FILE +sleep 1 +echo "Starting Xephyr..." +DISPLAY=:33 Xephyr :32 -screen 1024x768 &> /dev/null & +echo "kill -9 $!" >> $PID_FILE +sleep 1 +echo "Starting metacity" +metacity --display=:32 &> /dev/null & +echo "kill -9 $!" >> $PID_FILE + +export DISPLAY=:32 + +# Install schema to temporary directory. This removes the build dependency on the keyman package. +TEMP_DATA_DIR=$(mktemp --directory) +SCHEMA_DIR=$TEMP_DATA_DIR/glib-2.0/schemas +export XDG_DATA_DIRS=$TEMP_DATA_DIR:$XDG_DATA_DIRS + +mkdir -p $SCHEMA_DIR +cp $SRCDIR/../keyman-config/com.keyman.gschema.xml $SCHEMA_DIR/ +glib-compile-schemas $SCHEMA_DIR + +export GSETTINGS_BACKEND=memory + +./keymanutil-tests $@ diff --git a/linux/ibus-keyman/tap-driver.sh b/linux/ibus-keyman/tap-driver.sh new file mode 100755 index 0000000000..65866ae696 --- /dev/null +++ b/linux/ibus-keyman/tap-driver.sh @@ -0,0 +1,644 @@ +#! /bin/sh +# Copyright (C) 2011-2013 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +# from https://gitlab.gnome.org/GNOME/glib/-/blob/glib-2-58/tap-driver.sh + +scriptversion=2011-12-27.17; # UTC + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +me=tap-driver.sh + +fatal () +{ + echo "$me: fatal: $*" >&2 + exit 1 +} + +usage_error () +{ + echo "$me: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat < + # + trap : 1 3 2 13 15 + if test $merge -gt 0; then + exec 2>&1 + else + exec 2>&3 + fi + "$@" + echo $? + ) | LC_ALL=C ${AM_TAP_AWK-awk} \ + -v me="$me" \ + -v test_script_name="$test_name" \ + -v log_file="$log_file" \ + -v trs_file="$trs_file" \ + -v expect_failure="$expect_failure" \ + -v merge="$merge" \ + -v ignore_exit="$ignore_exit" \ + -v comments="$comments" \ + -v diag_string="$diag_string" \ +' +# FIXME: the usages of "cat >&3" below could be optimized when using +# FIXME: GNU awk, and/or on systems that supports /dev/fd/. + +# Implementation note: in what follows, `result_obj` will be an +# associative array that (partly) simulates a TAP result object +# from the `TAP::Parser` perl module. + +## ----------- ## +## FUNCTIONS ## +## ----------- ## + +function fatal(msg) +{ + print me ": " msg | "cat >&2" + exit 1 +} + +function abort(where) +{ + fatal("internal error " where) +} + +# Convert a boolean to a "yes"/"no" string. +function yn(bool) +{ + return bool ? "yes" : "no"; +} + +function add_test_result(result) +{ + if (!test_results_index) + test_results_index = 0 + test_results_list[test_results_index] = result + test_results_index += 1 + test_results_seen[result] = 1; +} + +# Whether the test script should be re-run by "make recheck". +function must_recheck() +{ + for (k in test_results_seen) + if (k != "XFAIL" && k != "PASS" && k != "SKIP") + return 1 + return 0 +} + +# Whether the content of the log file associated to this test should +# be copied into the "global" test-suite.log. +function copy_in_global_log() +{ + for (k in test_results_seen) + if (k != "PASS") + return 1 + return 0 +} + +# FIXME: this can certainly be improved ... +function get_global_test_result() +{ + if ("ERROR" in test_results_seen) + return "ERROR" + if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) + return "FAIL" + all_skipped = 1 + for (k in test_results_seen) + if (k != "SKIP") + all_skipped = 0 + if (all_skipped) + return "SKIP" + return "PASS"; +} + +function stringify_result_obj(result_obj) +{ + if (result_obj["is_unplanned"] || result_obj["number"] != testno) + return "ERROR" + + if (plan_seen == LATE_PLAN) + return "ERROR" + + if (result_obj["directive"] == "TODO") + return result_obj["is_ok"] ? "XPASS" : "XFAIL" + + if (result_obj["directive"] == "SKIP") + return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; + + if (length(result_obj["directive"])) + abort("in function stringify_result_obj()") + + return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL +} + +function decorate_result(result) +{ + color_name = color_for_result[result] + if (color_name) + return color_map[color_name] "" result "" color_map["std"] + # If we are not using colorized output, or if we do not know how + # to colorize the given result, we should return it unchanged. + return result +} + +function report(result, details) +{ + if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) + { + msg = ": " test_script_name + add_test_result(result) + } + else if (result == "#") + { + msg = " " test_script_name ":" + } + else + { + abort("in function report()") + } + if (length(details)) + msg = msg " " details + # Output on console might be colorized. + print decorate_result(result) msg + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print result msg | "cat >&3"; +} + +function testsuite_error(error_message) +{ + report("ERROR", "- " error_message) +} + +function handle_tap_result() +{ + details = result_obj["number"]; + if (length(result_obj["description"])) + details = details " " result_obj["description"] + + if (plan_seen == LATE_PLAN) + { + details = details " # AFTER LATE PLAN"; + } + else if (result_obj["is_unplanned"]) + { + details = details " # UNPLANNED"; + } + else if (result_obj["number"] != testno) + { + details = sprintf("%s # OUT-OF-ORDER (expecting %d)", + details, testno); + } + else if (result_obj["directive"]) + { + details = details " # " result_obj["directive"]; + if (length(result_obj["explanation"])) + details = details " " result_obj["explanation"] + } + + report(stringify_result_obj(result_obj), details) +} + +# `skip_reason` should be empty whenever planned > 0. +function handle_tap_plan(planned, skip_reason) +{ + planned += 0 # Avoid getting confused if, say, `planned` is "00" + if (length(skip_reason) && planned > 0) + abort("in function handle_tap_plan()") + if (plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error("multiple test plans") + return; + } + planned_tests = planned + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) + # If testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so do not worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if (planned == 0 && testno == 0) + { + if (length(skip_reason)) + skip_reason = "- " skip_reason; + report("SKIP", skip_reason); + } +} + +function extract_tap_comment(line) +{ + if (index(line, diag_string) == 1) + { + # Strip leading `diag_string` from `line`. + line = substr(line, length(diag_string) + 1) + # And strip any leading and trailing whitespace left. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + # Return what is left (if any). + return line; + } + return ""; +} + +# When this function is called, we know that line is a TAP result line, +# so that it matches the (perl) RE "^(not )?ok\b". +function setup_result_obj(line) +{ + # Get the result, and remove it from the line. + result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) + sub("^(not )?ok[ \t]*", "", line) + + # If the result has an explicit number, get it and strip it; otherwise, + # automatically assing the next progresive number to it. + if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) + { + match(line, "^[0-9]+") + # The final `+ 0` is to normalize numbers with leading zeros. + result_obj["number"] = substr(line, 1, RLENGTH) + 0 + line = substr(line, RLENGTH + 1) + } + else + { + result_obj["number"] = testno + } + + if (plan_seen == LATE_PLAN) + # No further test results are acceptable after a "late" TAP plan + # has been seen. + result_obj["is_unplanned"] = 1 + else if (plan_seen && testno > planned_tests) + result_obj["is_unplanned"] = 1 + else + result_obj["is_unplanned"] = 0 + + # Strip trailing and leading whitespace. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + + # This will have to be corrected if we have a "TODO"/"SKIP" directive. + result_obj["description"] = line + result_obj["directive"] = "" + result_obj["explanation"] = "" + + if (index(line, "#") == 0) + return # No possible directive, nothing more to do. + + # Directives are case-insensitive. + rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" + + # See whether we have the directive, and if yes, where. + pos = match(line, rx "$") + if (!pos) + pos = match(line, rx "[^a-zA-Z0-9_]") + + # If there was no TAP directive, we have nothing more to do. + if (!pos) + return + + # Let`s now see if the TAP directive has been escaped. For example: + # escaped: ok \# SKIP + # not escaped: ok \\# SKIP + # escaped: ok \\\\\# SKIP + # not escaped: ok \ # SKIP + if (substr(line, pos, 1) == "#") + { + bslash_count = 0 + for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) + bslash_count += 1 + if (bslash_count % 2) + return # Directive was escaped. + } + + # Strip the directive and its explanation (if any) from the test + # description. + result_obj["description"] = substr(line, 1, pos - 1) + # Now remove the test description from the line, that has been dealt + # with already. + line = substr(line, pos) + # Strip the directive, and save its value (normalized to upper case). + sub("^[ \t]*#[ \t]*", "", line) + result_obj["directive"] = toupper(substr(line, 1, 4)) + line = substr(line, 5) + # Now get the explanation for the directive (if any), with leading + # and trailing whitespace removed. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + result_obj["explanation"] = line +} + +function get_test_exit_message(status) +{ + if (status == 0) + return "" + if (status !~ /^[1-9][0-9]*$/) + abort("getting exit status") + if (status < 127) + exit_details = "" + else if (status == 127) + exit_details = " (command not found?)" + else if (status >= 128 && status <= 255) + exit_details = sprintf(" (terminated by signal %d?)", status - 128) + else if (status > 256 && status <= 384) + # We used to report an "abnormal termination" here, but some Korn + # shells, when a child process die due to signal number n, can leave + # in $? an exit status of 256+n instead of the more standard 128+n. + # Apparently, both behaviours are allowed by POSIX (2008), so be + # prepared to handle them both. See also Austing Group report ID + # 0000051 + exit_details = sprintf(" (terminated by signal %d?)", status - 256) + else + # Never seen in practice. + exit_details = " (abnormal termination)" + return sprintf("exited with status %d%s", status, exit_details) +} + +function write_test_results() +{ + print ":global-test-result: " get_global_test_result() > trs_file + print ":recheck: " yn(must_recheck()) > trs_file + print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file + for (i = 0; i < test_results_index; i += 1) + print ":test-result: " test_results_list[i] > trs_file + close(trs_file); +} + +BEGIN { + +## ------- ## +## SETUP ## +## ------- ## + +'"$init_colors"' + +# Properly initialized once the TAP plan is seen. +planned_tests = 0 + +COOKED_PASS = expect_failure ? "XPASS": "PASS"; +COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; + +# Enumeration-like constants to remember which kind of plan (if any) +# has been seen. It is important that NO_PLAN evaluates "false" as +# a boolean. +NO_PLAN = 0 +EARLY_PLAN = 1 +LATE_PLAN = 2 + +testno = 0 # Number of test results seen so far. +bailed_out = 0 # Whether a "Bail out!" directive has been seen. + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +plan_seen = NO_PLAN + +## --------- ## +## PARSING ## +## --------- ## + +is_first_read = 1 + +while (1) + { + # Involutions required so that we are able to read the exit status + # from the last input line. + st = getline + if (st < 0) # I/O error. + fatal("I/O error while reading from input stream") + else if (st == 0) # End-of-input + { + if (is_first_read) + abort("in input loop: only one input line") + break + } + if (is_first_read) + { + is_first_read = 0 + nextline = $0 + continue + } + else + { + curline = nextline + nextline = $0 + $0 = curline + } + # Copy any input line verbatim into the log file. + print | "cat >&3" + # Parsing of TAP input should stop after a "Bail out!" directive. + if (bailed_out) + continue + + # TAP test result. + if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) + { + testno += 1 + setup_result_obj($0) + handle_tap_result() + } + # TAP plan (normal or "SKIP" without explanation). + else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) + { + # The next two lines will put the number of planned tests in $0. + sub("^1\\.\\.", "") + sub("[^0-9]*$", "") + handle_tap_plan($0, "") + continue + } + # TAP "SKIP" plan, with an explanation. + else if ($0 ~ /^1\.\.0+[ \t]*#/) + { + # The next lines will put the skip explanation in $0, stripping + # any leading and trailing whitespace. This is a little more + # tricky in truth, since we want to also strip a potential leading + # "SKIP" string from the message. + sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") + sub("[ \t]*$", ""); + handle_tap_plan(0, $0) + } + # "Bail out!" magic. + # Older versions of prove and TAP::Harness (e.g., 3.17) did not + # recognize a "Bail out!" directive when preceded by leading + # whitespace, but more modern versions (e.g., 3.23) do. So we + # emulate the latter, "more modern" behaviour. + else if ($0 ~ /^[ \t]*Bail out!/) + { + bailed_out = 1 + # Get the bailout message (if any), with leading and trailing + # whitespace stripped. The message remains stored in `$0`. + sub("^[ \t]*Bail out![ \t]*", ""); + sub("[ \t]*$", ""); + # Format the error message for the + bailout_message = "Bail out!" + if (length($0)) + bailout_message = bailout_message " " $0 + testsuite_error(bailout_message) + } + # Maybe we have too look for dianogtic comments too. + else if (comments != 0) + { + comment = extract_tap_comment($0); + if (length(comment)) + report("#", comment); + } + } + +## -------- ## +## FINISH ## +## -------- ## + +# A "Bail out!" directive should cause us to ignore any following TAP +# error, as well as a non-zero exit status from the TAP producer. +if (!bailed_out) + { + if (!plan_seen) + { + testsuite_error("missing test plan") + } + else if (planned_tests != testno) + { + bad_amount = testno > planned_tests ? "many" : "few" + testsuite_error(sprintf("too %s tests run (expected %d, got %d)", + bad_amount, planned_tests, testno)) + } + if (!ignore_exit) + { + # Fetch exit status from the last line. + exit_message = get_test_exit_message(nextline) + if (exit_message) + testsuite_error(exit_message) + } + } + +write_test_results() + +exit 0 + +} # End of "BEGIN" block. +' + +# TODO: document that we consume the file descriptor 3 :-( +} 3>"$log_file" + +test $? -eq 0 || fatal "I/O or internal error" diff --git a/linux/ibus-keyman/tap-test b/linux/ibus-keyman/tap-test new file mode 100755 index 0000000000..74f8ac94e7 --- /dev/null +++ b/linux/ibus-keyman/tap-test @@ -0,0 +1,7 @@ +#! /bin/sh + +# from https://gitlab.gnome.org/GNOME/glib/-/blob/glib-2-58/tap-test + +# run a GTest in tap mode. The test binary is passed as $1 + +$1 -k --tap diff --git a/linux/ibus-keyman/test/README.md b/linux/ibus-keyman/test/README.md deleted file mode 100644 index a61ab64add..0000000000 --- a/linux/ibus-keyman/test/README.md +++ /dev/null @@ -1,43 +0,0 @@ -ibus-keyman tests -================= - -Setup ------ - -You need pynput to generate keypresses - -`pip3 install pynput` - -These tests assume that the test kmx and kmn are located in -`~/.local/share/keyman/test_kmx` - -You may like to link `common/core/desktop/tests/unit/kmx` to that location - - -Running tests -------------- - -To run all the tests - -`./runtests.sh` - -To run an individual test - -`python3 ./test_ibus_keyman.py ""` - -If your keyboard isn't "us" then you need to set the keyboard before running the test - -`setxkbmap us` - -You may like to `setxkbmap -query|grep layout` before you run the test to find -out what to set it back to afterwards. - -Test output ------------ - -The tests produce `.in` and `.out` files -`.in` is the expected output -`.out` is the actual output - -You may use `diff` or something else to compare them - diff --git a/linux/ibus-keyman/test/change_keyboard.py b/linux/ibus-keyman/test/change_keyboard.py deleted file mode 100644 index eee760e21d..0000000000 --- a/linux/ibus-keyman/test/change_keyboard.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/python3 - -import gi -import logging -import sys - -gi.require_version('IBus', '1.0') -from gi.repository import IBus - -def bus_has_engine(bus, name): - engines = bus.get_engines_by_names([name]) - return len(engines) - -def get_current_engine(bus): - try: - contextname = bus.current_input_context() - ic = IBus.InputContext.get_input_context(contextname, bus.get_connection()) - engine = ic.get_engine() - if engine: - return engine.get_name() - except Exception as e: - print("Failed to get current engine") - print(e) - - -def change_keyboard(bus, name): - try: - contextname = bus.current_input_context() - ic = IBus.InputContext.get_input_context(contextname, bus.get_connection()) - if bus_has_engine(bus, name) <= 0: - print ("Could not find engine %s"%name) - else: - ic.set_engine(name) - except Exception as e: - print("Failed to change keyboard") - print(e) - -def main(argv): - if len(sys.argv) != 2: - logging.error("change_keyboard.py ") - sys.exit(2) - logging.basicConfig(level=logging.INFO) - change_keyboard(sys.argv[1]) - -if __name__ == "__main__": - main(sys.argv[1:]) \ No newline at end of file diff --git a/linux/ibus-keyman/test/keypress_actions.py b/linux/ibus-keyman/test/keypress_actions.py deleted file mode 100644 index 7d46e51ef3..0000000000 --- a/linux/ibus-keyman/test/keypress_actions.py +++ /dev/null @@ -1,445 +0,0 @@ -""" -Keypress actions. - -@author: Eitan Isaacson -@author: Nagappan Alagappan -@copyright: Copyright (c) 2007 - 2009 Eitan Isaacson -@copyright: Copyright (c) 2009-13 Nagappan Alagappan -@license: LGPL - -http://ldtp.freedesktop.org - -This file may be distributed and/or modified under the terms of the GNU Lesser General -Public License version 2 as published by the Free Software Foundation. This file -is distributed without any warranty; without even the implied warranty of -merchantability or fitness for a particular purpose. - -See 'COPYING' in the source distribution for more information. - -Headers in this file shall remain intact. -""" - -import re -import time -try: - from gi.repository import GObject as gobject -except: - import gobject -import pyatspi -import subprocess - -from sequence_step import AtomicAction -_ = lambda x: x - -# Highest granularity, define timing for every single press and release - -# Minimum delta time -min_delta = 50 - -# Maximum time before a key release -release_max = 400 - -_non_print_key_val = {"escape" : 9, "esc" : 9, "backspace" : 22, - "bksp" : 22, "ctrl" : 37, "windowskey" : 115, - "tab" : 23, "return" : 36, "enter" : 36, - "shift" : 50, "shiftl" : 50, "shiftr" : 62, - "home" : 110, "end" : 115, "window" : 115, - "alt" : 64, "altl" : 64, "altr" : 108, - "up" : 111, "down" : 116, "right" : 114, - "left" : 113, "space" : 65, "capslock" : 66, - "caps" : 66, "menu" : 135, "ins" : 106, - "del" : 119, "insert" : 106, "delete" : 119, - "pageup" : 112, "pagedown" : 117, "pgup" : 112, - "pgdown" : 117, "numlock" : 77, "scrolllock" : 78, - "F1" : 67, "F2" : 68, "F3" : 69, "F4" : 70, - "F5" : 71, "F6" : 72, "F7" : 73, "F8" : 74, - "F9" : 75, "F10" : 76, "F11" : 95, "F12" : 96, - "ctrlr" : 105, "prtscrn" : 107, "ctrll" : 37} - -def _get_keyboard_keycodes(): - result = subprocess.run('xmodmap -pke', stdout = subprocess.PIPE, - stderr = subprocess.PIPE, - shell = True, close_fds = True) - if (result.returncode == 0 and result.stdout != ""): - output = result.stdout.decode("utf-8", "strict") - for line in output.split('\n'): - if line.strip() == '': - continue - split = re.split('=', line, maxsplit = 2) - keycode = int(re.split(" ", split[0].strip())[-1]) - if len(split[1]): - key = re.split(" ", split[1], 3)[1].lower() - _non_print_key_val[key] = keycode - -_get_keyboard_keycodes() - -class KeyCombo: - def __init__(self): - self.shift = False - self.capslck = False - self.non_print_key = False - self.value = None - -class KeyboardOp: - def __init__(self): - self._downchar = 12 - self._upchar = 21 - self._undefined_key = -1 - self._max_tokens = 256 - self._max_tok_size = 15 - - def _get_key_value(self, keyval): - # A - Z / a - z - _char_key = {'a' : 38, 'b' : 56, 'c' : 54, 'd' : 40, 'e' : 26, - 'f' : 41, 'g' : 42, 'h' : 43, 'i' : 31, 'j' : 44, - 'k' : 45, 'l' : 46, 'm' : 58, 'n' : 57, 'o' : 32, - 'p' : 33, 'q' : 24, 'r' : 27, 's' : 39, 't' : 28, - 'u' : 30, 'v' : 55, 'w' : 25, 'x' : 53, 'y' : 29, - 'z' : 52} - # 0 - 9 - _digit_key = {'0' : 19, '1' : 10, '2' : 11, '3' : 12, '4' : 13, - '5' : 14, '6' : 15, '7' : 16, '8' : 17, '9' : 18} - # Symbols - _symbol_key_val = {'-' : 20, '=' : 21, '[' : 34, - ']' : 35, ';' : 47, '\'' : 48, - '`' : 49, '\\' : 51, ',' : 59, - '.' : 60, '/' : 61, ' ' : 65} - _symbol_shift_key_val = {'!' : 10, '@' : 11, '#' : 12, - '$' : 13, '%' : 14, '^' : 15, - '&' : 16, '*' : 17, '(' : 18, - ')' : 19, '_' : 20, '+' : 21, - '{' : 34, '}' : 35, ':' : 47, - '"' :48, '~' : 49, '|' : 51, - '<' : 59, '>' : 60, '?' : 61} - - return_val = KeyCombo() - if len(keyval) == 1: - # This will identify small characters - if keyval in _char_key: - return_val.shift = False - return_val.capslck = False - return_val.non_print_key = False - return_val.value = _char_key[keyval] - return return_val - # This will identify Capital Charaters i.e. Shift+Small - # Character - if keyval.lower() in _char_key: - return_val.shift = False - return_val.capslck = True - return_val.non_print_key = False - return_val.value = _char_key[keyval.lower()] - return return_val - # This will identify Digits - if keyval in _digit_key: - return_val.shift = False - return_val.capslck = False - return_val.non_print_key = False - return_val.value = _digit_key[keyval] - return return_val - # This will identify Symbols - # Symbols obtained without using Shift Key - if keyval in _symbol_key_val: - return_val.shift = False - return_val.capslck = False - return_val.non_print_key = False - return_val.value = _symbol_key_val[keyval] - return return_val - # Symbols produced with a key combination - # including Shift Key - if keyval in _symbol_shift_key_val: - return_val.shift = True - return_val.capslck = False - return_val.non_print_key = False - return_val.value = _symbol_shift_key_val[keyval] - return return_val - else: - # This is for identifying non printing keys like numlock, - # capslock, etc - if keyval.lower() in _non_print_key_val: - return_val.shift = False - return_val.capslck = False - return_val.non_print_key = True - return_val.value = _non_print_key_val[keyval.lower()] - return return_val - - # Key Undefined - return_val.shift = False - return_val.capslck = False - return_val.non_print_key = False - return_val.value = self._undefined_key - return return_val - - def get_keyval_id(self, input_str): - index = 0 - key_vals = [] - while index < len(input_str): - token = '' - # Identified a Non Printing Key - if input_str[index] == '<': - index += 1 - i = 0 - while input_str[index] != '>' and i < self._max_tok_size: - token += input_str[index] - index += 1 - i += 1 - if input_str[index] != '>': - # Premature end of string without an opening '<' - return None - index += 1 - else: - token = input_str[index] - index += 1 - - key_val = self._get_key_value(token) - if key_val.value == self._undefined_key: - # Invalid key - return None - key_vals.append(key_val) - return key_vals - -class KeyPressAction(AtomicAction): - """ - A key press step. Emulates a keyboard key press. - """ - def __init__(self, key_code=None, key_name=None, delta_time=0): - """ - Initialize L{KeyPressAction}. Could use either a hardware keycode, - a key name, or both. - - @param delta_time: Time to wait before performing this step. - @type delta_time: integer - @param key_code: Hardware keycode. - @type key_code: integer. - @param key_name: Key name. - @type key_name: string. - """ - if (None, None) == (key_name, key_code): - raise TypeError("Need either a key code or a key name") - if delta_time > release_max: delta_time = release_max - self._key_name = key_name - if key_code is None: - self.key_op = KeyboardOp() - key_vals = self.key_op.get_keyval_id(key_name) - if not key_vals: - return - for key_val in key_vals: - AtomicAction.__init__(self, delta_time, self._keyPress, key_val.value) - else: - AtomicAction.__init__(self, delta_time, self._keyPress, key_code) - - def _keyPress(self, key_code): - """ - Perform actual key press. - - @param key_code: Hardware key code. - @type key_code: integer - """ - pyatspi.Registry.generateKeyboardEvent(key_code, None, pyatspi.KEY_PRESS) - - def __str__(self): - """ - String representation of instance. - - @return: String representation of instance. - @rtype: string - """ - return _('Key press %s') % self._key_name or 'a key' - -class KeyReleaseAction(AtomicAction): - """ - A key release step. Emulates a keyboard key release. - """ - def __init__(self, key_code=None, key_name=None, delta_time=0): - """ - Initialize L{KeyReleaseAction}. Could use either a hardware keycode, - a key name, or both. - - @param delta_time: Time to wait before performing this step. - @type delta_time: integer - @param key_code: Hardware keycode. - @type key_code: integer. - @param key_name: Key name. - @type key_name: string. - """ - if (None, None) == (key_name, key_code): - raise TypeError("Need either a key code or a key name") - if delta_time > release_max: delta_time = release_max - self._key_name = key_name - if key_code is None: - self.key_op = KeyboardOp() - key_vals = self.key_op.get_keyval_id(key_name) - if not key_vals: - return - for key_val in key_vals: - AtomicAction.__init__(self, delta_time, self._keyRelease, key_val.value) - else: - AtomicAction.__init__(self, delta_time, self._keyRelease, key_code) - - def _keyRelease(self, key_code): - """ - Perform actual key release. - - @param key_code: Hardware key code. - @type key_code: integer - """ - pyatspi.Registry.generateKeyboardEvent(key_code, None, pyatspi.KEY_RELEASE) - - def __str__(self): - """ - String representation of instance. - - @return: String representation of instance. - @rtype: string - """ - return _('Key release %s') % self._key_name or 'a key' - -# A bit smarter about common interactions. - -keystroke_interval = 10 -mod_key_code_mappings = { - 'GDK_CONTROL_MASK' : 37, - 'GDK_MOD1_MASK' : 64, - 'GDK_LOCK_MASK' : 66, - 'GDK_SHIFT_MASK' : 50 - } - -class KeyComboAction(AtomicAction): - """ - Key combo action. Performs a press and release of a single or key combination. - - @ivar _key_combo: Name of key combination or single key press-release. - @type _key_combo: string - """ - def __init__(self, key_combo, delta_time=0): - """ - Initialize L{KeyComboAction}. - - @param key_combo: Name of key combination or single key press-release. - @type key_combo: string - @param delta_time: Time to wait before performing step. - @type delta_time: integer - """ - self.key_op = KeyboardOp() - key_vals = self.key_op.get_keyval_id(key_combo) - if not key_vals: - return - self._key_combo = key_vals - if delta_time < min_delta: delta_time = min_delta - AtomicAction.__init__(self, delta_time, self._doCombo) - - def __call__(self): - """ - Perform step. Overridden to omit L{SequenceStep.stepDone}. - """ - self._func(*self._args) - - def _doCombo(self): - """ - Perform combo operation. - - @param key_code: Key code to press. - @type key_code: integer - @param modifiers: Modifier mask to press with. - @type modifiers: integer - """ - interval = 0 - for key_val in self._key_combo: - if key_val.non_print_key == True: - _type = pyatspi.KEY_PRESS - else: - _type = pyatspi.KEY_PRESSRELEASE - - if key_val.shift: - # press shift - pyatspi.Registry.generateKeyboardEvent(50, None, pyatspi.KEY_PRESS) - if key_val.capslck: - # press / release capslck - pyatspi.Registry.generateKeyboardEvent(66, None, pyatspi.KEY_PRESSRELEASE) - - time.sleep(0.01) - pyatspi.Registry.generateKeyboardEvent(key_val.value, None, _type) - - if key_val.shift: - # release shift - pyatspi.Registry.generateKeyboardEvent(50, None, pyatspi.KEY_RELEASE) - if key_val.capslck: - # press / release capslck - pyatspi.Registry.generateKeyboardEvent(66, None, pyatspi.KEY_PRESSRELEASE) - - if key_val.non_print_key == False: - # If NOT found non_print_key, then release all - # non_print_key - index = self._key_combo.index(key_val) - while index >= 0: - index -= 1 - if index == -1: - break - tmp_key_val = self._key_combo[index] - # EX: - Here release both alt and tab - # m - - if tmp_key_val.non_print_key == False: - break - # Release all non_print_key - pyatspi.Registry.generateKeyboardEvent(tmp_key_val.value, None, pyatspi.KEY_RELEASE) - - index = self._key_combo.index(key_val) - if key_val.non_print_key == False: - index -= 1 - while index >= 0: - tmp_key_val = self._key_combo[index] - index -= 1 - # EX: - Here release both alt and tab - if tmp_key_val.non_print_key == False: - break - # Release all non_print_key - pyatspi.Registry.generateKeyboardEvent(tmp_key_val.value, None, pyatspi.KEY_RELEASE) - - gobject.timeout_add(interval, self.stepDone) - - def _keyPress(self, hw_code): - """ - Press modifier key. - - @param hw_code: Hardware code for key. - @type hw_code: integer - """ - pyatspi.Registry.generateKeyboardEvent(hw_code, None, pyatspi.KEY_PRESS) - return False - - def _keyRelease(self, hw_code): - """ - Release modifier key. - - @param hw_code: Hardware code for key. - @type hw_code: integer - """ - pyatspi.Registry.generateKeyboardEvent(hw_code, None, pyatspi.KEY_RELEASE) - return False - - def _keyPressRelease(self, keyval): - """ - Press and release non-modifier key. - - @param key_code: Key code. - @type key_code: integer - """ - pyatspi.Registry.generateKeyboardEvent(keyval, None, - pyatspi.KEY_PRESSRELEASE) - return False - - def __str__(self): - """ - String representation of instance. - - @return: String representation of instance. - @rtype: string - """ - return _('Press %s') % self._key_combo - -if __name__ == "__main__": - type_action = KeyComboAction("Hello world!") - type_action() - try: - gobject.MainLoop().run() - except KeyboardInterrupt: - pass diff --git a/linux/ibus-keyman/test/runtests.sh b/linux/ibus-keyman/test/runtests.sh deleted file mode 100755 index 43d701b543..0000000000 --- a/linux/ibus-keyman/test/runtests.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash - -tests=( - '000 - null keyboard' - '001 - basic input UnicodeI' - '002 - basic input Unicode' - '003 - nul' - '004 - basic input (shift 2)' - '005 - nul with initial context' - '006 - vkey input (shift ctrl)' - '007 - vkey input (ctrl alt)' - '008 - vkey input (ctrl alt 2)' - '012 - ralt' - '013 - deadkeys' - '014 - groups and virtual keys' - '015 - ralt 2' - '017 - space mnemonic kbd' - '018 - nul testing' - '019 - multiple deadkeys' - '020 - deadkeys and backspace' - '028 - smp' - '029 - beep' - '030 - multiple groups' - '038 - punctkeys' - '039 - generic ctrlalt' - ) - - -#echo ${tests[*]} -#echo $tests[*] - -#for test in ${tests}; do echo "$test"; done - -model=`setxkbmap -query|grep model|cut -d ':' -f 2|tr -d ' '` -layout=`setxkbmap -query|grep layout|cut -d ':' -f 2|tr -d ' '` -rules=`setxkbmap -query|grep rules|cut -d ':' -f 2|tr -d ' '` -variant=`setxkbmap -query|grep variant|cut -d ':' -f 2|tr -d ' '` - -echo "System xkb keyboard" -echo "model:$model" -echo "layout:$layout" -echo "rules:$rules" -echo "variant:$variant" - -echo "Setting keyboard to us" -setxkbmap us -setxkbmap -query - -count=0 -passed=0 -failed=0 -while [ "x${tests[count]}" != "x" ] -do - echo ${tests[count]} - setxkbmap us - python3 test_ibus_keyman.py "${tests[count]}" - diffret=`diff -uws "${tests[count]}.in" "${tests[count]}.out"` - # echo $? - if [ "x$?" == "x0" ]; then - echo "passed" - passed=$(( $passed + 1 )) - else - echo "failed" - failed=$(( $failed + 1 )) - fi - count=$(( $count + 1 )) -done - -echo "Setting xkb keyboard back to system one" -if [ "x$variant" == "x" ]; then - setxkbmap -model $model -layout $layout -rules $rules -else - setxkbmap -model $model -layout $layout -rules $rules -variant $variant -fi -setxkbmap -query - -echo "-----------------------------------------------------" -echo "Tests run : ${count}" -echo "Passed : ${passed}" -echo "Failed : ${failed}" - diff --git a/linux/ibus-keyman/test/sequence_step.py b/linux/ibus-keyman/test/sequence_step.py deleted file mode 100644 index 9ceabf4061..0000000000 --- a/linux/ibus-keyman/test/sequence_step.py +++ /dev/null @@ -1,173 +0,0 @@ -''' -Base sequence step class and some derivatives. - -@author: Eitan Isaacson -@copyright: Copyright (c) 2007 - 2009 Eitan Isaacson -@license: LGPL - -http://ldtp.freedesktop.org - -This file may be distributed and/or modified under the terms of the GNU Lesser General -Public License version 2 as published by the Free Software Foundation. This file -is distributed without any warranty; without even the implied warranty of -merchantability or fitness for a particular purpose. - -See "COPYING" in the source distribution for more information. - -Headers in this file shall remain intact. -''' - -import pyatspi, sys -try: - from gi.repository import GObject as gobject -except: - import gobject - -class SequenceStep(gobject.GObject): - ''' - Base class for all sequence steps in a Macaroon sequence. Emits a "done" - signal when the step is done. - - @cvar delta_time: Time, in milliseconds, before this step should be executed. - @type delta_time: integer - - @ivar done: True if step is done. - @type done: boolean - ''' - __gsignals__ = {'done' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ())} - delta_time = 0 - def __init__(self): - ''' - Initialize L{SequenceStep}. - ''' - try: - super(SequenceStep, self).__init__() - except: - self.__gobject_init__() - self.done = False - - def stepDone(self): - ''' - Puts instance in "done" state. And emits "done" signal. - - @return: Return False because this is usually called through a - gobject timeout. - @rtype: boolean - ''' - if not self.done: - self.done = True - self.emit('done') - return False - -class AtomicAction(SequenceStep): - ''' - A L{SequenceStep} that performs a certain action. - - @ivar _func: Function to call. - @type _func: callable - @ivar _args: Arguments to give the function. - @type _args: list - @ivar _kwargs: Key word arguments for function. - @type _kwargs: dictionary - ''' - def __init__(self, delta_time, func, *args, **kwargs): - ''' - Initialize L{AtomicAction}. - - @param delta_time: Time, in milliseconds, before this step should be - executed. - @type delta_time: integer - @param func: Function to call. - @type func: callable - @param _args: Arguments to give the function. - @type _args: list - @param _kwargs: Key word arguments for function. - @type _kwargs: dictionary - ''' - SequenceStep.__init__(self) - self.delta_time = delta_time - self._func = func - self._args = args - self._kwargs = kwargs - - def __call__(self): - ''' - Perform the given function. - ''' - self._func(*self._args, **self._kwargs) - self.stepDone() - -class DebugAction(AtomicAction): - ''' - An action that prints a debug message to standard output. - ''' - def __init__(self, debug_msg, delta_time=0): - ''' - Initialize L{DebugAction} - - @param debug_msg: Message to print out to standard output. - @type debug_msg: string - @param delta_time: Time to wait before printing the message. - @type delta_time: integer - ''' - self._debug_msg = debug_msg - AtomicAction.__init__(self, 0, self._printDebugMsg) - - def _printDebugMsg(self): - ''' - Print the debug message. - ''' - print(self._debug_msg) - - def __str__(self): - ''' - String representation of instance. - - @return: String representation of instance. - @rtype: string - ''' - return 'Debug message: %s' % self._debug_msg - -class PauseAction(AtomicAction): - ''' - A simple pause for a given amount of time. - ''' - def __init__(self, delta_time): - ''' - Initialize a L{PauseAction}. - - @param delta_time: Duration of pause in milliseconds. - @type delta_time: integer - ''' - AtomicAction.__init__(self, delta_time, lambda args, kwargs: None, [], {}) - - def __str__(self): - ''' - String representation of instance. - - @return: String representation of instance. - @rtype: string - ''' - return 'Pause for %d milliseconds' % self.delta_time - - -class CallableAction(AtomicAction): - ''' - A simplified L{AtomicAction} that makes it easy for simply calling a function. - ''' - def __init__(self, func, *args, **kwargs): - ''' - Initialize L{AtomicAction}. - - @param delta_time: Time, in milliseconds, before this step should be - executed. - @type delta_time: integer - @param func: Function to call. - @type func: callable - @param _args: Arguments to give the function. - @type _args: list - @param _kwargs: Key word arguments for function. - @type _kwargs: dictionary - ''' - AtomicAction.__init__(self, 0, func, *args, **kwargs) diff --git a/linux/ibus-keyman/test/test_ibus_keyman.py b/linux/ibus-keyman/test/test_ibus_keyman.py deleted file mode 100644 index 1f2798ba89..0000000000 --- a/linux/ibus-keyman/test/test_ibus_keyman.py +++ /dev/null @@ -1,296 +0,0 @@ -#!/usr/bin/python3 - -import gi -import logging -import os -import sys -import time -from threading import Timer - -from keypress_actions import KeyPressAction, KeyReleaseAction - -try: - from xkbgroup import XKeyboard - has_xkbgroup = True -except: - has_xkbgroup = False - -gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk, Gio -gi.require_version('IBus', '1.0') -from gi.repository import IBus - -from change_keyboard import change_keyboard, bus_has_engine, get_current_engine - -class TestView(Gtk.Window): - - def __init__(self, test_name): - Gtk.Window.__init__(self, title=test_name) - self.known_modifiers = { - "LCTRL" : 37, - "RCTRL" : 105, - "LALT" : 64, - "RALT" : 108, - "SHIFT" : 50, - "ALT" : 64, - "CTRL" : 37 } - - self.known_keys = { - "K_A" : 38, - "K_B" : 56, - "K_C" : 54, - "K_D" : 40, - "K_E" : 26, - "K_F" : 41, - "K_G" : 42, - "K_H" : 43, - "K_I" : 31, - "K_J" : 44, - "K_K" : 45, - "K_L" : 46, - "K_M" : 58, - "K_N" : 57, - "K_O" : 32, - "K_P" : 33, - "K_Q" : 24, - "K_R" : 27, - "K_S" : 39, - "K_T" : 28, - "K_U" : 30, - "K_V" : 55, - "K_W" : 25, - "K_X" : 53, - "K_Y" : 29, - "K_Z" : 52, - "K_COLON" : 47, # // &HBA - "K_EQUAL" : 21, # // &HBB - "K_COMMA" : 59, # // &HBC - "K_HYPHEN" : 20, # // &HBD - "K_PERIOD" : 60, # // &HBE - "K_SLASH" : 61, # // &HBF - "K_BKQUOTE" : 49, # // &HC0 - "K_LBRKT" : 34, # // &HDB - "K_BKSLASH" : 51, # // &HDC - "K_RBRKT" : 35, # // &HDD - "K_QUOTE" : 48, # // &HDE - "K_oE2" : 94, # // &HE2 - "K_0" : 19, - "K_1" : 10, - "K_2" : 11, - "K_3" : 12, - "K_4" : 13, - "K_5" : 14, - "K_6" : 15, - "K_7" : 16, - "K_8" : 17, - "K_9" : 18, - "K_F1" : 67, - "K_F2" : 68, - "K_F3" : 69, - "K_F4" : 70, - "K_F5" : 71, - "K_F6" : 72, - "K_F7" : 73, - "K_F8" : 74, - "K_F9" : 75, - "K_F10" : 76, - "K_F11" : 95, - "K_F12" : 96, - "K_CAPS" : 66, - "K_ENTER" : 36, - "K_ESC" : 9, - "K_SPACE" : 65, - "K_TAB" : 23, - "K_UP" : 111, - "K_DOWN" : 116, - "K_LEFT" : 113, - "K_RIGHT" : 114, - "K_HOME" : 110, - "K_END" : 115, - "K_BKSP" : 22 } - self.keys = self.context = self.expected = "" - self.haspressedkeys = False - self.test_name = test_name - home = os.path.expanduser("~") - keyboarddir = os.path.join(home, ".local/share/keyman/test_kmx") - self.kmx_path = os.path.join(keyboarddir, self.test_name + ".kmx") - self.kmn_path = os.path.join(keyboarddir, self.test_name + ".kmn") - self.keyboard_id = "und:" + keyboarddir + "/" + self.test_name + ".kmx" - - self.load_source(self.kmn_path) - self.change_to_keyboard(self.keyboard_id, self.kmx_path) - with open(self.test_name+".in", "wt") as f: - f.write(self.expected) - - self.grid = Gtk.Grid() - self.add(self.grid) - self.create_textview() - - logging.info("keys %d:%s:" % (len(self.keys), self.keys)) - logging.info("context %d:%s:" % (len(self.context), self.context)) - logging.info("expected %d:%s:" % (len(self.expected), self.expected)) - - def on_focus_in(self, args, data): - t = Timer(1.0, self.do_keypresses) - t.start() - - def do_keypresses(self): - if self.keys and not self.haspressedkeys: - self.haspressedkeys = True - if has_xkbgroup: - with XKeyboard() as xkb: - logging.info("xkb %d:%s:%s", xkb.group_num, xkb.group_symbol, xkb.group_name) - keys = self.keys.split("]") - for key in keys: - if (key): - key = key[1:] - keyparts = key.split(" ") - mods = [] - for part in keyparts: - if part in self.known_keys: - mainkey = part - elif part in self.known_modifiers: - mods.append(part) - logging.info("key is %s with modifiers %s" % (mainkey, mods)) - keyval = self.known_keys[mainkey] - logging.info("%s", keyval) - for modifier in mods: - modkeypress = KeyPressAction(self.known_modifiers[modifier]) - modkeypress._keyPress(self.known_modifiers[modifier]) - - mainkeypress = KeyPressAction(keyval) - mainkeyrelease = KeyReleaseAction(keyval) - mainkeypress._keyPress(keyval) - mainkeyrelease._keyRelease(keyval) - - for modifier in mods: - modkeypress = KeyReleaseAction(self.known_modifiers[modifier]) - modkeypress._keyRelease(self.known_modifiers[modifier]) - - time.sleep(0.05) - - t = Timer(1.0, self.do_destroy) - t.start() - - def change_to_keyboard(self, keyboard_id, kmx_path): - logging.debug(keyboard_id) - try: - logging.debug("getting bus") - bus = IBus.Bus() - logging.debug("getting default keyboard") - self.default_keyboard = get_current_engine(bus) - logging.debug(self.default_keyboard) - logging.debug("installing to ibus") - ibus_settings = Gio.Settings.new("org.freedesktop.ibus.general") - preload_engines = ibus_settings.get_strv("preload-engines") - logging.debug(preload_engines) - # if bad_keyboard in preload_engines: - # preload_engines.remove(bad_keyboard) - if keyboard_id not in preload_engines: - preload_engines.append(keyboard_id) - logging.debug(preload_engines) - ibus_settings.set_strv("preload-engines", preload_engines) - bus.preload_engines(preload_engines) - logging.info("changing keyboard to %s", keyboard_id) - change_keyboard(bus, keyboard_id) - except Exception as e: - logging.debug("Failed to set up keyboard") - logging.debug(e) - - def reset_keyboard(self, keyboard_id): - try: - logging.debug("getting bus") - bus = IBus.Bus() - logging.debug("setting keyboard back to default: %s", self.default_keyboard) - change_keyboard(bus, self.default_keyboard) - ibus_settings = Gio.Settings.new("org.freedesktop.ibus.general") - preload_engines = ibus_settings.get_strv("preload-engines") - logging.debug(preload_engines) - if keyboard_id in preload_engines: - preload_engines.remove(keyboard_id) - logging.debug(preload_engines) - ibus_settings.set_strv("preload-engines", preload_engines) - bus.preload_engines(preload_engines) - except Exception as e: - logging.debug("Failed to reset keyboard") - logging.debug(e) - - - def load_source(self, kmn_path): - if os.path.exists(kmn_path): - with open(kmn_path) as kmn: - for line in kmn: - if line.startswith("c keys: "): - self.keys = line[8:].rstrip() - if line.startswith("c expected: "): - expected = line[12:].rstrip() - if expected == "\\b": # beep test is sound not text - self.expected = "" - else: - self.expected = expected.encode("utf-8").decode('unicode-escape') - if line.startswith("c context: "): - self.context = line[11:].rstrip() - - def create_textview(self): - scrolledwindow = Gtk.ScrolledWindow() - scrolledwindow.set_hexpand(True) - scrolledwindow.set_vexpand(True) - self.grid.attach(scrolledwindow, 0, 1, 3, 1) - - self.textview = Gtk.TextView() - self.textview.connect("focus-in-event", self.on_focus_in) - self.textbuffer = self.textview.get_buffer() - self.textbuffer.set_text(self.context) - scrolledwindow.add(self.textview) - - def do_destroy(self): - with open(self.test_name+".out", "wt") as f: - start = self.textbuffer.get_start_iter() - end = self.textbuffer.get_end_iter() - text = self.textbuffer.get_text(start, end, True) - logging.info("text buffer:%s", text) - f.write(text) - self.reset_keyboard(self.keyboard_id) - Gtk.main_quit() - - def on_destroy(self, args): - self.do_destroy() - -# Test view contains a single multiline edit - -# input test_name -# test dir ~/.local/share/keyman/test_kmx -# read test_name.kmn to get e.g. -#c keys: [K_1][K_BKSP][K_2][K_BKSP][K_3][K_BKSP][K_4][K_BKSP][K_5][K_BKSP][K_6][K_BKSP] -#c expected: aa -#c context: - -# keyboard is und:~/.local/share/keyman/test_kmx/test_name.kmx -# can the current ibus keyboard be changed from a python program? - -# set initial text to "context" -# parse "keys" and input them -# can I generate keypresses in python? -# read output text and compare to "expected" - - -def main(argv): - if len(sys.argv) != 2: - logging.error("Too many arguments: %s", sys.argv) - logging.error("test_ibus_keyman.py ") - sys.exit(2) - if len(sys.argv[1]) == 0: - logging.error("Empty test name: %s", sys.argv) - logging.error("test_ibus_keyman.py ") - sys.exit(2) - logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s') - # logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(message)s') - w = TestView(sys.argv[1]) - w.connect("destroy", w.on_destroy) - w.resize(576, 324) - w.show_all() - Gtk.main() - -if __name__ == "__main__": - main(sys.argv[1:]) \ No newline at end of file diff --git a/linux/ibus-keyman/tests/.gitignore b/linux/ibus-keyman/tests/.gitignore new file mode 100644 index 0000000000..6d154ea019 --- /dev/null +++ b/linux/ibus-keyman/tests/.gitignore @@ -0,0 +1,2 @@ +.libs/ +ibus-keyman-tests diff --git a/linux/ibus-keyman/tests/Makefile.am b/linux/ibus-keyman/tests/Makefile.am new file mode 100644 index 0000000000..3be1e93763 --- /dev/null +++ b/linux/ibus-keyman/tests/Makefile.am @@ -0,0 +1,129 @@ +# vim:set noet ts=4: +# +# ibus-keyman unit tests +# +# Copyright (c) 2021 SIL International +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +include $(top_srcdir)/glib-tap.mk + +AUTOMAKE_OPTIONS = subdir-objects + +AM_CFLAGS = \ + @IBUS_CFLAGS@ \ + @GTK_CFLAGS@ \ + @JSON_GLIB_CFLAGS@ \ + @X11_CFLAGS@ \ + @KEYMAN_PROC_CFLAGS@ \ + -DPKGDATADIR=\"$(pkgdatadir)\" \ + $(NULL) + +AM_LDFLAGS = \ + @IBUS_LIBS@ \ + @GTK_LIBS@ \ + @X11_LIBS@ \ + @JSON_GLIB_LIBS@ \ + @KEYMAN_PROC_LIBS@ \ + $(NULL) + +libexec_PROGRAMS = ibus-keyman-tests + +uninstalled_test_scripts = run-tests.sh + +# Note: don't use $(top_srcdir) here for the first `..` - automake <= 1.15 (Ubuntu 18.04) +# has a bug where it doesn't resolve the variable and so stores some files in the wrong +# location and can't find them later. https://debbugs.gnu.org/cgi/bugreport.cgi?bug=13928 +# Once we drop support for Ubuntu 18.04 Bionic we change this. + +ibus_keyman_tests_SOURCES = \ + ibusimcontext.c \ + ibusimcontext.h \ + testfixture.cpp \ + testmodule.c \ + testmodule.h \ + ../../../common/core/desktop/src/json.cpp \ + ../../../common/core/desktop/src/keyboard.cpp \ + ../../../common/core/desktop/src/kmx/kmx_actions.cpp \ + ../../../common/core/desktop/src/kmx/kmx_actions.h \ + ../../../common/core/desktop/src/kmx/kmx_base.h \ + ../../../common/core/desktop/src/kmx/kmx_capslock.cpp \ + ../../../common/core/desktop/src/kmx/kmx_consts.cpp \ + ../../../common/core/desktop/src/kmx/kmx_context.cpp \ + ../../../common/core/desktop/src/kmx/kmx_context.h \ + ../../../common/core/desktop/src/kmx/kmx_conversion.cpp \ + ../../../common/core/desktop/src/kmx/kmx_debug.cpp \ + ../../../common/core/desktop/src/kmx/kmx_debugger.cpp \ + ../../../common/core/desktop/src/kmx/kmx_debugger.h \ + ../../../common/core/desktop/src/kmx/kmx_environment.cpp \ + ../../../common/core/desktop/src/kmx/kmx_environment.h \ + ../../../common/core/desktop/src/kmx/kmx_file.cpp \ + ../../../common/core/desktop/src/kmx/kmx_file.h \ + ../../../common/core/desktop/src/kmx/kmx_modifiers.cpp \ + ../../../common/core/desktop/src/kmx/kmx_options.cpp \ + ../../../common/core/desktop/src/kmx/kmx_options.h \ + ../../../common/core/desktop/src/kmx/kmx_processevent.cpp \ + ../../../common/core/desktop/src/kmx/kmx_processevent.h \ + ../../../common/core/desktop/src/kmx/kmx_processor.cpp \ + ../../../common/core/desktop/src/kmx/kmx_processor.hpp \ + ../../../common/core/desktop/src/kmx/kmx_xstring.cpp \ + ../../../common/core/desktop/src/kmx/kmx_xstring.h \ + ../../../common/core/desktop/src/option.cpp \ + ../../../common/core/desktop/src/state.cpp \ + ../../../common/core/desktop/src/utfcodec.cpp \ + ../../../common/core/desktop/tests/unit/kmx/kmx_test_source.cpp \ + ../../../common/core/desktop/tests/unit/kmx/kmx_test_source.hpp \ + ../src/keycodes.h \ + ../src/keymanutil.c \ + ../src/keymanutil.h \ + ../src/kmpdetails.c \ + ../src/kmpdetails.h \ + $(NULL) + +ibus_keyman_tests_CFLAGS = \ + @IBUS_CFLAGS@ \ + @GTK_CFLAGS@ \ + @X11_CFLAGS@ \ + @JSON_GLIB_CFLAGS@ \ + @KEYMAN_PROC_CFLAGS@ \ + -DPKGDATADIR=\"$(pkgdatadir)\" \ + -I$(top_srcdir)/../../common/core/desktop/src \ + -I$(top_srcdir)/../../common/core/desktop/src/kmx \ + -I$(top_srcdir)/../../common/core/desktop/tests/unit/kmx \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src \ + $(NULL) + +ibus_keyman_tests_CPPFLAGS = \ + @IBUS_CFLAGS@ \ + @GTK_CFLAGS@ \ + @X11_CFLAGS@ \ + @JSON_GLIB_CFLAGS@ \ + @KEYMAN_PROC_CFLAGS@ \ + -DPKGDATADIR=\"$(pkgdatadir)\" \ + -I$(top_srcdir)/../../common/core/desktop/src \ + -I$(top_srcdir)/../../common/core/desktop/src/kmx \ + -I$(top_srcdir)/../../common/core/desktop/tests/unit/kmx \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src \ + $(NULL) + +ibus_keyman_tests_LDFLAGS = \ + @GTK_LIBS@ \ + @IBUS_LIBS@ \ + @X11_LIBS@ \ + @JSON_GLIB_LIBS@ \ + @KEYMAN_PROC_LIBS@ \ + $(NULL) diff --git a/linux/ibus-keyman/tests/README.md b/linux/ibus-keyman/tests/README.md new file mode 100644 index 0000000000..caf2205032 --- /dev/null +++ b/linux/ibus-keyman/tests/README.md @@ -0,0 +1,26 @@ +# ibus-keyman integration tests + +The ibus-keyman integration tests run the tests defined in `common/core/desktop/tests/unit/kmx`. +The tests run in a separate Xephyr session. Any dconf settings that have to be set during the tests +are stored in `~/.config/glib-2.0/settings/keyfile`. + +## Running tests + +The tests get run as part of building `ibus-keyman`, more specifically when running `make check`. + +### Run all tests + +All tests can be run with the test script: + +```bash +./run-tests.sh +``` + +### Run specific tests + +To run a single test you pass the testname (as found in +`common/core/desktop/build/arch/*/tests/unit/kmx`). Multiple tests should be separated by space. + +```bash +./run-tests.sh -- k_000___null_keyboard k_005___nul_with_initial_context +``` diff --git a/linux/ibus-keyman/tests/ibusimcontext.c b/linux/ibus-keyman/tests/ibusimcontext.c new file mode 100644 index 0000000000..89d3a3af60 --- /dev/null +++ b/linux/ibus-keyman/tests/ibusimcontext.c @@ -0,0 +1,1090 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* vim:set et sts=4: */ +/* ibus - The Input Bus + * Copyright (C) 2008-2013 Peng Huang + * Copyright (C) 2015-2021 Takao Fujiwara + * Copyright (C) 2008-2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +// This file is based on https://github.com/ibus/ibus/blob/master/client/gtk2/ibusimcontext.c + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "ibusimcontext.h" +#include +#include +#include +#include + +#ifdef GDK_WINDOWING_WAYLAND +#include +#endif + +#ifdef DEBUG +#define IDEBUG g_debug +#else +#define IDEBUG(a...) +#endif + +#define MAX_QUEUED_EVENTS 20 + +struct _IBusIMContext { + GtkIMContext parent; + + /* instance members */ + GdkWindow *client_window; + + IBusInputContext *ibuscontext; + + GdkRectangle cursor_area; + gboolean has_focus; + + guint32 time; + gint caps; + + /* cancellable */ + GCancellable *cancellable; + GQueue *events_queue; + + gboolean use_button_press_event; + + GMainLoop *thread_loop; + + // test properties + GString *text; +}; + +struct _IBusIMContextClass { + GtkIMContextClass parent; + /* class members */ +}; + +typedef struct _timeout_data { + GMainLoop *thread_loop; + gboolean timed_out; +} timeout_data; + +static GtkIMContext *_focus_im_context = NULL; +static IBusInputContext *_fake_context = NULL; +static GdkWindow *_input_window = NULL; +static GtkWidget *_input_widget = NULL; + +/* functions prototype */ +static void ibus_im_context_class_init(IBusIMContextClass *class); +static void ibus_im_context_class_fini(IBusIMContextClass *class); +static void ibus_im_context_init(GObject *obj); +static void ibus_im_context_notify(GObject *obj, GParamSpec *pspec); +static void ibus_im_context_finalize(GObject *obj); +static void ibus_im_context_reset(GtkIMContext *context); +static gboolean ibus_im_context_filter_keypress(GtkIMContext *context, GdkEventKey *key); +static void ibus_im_context_focus_in(GtkIMContext *context); +static void ibus_im_context_focus_out(GtkIMContext *context); +static void ibus_im_context_set_client_window(GtkIMContext *context, GdkWindow *client); +static void ibus_im_context_set_cursor_location(GtkIMContext *context, GdkRectangle *area); +static void ibus_im_context_set_surrounding(GtkIMContext *context, const gchar *text, int len, int cursor_index); +static void ibus_im_context_set_surrounding_with_selection( + GtkIMContext *context, + const gchar *text, + int len, + int cursor_index, + int anchor_index); + +/* static methods*/ +static void _create_input_context(IBusIMContext *context); +static gboolean _set_cursor_location_internal(IBusIMContext *context); + +static void _bus_connected_cb(IBusBus *bus, IBusIMContext *context); +static void _request_surrounding_text(IBusIMContext *context); +static gboolean _set_content_type(IBusIMContext *context); +static void _commit_text(IBusIMContext *context, const gchar *text); +static gboolean _retrieve_surrounding(IBusIMContext *context); +static gboolean _delete_surrounding(IBusIMContext *context, gint offset_from_cursor, guint nchars); +static gboolean _timeout_callback(gpointer *user_data); +static void _run_main_loop_with_timeout(IBusIMContext *ibusimcontext); + +static GType _ibus_type_im_context = 0; +static GtkIMContextClass *parent_class = NULL; + +static IBusBus *_bus = NULL; +static guint _daemon_name_watch_id = 0; +static gboolean _daemon_is_running = FALSE; + +void +ibus_im_context_register_type(GTypeModule *type_module) { + IDEBUG("%s", __FUNCTION__); + + static const GTypeInfo ibus_im_context_info = { + sizeof(IBusIMContextClass), + (GBaseInitFunc)NULL, + (GBaseFinalizeFunc)NULL, + (GClassInitFunc)ibus_im_context_class_init, + (GClassFinalizeFunc)ibus_im_context_class_fini, + NULL, /* class data */ + sizeof(IBusIMContext), + 0, + (GInstanceInitFunc)ibus_im_context_init, + }; + + if (!_ibus_type_im_context) { + if (type_module) { + _ibus_type_im_context = + g_type_module_register_type(type_module, GTK_TYPE_IM_CONTEXT, "IBusIMContext", &ibus_im_context_info, (GTypeFlags)0); + } else { + _ibus_type_im_context = g_type_register_static(GTK_TYPE_IM_CONTEXT, "IBusIMContext", &ibus_im_context_info, (GTypeFlags)0); + } + } +} + +GType +ibus_im_context_get_type(void) { + IDEBUG("%s", __FUNCTION__); + + if (_ibus_type_im_context == 0) { + ibus_im_context_register_type(NULL); + } + + g_assert(_ibus_type_im_context != 0); + return _ibus_type_im_context; +} + +IBusIMContext * +ibus_im_context_new(void) { + IDEBUG("%s", __FUNCTION__); + + GObject *obj = g_object_new(IBUS_TYPE_IM_CONTEXT, NULL); + return IBUS_IM_CONTEXT(obj); +} + +static gboolean +_focus_in_cb(GtkWidget *widget, GdkEventFocus *event, gpointer user_data) { + if (_focus_im_context == NULL && _fake_context != NULL) { + ibus_input_context_focus_in(_fake_context); + } + return FALSE; +} + +static gboolean +_focus_out_cb(GtkWidget *widget, GdkEventFocus *event, gpointer user_data) { + if (_focus_im_context == NULL && _fake_context != NULL) { + ibus_input_context_focus_out(_fake_context); + } + return FALSE; +} + +static gboolean +ibus_im_context_commit_event(IBusIMContext *ibusimcontext, GdkEventKey *event) { + IDEBUG("%s", __FUNCTION__); + guint keyval = 0; + GdkModifierType state = 0; + int i; + GdkModifierType no_text_input_mask; + gunichar ch; + + if (event->type == GDK_KEY_RELEASE) + return FALSE; + keyval = event->keyval; + state = event->state; + + /* Ignore modifier key presses */ + for (i = 0; i < G_N_ELEMENTS(IBUS_COMPOSE_IGNORE_KEYLIST); i++) + if (keyval == IBUS_COMPOSE_IGNORE_KEYLIST[i]) + return FALSE; + no_text_input_mask = + gdk_keymap_get_modifier_mask(gdk_keymap_get_for_display(gdk_display_get_default()), GDK_MODIFIER_INTENT_NO_TEXT_INPUT); + if (state & no_text_input_mask || keyval == GDK_KEY_Return || keyval == GDK_KEY_ISO_Enter || keyval == GDK_KEY_KP_Enter) { + return FALSE; + } + ch = ibus_keyval_to_unicode(keyval); + if (ch != 0 && !g_unichar_iscntrl(ch)) { + IBusText *text = ibus_text_new_from_unichar(ch); + IDEBUG("%s: text=%p", __FUNCTION__, text); + _request_surrounding_text(ibusimcontext); + _commit_text(ibusimcontext, text->text); + return TRUE; + } + return FALSE; +} + +struct _ProcessKeyEventData { + GdkEvent *event; + IBusIMContext *ibusimcontext; +}; + +typedef struct _ProcessKeyEventData ProcessKeyEventData; + +static void +_process_key_event_done(GObject *object, GAsyncResult *res, gpointer user_data) { + IBusInputContext *context = (IBusInputContext *)object; + + ProcessKeyEventData *data = (ProcessKeyEventData *)user_data; + GdkEvent *event = data->event; + GError *error = NULL; + + g_slice_free(ProcessKeyEventData, data); + gboolean retval = ibus_input_context_process_key_event_async_finish(context, res, &error); + + if (error != NULL) { + g_warning("Process Key Event failed: %s.", error->message); + g_error_free(error); + } + + if (retval == FALSE) { + ((GdkEventKey *)event)->state |= IBUS_IGNORED_MASK; + gdk_event_put(event); + } + gdk_event_free(event); +} + +static gboolean +_process_key_event(IBusInputContext *context, GdkEventKey *event, IBusIMContext *ibusimcontext) { + guint state; + guint keyval = 0; + guint16 hardware_keycode = 0; + guint keycode = 0; + gboolean retval = FALSE; + + state = event->state; + if (event->type == GDK_KEY_RELEASE) + state |= IBUS_RELEASE_MASK; + keyval = event->keyval; + hardware_keycode = event->hardware_keycode; + keycode = hardware_keycode; + + ProcessKeyEventData *data = g_slice_new0(ProcessKeyEventData); + data->event = gdk_event_copy((GdkEvent *)event); + data->ibusimcontext = ibusimcontext; + ibus_input_context_process_key_event_async(context, keyval, keycode, state, -1, NULL, _process_key_event_done, data); + + retval = TRUE; + + if (retval) + event->state |= IBUS_HANDLED_MASK; + else + event->state |= IBUS_IGNORED_MASK; + + return retval; +} + +/* emit "retrieve-surrounding" glib signal of GtkIMContext, if + * context->caps has IBUS_CAP_SURROUNDING_TEXT and the current IBus + * engine needs surrounding-text. + */ +static void +_request_surrounding_text(IBusIMContext *context) { + if (context && (context->caps & IBUS_CAP_SURROUNDING_TEXT) != 0 && context->ibuscontext != NULL && + ibus_input_context_needs_surrounding_text(context->ibuscontext)) { + _retrieve_surrounding(context); + } else { + // g_debug("%s has no capability of surrounding-text feature", g_get_prgname()); + } +} + +static gboolean +_set_content_type(IBusIMContext *context) { + if (context->ibuscontext != NULL) { + GtkInputPurpose purpose; + GtkInputHints hints; + + g_object_get(G_OBJECT(context), "input-purpose", &purpose, "input-hints", &hints, NULL); + + ibus_input_context_set_content_type(context->ibuscontext, purpose, hints); + } + return TRUE; +} + +static gboolean +_get_boolean_env(const gchar *name, gboolean defval) { + const gchar *value = g_getenv(name); + + if (value == NULL) + return defval; + + if (g_strcmp0(value, "") == 0 || g_strcmp0(value, "0") == 0 || g_strcmp0(value, "false") == 0 || + g_strcmp0(value, "False") == 0 || g_strcmp0(value, "FALSE") == 0) + return FALSE; + + return TRUE; +} + +static void +daemon_name_appeared(GDBusConnection *connection, const gchar *name, const gchar *owner, gpointer data) { + if (!g_strcmp0(ibus_bus_get_service_name(_bus), IBUS_SERVICE_PORTAL)) { + _daemon_is_running = TRUE; + return; + } + /* If ibus-daemon is running and run ssh -X localhost, + * daemon_name_appeared() is called but ibus_get_address() == NULL + * because the hostname and display number are different between + * ibus-daemon and clients. So IBusBus would not be connected and + * ibusimcontext->ibuscontext == NULL and ibusimcontext->events_queue + * could go beyond MAX_QUEUED_EVENTS . */ + _daemon_is_running = (ibus_get_address() != NULL); +} + +static void +daemon_name_vanished(GDBusConnection *connection, const gchar *name, gpointer data) { + _daemon_is_running = FALSE; +} + +static void +ibus_im_context_class_init(IBusIMContextClass *class) { + IDEBUG("%s", __FUNCTION__); + + GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS(class); + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + + parent_class = (GtkIMContextClass *)g_type_class_peek_parent(class); + + im_context_class->reset = ibus_im_context_reset; + im_context_class->focus_in = ibus_im_context_focus_in; + im_context_class->focus_out = ibus_im_context_focus_out; + im_context_class->filter_keypress = ibus_im_context_filter_keypress; + im_context_class->set_client_window = ibus_im_context_set_client_window; + im_context_class->set_cursor_location = ibus_im_context_set_cursor_location; + im_context_class->set_surrounding = ibus_im_context_set_surrounding; + gobject_class->notify = ibus_im_context_notify; + gobject_class->finalize = ibus_im_context_finalize; + + /* init bus object */ + if (_bus == NULL) { + _bus = ibus_bus_new_async_client(); + + g_signal_connect(_bus, "connected", G_CALLBACK(_bus_connected_cb), NULL); + } + + _daemon_name_watch_id = g_bus_watch_name( + G_BUS_TYPE_SESSION, ibus_bus_get_service_name(_bus), G_BUS_NAME_WATCHER_FLAGS_NONE, daemon_name_appeared, + daemon_name_vanished, NULL, NULL); +} + +static void +ibus_im_context_class_fini(IBusIMContextClass *class) { + g_bus_unwatch_name(_daemon_name_watch_id); +} + +/* Copied from gtk+2.0-2.20.1/modules/input/imcedilla.c to fix crosbug.com/11421. + * Overwrite the original Gtk+'s compose table in gtk+-2.x.y/gtk/gtkimcontextsimple.c. */ + +/* The difference between this and the default input method is the handling + * of C+acute - this method produces C WITH CEDILLA rather than C WITH ACUTE. + * For languages that use CCedilla and not acute, this is the preferred mapping, + * and is particularly important for pt_BR, where the us-intl keyboard is + * used extensively. + */ +static guint16 cedilla_compose_seqs[] = { + GDK_KEY_dead_acute, GDK_KEY_C, 0, 0, 0, 0x00C7, /* LATIN_CAPITAL_LETTER_C_WITH_CEDILLA */ + GDK_KEY_dead_acute, GDK_KEY_c, 0, 0, 0, 0x00E7, /* LATIN_SMALL_LETTER_C_WITH_CEDILLA */ + GDK_KEY_Multi_key, GDK_KEY_apostrophe, GDK_KEY_C, 0, 0, 0x00C7, /* LATIN_CAPITAL_LETTER_C_WITH_CEDILLA */ + GDK_KEY_Multi_key, GDK_KEY_apostrophe, GDK_KEY_c, 0, 0, 0x00E7, /* LATIN_SMALL_LETTER_C_WITH_CEDILLA */ + GDK_KEY_Multi_key, GDK_KEY_C, GDK_KEY_apostrophe, 0, 0, 0x00C7, /* LATIN_CAPITAL_LETTER_C_WITH_CEDILLA */ + GDK_KEY_Multi_key, GDK_KEY_c, GDK_KEY_apostrophe, 0, 0, 0x00E7, /* LATIN_SMALL_LETTER_C_WITH_CEDILLA */ +}; + +static void +ibus_im_context_init(GObject *obj) { + IDEBUG("%s", __FUNCTION__); + + IBusIMContext *ibusimcontext = IBUS_IM_CONTEXT(obj); + + ibusimcontext->client_window = NULL; + + // Init cursor area + ibusimcontext->cursor_area.x = -1; + ibusimcontext->cursor_area.y = -1; + ibusimcontext->cursor_area.width = 0; + ibusimcontext->cursor_area.height = 0; + + ibusimcontext->ibuscontext = NULL; + ibusimcontext->has_focus = FALSE; + ibusimcontext->time = GDK_CURRENT_TIME; + ibusimcontext->caps = IBUS_CAP_FOCUS | IBUS_CAP_SURROUNDING_TEXT | IBUS_CAP_AUXILIARY_TEXT | IBUS_CAP_PROPERTY; + + ibusimcontext->events_queue = g_queue_new(); + + if (ibus_bus_is_connected(_bus)) { + _create_input_context(ibusimcontext); + } + + g_signal_connect(_bus, "connected", G_CALLBACK(_bus_connected_cb), obj); + + _daemon_is_running = (ibus_get_address() != NULL); +} + +static void +ibus_im_context_notify(GObject *obj, GParamSpec *pspec) { + IDEBUG("%s", __FUNCTION__); + + if (g_strcmp0(pspec->name, "input-purpose") == 0 || g_strcmp0(pspec->name, "input-hints") == 0) { + _set_content_type(IBUS_IM_CONTEXT(obj)); + } +} + +static void +ibus_im_context_finalize(GObject *obj) { + IDEBUG("%s", __FUNCTION__); + + IBusIMContext *ibusimcontext = IBUS_IM_CONTEXT(obj); + + g_signal_handlers_disconnect_by_func(_bus, G_CALLBACK(_bus_connected_cb), obj); + + if (ibusimcontext->cancellable != NULL) { + /* Cancel any ongoing create input context request */ + g_cancellable_cancel(ibusimcontext->cancellable); + g_object_unref(ibusimcontext->cancellable); + ibusimcontext->cancellable = NULL; + } + + if (ibusimcontext->ibuscontext) { + ibus_proxy_destroy((IBusProxy *)ibusimcontext->ibuscontext); + } + + ibus_im_context_set_client_window((GtkIMContext *)ibusimcontext, NULL); + + g_queue_free_full(ibusimcontext->events_queue, (GDestroyNotify)gdk_event_free); + + G_OBJECT_CLASS(parent_class)->finalize(obj); +} + +static gboolean +ibus_im_context_filter_keypress(GtkIMContext *context, GdkEventKey *event) { + IDEBUG("%s", __FUNCTION__); + + IBusIMContext *ibusimcontext = IBUS_IM_CONTEXT(context); + + if (event->state & IBUS_HANDLED_MASK) + return TRUE; + + /* Do not call gtk_im_context_filter_keypress() because + * gtk_im_context_simple_filter_keypress() binds Ctrl-Shift-u + */ + if (event->state & IBUS_IGNORED_MASK) + return ibus_im_context_commit_event(ibusimcontext, event); + + /* XXX it is a workaround for some applications do not set client + * window. */ + if (ibusimcontext->client_window == NULL && event->window != NULL) + gtk_im_context_set_client_window((GtkIMContext *)ibusimcontext, event->window); + + _request_surrounding_text(ibusimcontext); + + ibusimcontext->time = event->time; + + if (ibusimcontext->ibuscontext) { + gboolean result = _process_key_event(ibusimcontext->ibuscontext, event, ibusimcontext); + if (result) { + _run_main_loop_with_timeout(ibusimcontext); + } + return result; + } + + /* At this point we _should_ be waiting for the IBus context to be + * created or the connection to IBus to be established. If that's + * the case we queue events to be processed when the IBus context + * is ready. */ + g_return_val_if_fail(ibusimcontext->cancellable != NULL || ibus_bus_is_connected(_bus) == FALSE, FALSE); + g_queue_push_tail(ibusimcontext->events_queue, gdk_event_copy((GdkEvent *)event)); + + if (g_queue_get_length(ibusimcontext->events_queue) > MAX_QUEUED_EVENTS) { + g_warning("Events queue growing too big, will start to drop."); + gdk_event_free((GdkEvent *)g_queue_pop_head(ibusimcontext->events_queue)); + } + + return TRUE; +} + +static void +ibus_im_context_focus_in(GtkIMContext *context) { + IBusIMContext *ibusimcontext = (IBusIMContext *)context; + GtkWidget *widget = NULL; + + IDEBUG("%s: context=%p", __FUNCTION__, context); + + if (ibusimcontext->has_focus) + return; + + /* don't set focus on password entry */ + if (ibusimcontext->client_window != NULL) { + gdk_window_get_user_data(ibusimcontext->client_window, (gpointer *)&widget); + } + + if (widget && GTK_IS_ENTRY(widget) && !gtk_entry_get_visibility(GTK_ENTRY(widget))) { + return; + } + /* Do not call gtk_im_context_focus_out() here. + * google-chrome's notification popup window (Pushbullet) + * takes the focus and the popup window disappears. + * So other applications lose the focus because + * ibusimcontext->has_focus is FALSE if + * gtk_im_context_focus_out() is called here when + * _focus_im_context != context. + */ + if (_focus_im_context == NULL) { + /* focus out fake context */ + if (_fake_context != NULL) { + ibus_input_context_focus_out(_fake_context); + } + } + + ibusimcontext->has_focus = TRUE; + if (ibusimcontext->ibuscontext) { + if (!_set_content_type(ibusimcontext)) { + ibusimcontext->has_focus = FALSE; + return; + } + ibus_input_context_focus_in(ibusimcontext->ibuscontext); + } + + /* set_cursor_location_internal() will get origin from X server, + * it blocks UI. So delay it to idle callback. */ + g_idle_add_full( + G_PRIORITY_DEFAULT_IDLE, (GSourceFunc)_set_cursor_location_internal, g_object_ref(ibusimcontext), + (GDestroyNotify)g_object_unref); + + /* retrieve the initial surrounding-text (regardless of whether + * the current IBus engine needs surrounding-text) */ + _request_surrounding_text(ibusimcontext); + + g_object_add_weak_pointer((GObject *)context, (gpointer *)&_focus_im_context); + _focus_im_context = context; +} + +static void +ibus_im_context_focus_out(GtkIMContext *context) { + IDEBUG("%s", __FUNCTION__); + IBusIMContext *ibusimcontext = (IBusIMContext *)context; + + if (ibusimcontext->has_focus == FALSE) { + return; + } + + if (_focus_im_context) { + g_object_remove_weak_pointer((GObject *)context, (gpointer *)&_focus_im_context); + _focus_im_context = NULL; + } + + ibusimcontext->has_focus = FALSE; + if (ibusimcontext->ibuscontext) { + ibus_input_context_focus_out(ibusimcontext->ibuscontext); + } + + /* focus in the fake ic */ + if (_fake_context != NULL) { + ibus_input_context_focus_in(_fake_context); + } +} + +static void +ibus_im_context_reset(GtkIMContext *context) { + IDEBUG("%s", __FUNCTION__); + + IBusIMContext *ibusimcontext = IBUS_IM_CONTEXT(context); + + if (ibusimcontext->ibuscontext) { + ibus_input_context_reset(ibusimcontext->ibuscontext); + } +} + +/* Use the button-press-event signal until GtkIMContext always emits the reset + * signal. + * https://gitlab.gnome.org/GNOME/gtk/merge_requests/460 + */ +static gboolean +ibus_im_context_button_press_event_cb(GtkWidget *widget, GdkEventButton *event, IBusIMContext *ibusimcontext) { + IDEBUG("%s", __FUNCTION__); + if (event->button != 1) + return FALSE; + + if (ibusimcontext->ibuscontext) { + ibus_input_context_reset(ibusimcontext->ibuscontext); + } + return FALSE; +} + +static void +_connect_button_press_event(IBusIMContext *ibusimcontext, gboolean do_connect) { + GtkWidget *widget = NULL; + + g_assert(ibusimcontext->client_window); + gdk_window_get_user_data(ibusimcontext->client_window, (gpointer *)&widget); + /* firefox needs GtkWidget instead of GtkWindow */ + if (GTK_IS_WIDGET(widget)) { + if (do_connect) { + g_signal_connect(widget, "button-press-event", G_CALLBACK(ibus_im_context_button_press_event_cb), ibusimcontext); + ibusimcontext->use_button_press_event = TRUE; + } else { + g_signal_handlers_disconnect_by_func(widget, G_CALLBACK(ibus_im_context_button_press_event_cb), ibusimcontext); + ibusimcontext->use_button_press_event = FALSE; + } + } +} + +static void +ibus_im_context_set_client_window(GtkIMContext *context, GdkWindow *client) { + IBusIMContext *ibusimcontext; + + IDEBUG("%s", __FUNCTION__); + + ibusimcontext = IBUS_IM_CONTEXT(context); + + if (ibusimcontext->client_window) { + if (ibusimcontext->use_button_press_event) + _connect_button_press_event(ibusimcontext, FALSE); + g_object_unref(ibusimcontext->client_window); + ibusimcontext->client_window = NULL; + } + + if (client != NULL) { + ibusimcontext->client_window = g_object_ref(client); + if (!ibusimcontext->use_button_press_event) + _connect_button_press_event(ibusimcontext, TRUE); + } +} + +static void +_set_rect_scale_factor_with_window(GdkRectangle *area, GdkWindow *window) { + int scale_factor; + + g_assert(area); + g_assert(GDK_IS_WINDOW(window)); + + scale_factor = gdk_window_get_scale_factor(window); + area->x *= scale_factor; + area->y *= scale_factor; + area->width *= scale_factor; + area->height *= scale_factor; +} + +static gboolean +_set_cursor_location_internal(IBusIMContext *ibusimcontext) { + GdkRectangle area; + + if (ibusimcontext->client_window == NULL || ibusimcontext->ibuscontext == NULL) { + return FALSE; + } + + area = ibusimcontext->cursor_area; + + if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) { + gdouble px, py; + GdkWindow *parent; + GdkWindow *window = ibusimcontext->client_window; + + while ((parent = gdk_window_get_effective_parent(window)) != NULL) { + gdk_window_coords_to_parent(window, area.x, area.y, &px, &py); + area.x = px; + area.y = py; + window = parent; + } + + _set_rect_scale_factor_with_window(&area, ibusimcontext->client_window); + ibus_input_context_set_cursor_location_relative(ibusimcontext->ibuscontext, area.x, area.y, area.width, area.height); + return FALSE; + } + + if (area.x == -1 && area.y == -1 && area.width == 0 && area.height == 0) { + area.x = 0; + area.y += gdk_window_get_height(ibusimcontext->client_window); + } + gdk_window_get_root_coords(ibusimcontext->client_window, area.x, area.y, &area.x, &area.y); + _set_rect_scale_factor_with_window(&area, ibusimcontext->client_window); + ibus_input_context_set_cursor_location(ibusimcontext->ibuscontext, area.x, area.y, area.width, area.height); + return FALSE; +} + +static void +ibus_im_context_set_cursor_location(GtkIMContext *context, GdkRectangle *area) { + IDEBUG("%s", __FUNCTION__); + + IBusIMContext *ibusimcontext = IBUS_IM_CONTEXT(context); + + /* The area is the relative coordinates and this has to get the absolute + * ones in _set_cursor_location_internal() since GTK 4.0. + */ + if (ibusimcontext->cursor_area.x == area->x && ibusimcontext->cursor_area.y == area->y && + ibusimcontext->cursor_area.width == area->width && ibusimcontext->cursor_area.height == area->height) { + return; + } + ibusimcontext->cursor_area = *area; + _set_cursor_location_internal(ibusimcontext); +} + +static guint +get_selection_anchor_point(IBusIMContext *ibusimcontext, guint cursor_pos, guint surrounding_text_len) { + if (cursor_pos <= 0) { + return ibusimcontext->text->len; + } + return cursor_pos; +} + +static void +ibus_im_context_set_surrounding(GtkIMContext *context, const gchar *text, int len, int cursor_index) { + ibus_im_context_set_surrounding_with_selection(context, text, len, cursor_index, cursor_index); +} + +static void +ibus_im_context_set_surrounding_with_selection( + GtkIMContext *context, + const gchar *text, + int len, + int cursor_index, + int anchor_index) { + g_return_if_fail(context != NULL); + g_return_if_fail(IBUS_IS_IM_CONTEXT(context)); + g_return_if_fail(text != NULL); + g_return_if_fail(strlen(text) >= len); + g_return_if_fail(0 <= cursor_index && cursor_index <= len); + + IBusIMContext *ibusimcontext = IBUS_IM_CONTEXT(context); + + if (ibusimcontext->ibuscontext) { + IBusText *ibustext; + guint cursor_pos; + guint utf8_len; + gchar *p; + + p = g_strndup(text, len); + cursor_pos = g_utf8_strlen(p, cursor_index); + utf8_len = g_utf8_strlen(p, len); + ibustext = ibus_text_new_from_string(p); + IDEBUG("%s: ibustext=%p", __FUNCTION__, ibustext); + g_free(p); + + gint anchor_pos = get_selection_anchor_point(ibusimcontext, cursor_pos, utf8_len); + ibus_input_context_set_surrounding_text(ibusimcontext->ibuscontext, ibustext, cursor_pos, anchor_pos); + } +} + +static void +_bus_connected_cb(IBusBus *bus, IBusIMContext *ibusimcontext) { + IDEBUG("%s", __FUNCTION__); + if (ibusimcontext) + _create_input_context(ibusimcontext); +} + +static void +_ibus_context_commit_text_cb(IBusInputContext *ibuscontext, IBusText *text, IBusIMContext *ibusimcontext) { + IDEBUG("%s: text=%p", __FUNCTION__, text); + + _request_surrounding_text(ibusimcontext); + _commit_text(ibusimcontext, ibus_text_get_text(text)); + g_main_loop_quit(ibusimcontext->thread_loop); +} + +static void +_commit_text(IBusIMContext *ibusimcontext, const gchar *text) { + // g_signal_emit(ibusimcontext, _signal_commit_id, 0, text->text); + ibus_im_test_set_text(ibusimcontext, text); +} + +static gboolean +_retrieve_surrounding(IBusIMContext *ibusimcontext) { + // No-op in our simulation since we store the text + return TRUE; +} + +static gboolean +_delete_surrounding(IBusIMContext *ibusimcontext, gint offset_from_cursor, guint nchars) { + // g_signal_emit(ibusimcontext, _signal_delete_surrounding_id, 0); + if (offset_from_cursor < 0) { + for (guint val = nchars; + val <= ibusimcontext->text->len && (guint)ibusimcontext->text->str[ibusimcontext->text->len - val] >= 0x80; + val++) { + nchars = val; + } + g_string_erase(ibusimcontext->text, ibusimcontext->text->len - nchars, nchars); + } else { + g_string_erase(ibusimcontext->text, offset_from_cursor, nchars); + } + return TRUE; +} + +static gboolean +_key_is_modifier(guint keyval) { + /* See gdkkeys-x11.c:_gdk_keymap_key_is_modifier() for how this + * really should be implemented */ + + switch (keyval) { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Caps_Lock: + case GDK_KEY_Shift_Lock: + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Super_L: + case GDK_KEY_Super_R: + case GDK_KEY_Hyper_L: + case GDK_KEY_Hyper_R: + case GDK_KEY_ISO_Lock: + case GDK_KEY_ISO_Level2_Latch: + case GDK_KEY_ISO_Level3_Shift: + case GDK_KEY_ISO_Level3_Latch: + case GDK_KEY_ISO_Level3_Lock: + case GDK_KEY_ISO_Level5_Shift: + case GDK_KEY_ISO_Level5_Latch: + case GDK_KEY_ISO_Level5_Lock: + case GDK_KEY_ISO_Group_Shift: + case GDK_KEY_ISO_Group_Latch: + case GDK_KEY_ISO_Group_Lock: + return TRUE; + default: + return FALSE; + } +} + +/* Copy from gdk */ +static GdkEventKey * +_create_gdk_event(IBusIMContext *ibusimcontext, guint keyval, guint keycode, guint state) { + gunichar c = 0; + gchar buf[8]; + + GdkEventKey *event = (GdkEventKey *)gdk_event_new((state & IBUS_RELEASE_MASK) ? GDK_KEY_RELEASE : GDK_KEY_PRESS); + + if (ibusimcontext && ibusimcontext->client_window) + event->window = g_object_ref(ibusimcontext->client_window); + else if (_input_window) + event->window = g_object_ref(_input_window); + + /* The time is copied the latest value from the previous + * GdkKeyEvent in filter_keypress(). + * + * We understand the best way would be to pass the all time value + * to IBus functions process_key_event() and IBus DBus functions + * ProcessKeyEvent() in IM clients and IM engines so that the + * _create_gdk_event() could get the correct time values. + * However it would causes to change many functions and the time value + * would not provide the useful meanings for each IBus engines but just + * pass the original value to ForwardKeyEvent(). + * We use the saved value at the moment. + * + * Another idea might be to have the time implementation in X servers + * but some Xorg uses clock_gettime() and others use gettimeofday() + * and the values would be different in each implementation and + * locale/remote X server. So probably that idea would not work. */ + if (ibusimcontext) { + event->time = ibusimcontext->time; + } else { + event->time = GDK_CURRENT_TIME; + } + + event->send_event = FALSE; + event->state = state; + event->keyval = keyval; + event->string = NULL; + event->length = 0; + event->hardware_keycode = (keycode != 0) ? keycode + 8 : 0; + event->group = 0; + event->is_modifier = _key_is_modifier(keyval); + + if (keyval != GDK_KEY_VoidSymbol) + c = gdk_keyval_to_unicode(keyval); + + if (c) { + gsize bytes_written; + gint len; + + /* Apply the control key - Taken from Xlib + */ + if (event->state & GDK_CONTROL_MASK) { + if ((c >= '@' && c < '\177') || c == ' ') + c &= 0x1F; + else if (c == '2') { +#if GLIB_CHECK_VERSION(2, 68, 0) + event->string = g_memdup2("\0\0", 2); +#else + event->string = g_memdup("\0\0", 2); +#endif + event->length = 1; + buf[0] = '\0'; + goto out; + } else if (c >= '3' && c <= '7') + c -= ('3' - '\033'); + else if (c == '8') + c = '\177'; + else if (c == '/') + c = '_' & 0x1F; + } + + len = g_unichar_to_utf8(c, buf); + buf[len] = '\0'; + + event->string = g_locale_from_utf8(buf, len, NULL, &bytes_written, NULL); + if (event->string) + event->length = bytes_written; + } else if (keyval == GDK_KEY_Escape) { + event->length = 1; + event->string = g_strdup("\033"); + } else if (keyval == GDK_KEY_Return || keyval == GDK_KEY_KP_Enter) { + event->length = 1; + event->string = g_strdup("\r"); + } + + if (!event->string) { + event->length = 0; + event->string = g_strdup(""); + } +out: + return event; +} + +static void +_ibus_context_forward_key_event_cb( + IBusInputContext *ibuscontext, + guint keyval, + guint keycode, + guint state, + IBusIMContext *ibusimcontext) { + IDEBUG("%s", __FUNCTION__); + if (keycode == 0 && ibusimcontext->client_window) { + GdkDisplay *display = gdk_window_get_display(ibusimcontext->client_window); + GdkKeymap *keymap = gdk_keymap_get_for_display(display); + GdkKeymapKey *keys = NULL; + gint n_keys = 0; + if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) + keycode = keys->keycode; + else + g_warning("Failed to parse keycode from keyval %x", keyval); + } + GdkEventKey *event = _create_gdk_event(ibusimcontext, keyval, keycode, state); + gdk_event_put((GdkEvent *)event); + gdk_event_free((GdkEvent *)event); +} + +static void +_ibus_context_delete_surrounding_text_cb( + IBusInputContext *ibuscontext, + gint offset_from_cursor, + guint nchars, + IBusIMContext *ibusimcontext) { + IDEBUG("%s: offset %d, nchar: %d", __FUNCTION__, offset_from_cursor, nchars); + _delete_surrounding(ibusimcontext, offset_from_cursor, nchars); +} + +static void +_ibus_context_destroy_cb(IBusInputContext *ibuscontext, IBusIMContext *ibusimcontext) { + IDEBUG("%s", __FUNCTION__); + g_assert(ibusimcontext->ibuscontext == ibuscontext); + + g_object_unref(ibusimcontext->ibuscontext); + ibusimcontext->ibuscontext = NULL; +} + +static gboolean +_timeout_callback(gpointer *user_data) { + timeout_data *data = (timeout_data *)user_data; + g_main_loop_quit(data->thread_loop); + data->timed_out = TRUE; + return FALSE; +} + +static void +_run_main_loop_with_timeout(IBusIMContext *ibusimcontext) { + timeout_data data; + data.thread_loop = ibusimcontext->thread_loop; + data.timed_out = FALSE; + int timeout_id = g_timeout_add(1000 /*ms*/, (GSourceFunc)_timeout_callback, &data); + g_main_loop_run(ibusimcontext->thread_loop); + if (!data.timed_out) { + g_source_remove(timeout_id); + } +} + +static void +_create_input_context(IBusIMContext *ibusimcontext) { + IDEBUG("%s", __FUNCTION__); + + g_assert(ibusimcontext->ibuscontext == NULL); + + IBusInputContext *context = ibus_bus_create_input_context(_bus, "gtk-im"); + if (context == NULL) { + g_warning("Create input context failed."); + } else { + ibusimcontext->ibuscontext = context; + + g_signal_connect(ibusimcontext->ibuscontext, "commit-text", G_CALLBACK(_ibus_context_commit_text_cb), ibusimcontext); + g_signal_connect( + ibusimcontext->ibuscontext, "forward-key-event", G_CALLBACK(_ibus_context_forward_key_event_cb), ibusimcontext); + g_signal_connect( + ibusimcontext->ibuscontext, "delete-surrounding-text", G_CALLBACK(_ibus_context_delete_surrounding_text_cb), + ibusimcontext); + g_signal_connect(ibusimcontext->ibuscontext, "destroy", G_CALLBACK(_ibus_context_destroy_cb), ibusimcontext); + + ibus_input_context_set_capabilities(ibusimcontext->ibuscontext, ibusimcontext->caps); + + if (ibusimcontext->has_focus) { + /* The time order is _create_input_context() -> + * ibus_im_context_notify() -> ibus_im_context_focus_in() -> + * _create_input_context_done() + * so _set_content_type() is called at the beginning here + * because ibusimcontext->ibuscontext == NULL before. */ + _set_content_type(ibusimcontext); + + ibus_input_context_focus_in(ibusimcontext->ibuscontext); + _set_cursor_location_internal(ibusimcontext); + } + + if (!g_queue_is_empty(ibusimcontext->events_queue)) { + GdkEventKey *event; + while ((event = g_queue_pop_head(ibusimcontext->events_queue))) { + _process_key_event(context, event, ibusimcontext); + gboolean result = _process_key_event(context, event, ibusimcontext); + if (result) { + _run_main_loop_with_timeout(ibusimcontext); + } + gdk_event_free((GdkEvent *)event); + } + } + } +} + +void +ibus_im_test_set_thread_loop(IBusIMContext *context, GMainLoop *loop) { + context->thread_loop = loop; +} + +void +ibus_im_test_set_text(IBusIMContext *context, const gchar *text) { + if (!context->text) { + context->text = g_string_new(""); + } + + g_string_append(context->text, text); + ibus_im_context_set_surrounding_with_selection( + (GtkIMContext *)context, context->text->str, context->text->len, context->text->len, context->text->len); +} + +const gchar * +ibus_im_test_get_text(IBusIMContext *context) { + if (context->text) { + return context->text->str; + } + return NULL; +} + +void +ibus_im_test_clear_text(IBusIMContext *context) { + if (context->text) { + g_string_free(context->text, TRUE); + context->text = NULL; + } +} diff --git a/linux/ibus-keyman/tests/ibusimcontext.h b/linux/ibus-keyman/tests/ibusimcontext.h new file mode 100644 index 0000000000..9cea98751e --- /dev/null +++ b/linux/ibus-keyman/tests/ibusimcontext.h @@ -0,0 +1,73 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* vim:set et ts=4: */ +/* ibus - The Input Bus + * Copyright (C) 2008-2010 Peng Huang + * Copyright (C) 2008-2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ +#ifndef __IBUSIMCONTEXT_H__ +#define __IBUSIMCONTEXT_H__ + +#include + +/* + * Type macros. + */ +#define IBUS_TYPE_IM_CONTEXT \ + (ibus_im_context_get_type ()) +#define IBUS_IM_CONTEXT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), IBUS_TYPE_IM_CONTEXT, IBusIMContext)) +#define IBUS_IM_CONTEXT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), IBUS_TYPE_IM_CONTEXT, IBusIMContextClass)) +#define IBUS_IS_IM_CONTEXT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_IM_CONTEXT)) +#define IBUS_IS_IM_CONTEXT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_IM_CONTEXT)) +#define IBUS_IM_CONTEXT_GET_CLASS(obj) \ + (G_TYPE_CHECK_GET_CLASS ((obj), IBUS_TYPE_IM_CONTEXT, IBusIMContextClass)) + +G_BEGIN_DECLS +typedef struct _IBusIMContext IBusIMContext; +typedef struct _IBusIMContextClass IBusIMContextClass; +typedef struct _IBusIMContextPrivate IBusIMContextPrivate; + + +GType ibus_im_context_get_type (void); +IBusIMContext + *ibus_im_context_new (void); +void ibus_im_context_register_type + (GTypeModule *type_module); +void ibus_im_context_shutdown + (void); +const gchar + *ibus_im_context_get_ic (IBusIMContext *context); +void ibus_im_context_set_ic (IBusIMContext *context, + const gchar *ic); +void ibus_im_context_enable (IBusIMContext *context); +void ibus_im_context_disable (IBusIMContext *context); +void ibus_im_context_commit_string + (IBusIMContext *context, + const gchar *string); + +void ibus_im_test_set_thread_loop(IBusIMContext *context, GMainLoop *loop); + +void ibus_im_test_set_text(IBusIMContext *context, const gchar *text); +const gchar *ibus_im_test_get_text(IBusIMContext *context); +void ibus_im_test_clear_text(IBusIMContext *context); +G_END_DECLS +#endif // __IBUSIMCONTEXT_H__ + diff --git a/linux/ibus-keyman/tests/run-tests.sh b/linux/ibus-keyman/tests/run-tests.sh new file mode 100755 index 0000000000..72cc8f4a39 --- /dev/null +++ b/linux/ibus-keyman/tests/run-tests.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +TOP_SRCDIR=${top_srcdir:-$(realpath $(dirname $0)/..)} +BASEDIR=$(realpath $(dirname $0)) +TESTDIR=${XDG_DATA_HOME:-$HOME/.local/share}/keyman/test_kmx +PID_FILE=/tmp/ibus-keyman-test-pids + +if [ -v DEB_BUILD_MULTIARCH ]; then + # During package builds we skip these tests that require to start ibus because + # ibus requires to find /var/lib/dbus/machine-id or /etc/machine-id, otherwise it fails with: + # "Bail out! IBUS-FATAL-WARNING: Unable to load /var/lib/dbus/machine-id: Failed to open file + # “/var/lib/dbus/machine-id”: No such file or directory" + echo "1..1" + echo "ok 1 - Integration tests # SKIP on package build" + exit 0 +fi + +if ! which Xvfb > /dev/null || ! which Xephyr > /dev/null || ! which metacity > /dev/null; then + echo "Please install Xvfb, Xephyr and metacity before running these tests!" + exit 1 +fi + +function cleanup() { + if [ -f $PID_FILE ]; then + echo + echo "Shutting down processes..." + bash $PID_FILE > /dev/null 2>&1 + rm $PID_FILE + echo "Finished shutdown of processes." + fi +} + +echo > $PID_FILE +trap cleanup EXIT SIGINT + +TEMP_DATA_DIR=$(mktemp --directory) +echo "rm -rf ${TEMP_DATA_DIR}" >> $PID_FILE + +echo "Starting Xvfb..." +Xvfb -screen 0 1024x768x24 :33 &> /dev/null & +echo "kill -9 $!" >> $PID_FILE +sleep 1 +echo "Starting Xephyr..." +DISPLAY=:33 Xephyr :32 -screen 1024x768 &> /dev/null & +echo "kill -9 $!" >> $PID_FILE +sleep 1 +echo "Starting metacity" +metacity --display=:32 &> /dev/null & +echo "kill -9 $!" >> $PID_FILE + +export DISPLAY=:32 + +# Install schema to temporary directory. This removes the build dependency on the keyman package. +SCHEMA_DIR=$TEMP_DATA_DIR/glib-2.0/schemas +export XDG_DATA_DIRS=$TEMP_DATA_DIR:$XDG_DATA_DIRS + +mkdir -p $SCHEMA_DIR +cp ${TOP_SRCDIR}/../keyman-config/com.keyman.gschema.xml $SCHEMA_DIR/ +glib-compile-schemas $SCHEMA_DIR + +COMMON_ARCH_DIR= +[ -d ${TOP_SRCDIR}/../../common/core/desktop/build/arch ] && COMMON_ARCH_DIR=${TOP_SRCDIR}/../../common/core/desktop/build/arch +[ -d ${TOP_SRCDIR}/../keyboardprocessor/arch ] && COMMON_ARCH_DIR=${TOP_SRCDIR}/../keyboardprocessor/arch + +if [ -d ${COMMON_ARCH_DIR}/release ]; then + COMMON_ARCH_DIR=${COMMON_ARCH_DIR}/release +elif [ -d ${COMMON_ARCH_DIR}/debug ]; then + COMMON_ARCH_DIR=${COMMON_ARCH_DIR}/debug +else + echo "Can't find neither ${COMMON_ARCH_DIR}/release nor ${COMMON_ARCH_DIR}/debug" + exit 2 +fi + +if [ ! -d $TESTDIR ]; then + if [[ $(ls -l ${COMMON_ARCH_DIR}/tests/unit/kmx/*.kmx 2>/dev/null | wc -l) > 0 ]]; then + mkdir -p $(realpath --canonicalize-missing $TESTDIR/..) + ln -sf $(realpath ${COMMON_ARCH_DIR}/tests/unit/kmx) $TESTDIR + else + echo "Can't find kmx files in ${COMMON_ARCH_DIR}/tests/unit/kmx" + exit 3 + fi +fi + +[ "$1" == "-k" ] && ARG_K="-k" && shift +[ "$1" == "--tap" ] && ARG_TAP="--tap" && shift + +if [ $# -gt 0 ]; then + [ "$1" == "--" ] && shift + TESTFILES=($@) +else + pushd $TESTDIR > /dev/null + TESTFILES=(*.kmx) + popd > /dev/null +fi + +export GSETTINGS_BACKEND=keyfile +export LD_LIBRARY_PATH=${COMMON_ARCH_DIR}/src:$LD_LIBRARY_PATH + +# Ubuntu 18.04 Bionic doesn't have ibus-memconf +[ -f /usr/libexec/ibus-memconf ] && IBUS_CONFIG=--config=/usr/libexec/ibus-memconf + +ibus-daemon --panel=disable ${IBUS_CONFIG-} &> /tmp/ibus-daemon.log & +echo "kill -9 $!" >> $PID_FILE +../src/ibus-engine-keyman &> /tmp/ibus-engine-keyman.log & +echo "kill -9 $!" >> $PID_FILE +sleep 1s + +echo "Starting tests..." +./ibus-keyman-tests ${ARG_K-} ${ARG_TAP-} --directory $TESTDIR "${TESTFILES[@]}" +echo "Finished tests." diff --git a/linux/ibus-keyman/tests/testfixture.cpp b/linux/ibus-keyman/tests/testfixture.cpp new file mode 100644 index 0000000000..35fc53582d --- /dev/null +++ b/linux/ibus-keyman/tests/testfixture.cpp @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ibusimcontext.h" +#include "keycodes.h" +#include "keymanutil.h" +#include "kmx_test_source.hpp" +#include "testmodule.h" + +typedef struct { + IBusBus *bus; + GtkIMContext *context; + IBusIMContext *ibuscontext; +} IBusKeymanTestsFixture; + +typedef struct { + char *test_name; + char *test_path; +} TestData; + +static gboolean loaded = FALSE; +static GdkWindow *window = NULL; +static GMainLoop *thread_loop = NULL; +static GTypeModule *module = NULL; + +static void +module_register(GTypeModule *module) { + ibus_im_context_register_type(module); +} + +void +destroy(GtkWidget *widget, gpointer data) { + gtk_main_quit(); +} + +static void +ibus_keyman_tests_fixture_set_up(IBusKeymanTestsFixture *fixture, gconstpointer user_data) { + IBusIMContextClass *classClass; + + if (!window) { + GtkWidget *widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(widget, "destroy", G_CALLBACK(destroy), NULL); + window = gtk_widget_get_window(widget); + } + + if (!loaded) { + ibus_init(); + + module = test_module_new(module_register); + g_assert_nonnull(module); + + /* Not loaded until we call ref for the first time */ + classClass = static_cast(g_type_class_peek(IBUS_TYPE_IM_CONTEXT)); + g_assert_null(classClass); + + loaded = TRUE; + } + + fixture->bus = ibus_bus_new(); +} + +static void +ibus_keyman_tests_fixture_tear_down(IBusKeymanTestsFixture *fixture, gconstpointer user_data) { + if (thread_loop) { + g_main_loop_unref(thread_loop); + thread_loop = NULL; + } + + g_clear_object(&fixture->bus); + g_clear_object(&fixture->context); +} + +static void +switch_keyboard(IBusKeymanTestsFixture *fixture, const gchar *keyboard) { + ibus_bus_set_global_engine(fixture->bus, keyboard); + + IBusEngineDesc *desc = ibus_bus_get_global_engine(fixture->bus); + g_object_ref_sink(desc); + // g_debug("Switched to engine: '%s'", ibus_engine_desc_get_name(desc)); + g_assert_cmpstr(keyboard, ==, ibus_engine_desc_get_name(desc)); + g_clear_object(&desc); + + if (thread_loop) { + g_main_loop_unref(thread_loop); + thread_loop = NULL; + } + if (fixture->context) { + g_clear_object(&fixture->context); + } + fixture->ibuscontext = ibus_im_context_new(); + fixture->context = (GtkIMContext *)fixture->ibuscontext; + + thread_loop = g_main_loop_new(NULL, TRUE); + ibus_im_test_set_thread_loop(fixture->ibuscontext, thread_loop); +} + +template +std::string +string_format(const std::string &format, Args... args) { + // from https://stackoverflow.com/a/26221725/487503 + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +} + +static unsigned short vk_to_keycode(unsigned short vk) { + for (int i = 0; i < sizeof(keycode_to_vk); i++) { + if (keycode_to_vk[i] == vk) + return i; + } + return -1; +} + +#define KEYMAN_LCTRL 29 // 0x1D +#define KEYMAN_LALT 56 // 0x38 +#define KEYMAN_RCTRL 97 // 0x61 +#define KEYMAN_RALT 100 // 0x64 + +static guint +km_modifiers_to_ibus(km::tests::KmxTestSource& test_source, km::tests::key_event event) { + guint result = 0; + auto state = event.modifier_state | test_source.caps_lock_state(); + if (state & KM_KBP_MODIFIER_SHIFT) { + result |= IBUS_SHIFT_MASK; + } + if (state & KM_KBP_MODIFIER_ALT) { + result |= IBUS_MOD1_MASK; + } + if (state & KM_KBP_MODIFIER_RALT) { + if (event.vk == KEYMAN_RALT) + result |= IBUS_MOD1_MASK; + else + result |= IBUS_MOD5_MASK; + } + if (state & KM_KBP_MODIFIER_LALT) { + result |= IBUS_MOD1_MASK; + } + if (state & KM_KBP_MODIFIER_CTRL) { + result |= IBUS_CONTROL_MASK; + } + if (state & KM_KBP_MODIFIER_RCTRL) { + result |= IBUS_CONTROL_MASK; + } + if (state & KM_KBP_MODIFIER_LCTRL) { + result |= IBUS_CONTROL_MASK; + } + if (state & KM_KBP_MODIFIER_CAPS) { + result |= IBUS_LOCK_MASK; + } + return result; +} + +typedef struct { + guint modifiers_keydown; + guint modifiers_keyup; + guint keyval; + guint16 keycode; + guint is_modifier; +} gdk_key_event; + +static gdk_key_event +get_key_event_for_modifier(guint modifier, guint & prev_modifiers, guint keyval, guint keycode) { + gdk_key_event event; + event.modifiers_keydown = prev_modifiers; + event.modifiers_keyup = prev_modifiers | modifier | IBUS_RELEASE_MASK; + prev_modifiers |= modifier; + event.keyval = keyval; + event.keycode = keycode; + event.is_modifier = 1; + return event; +} + +static std::list +get_involved_keys(km::tests::KmxTestSource &test_source, km::tests::key_event test_event) { + guint modifiers = 0; + if (test_source.caps_lock_state()) { + modifiers = IBUS_LOCK_MASK; + } + + // process all modifiers + std::list result; + if (test_event.modifier_state & KM_KBP_MODIFIER_SHIFT) { + // we don't distinguish between R and L Shift + result.push_back(get_key_event_for_modifier(IBUS_SHIFT_MASK, modifiers, GDK_KEY_Shift_L, KEY_LEFTSHIFT)); + } + if (test_event.modifier_state & KM_KBP_MODIFIER_ALT || test_event.modifier_state & KM_KBP_MODIFIER_LALT) { + result.push_back(get_key_event_for_modifier(IBUS_MOD1_MASK, modifiers, GDK_KEY_Alt_L, KEY_LEFTALT)); + } + if (test_event.modifier_state & KM_KBP_MODIFIER_RALT) { + if (test_event.vk == KEYMAN_RALT) + result.push_back(get_key_event_for_modifier(IBUS_MOD1_MASK, modifiers, GDK_KEY_Alt_R, KEY_RIGHTALT)); + else + result.push_back(get_key_event_for_modifier(IBUS_MOD5_MASK, modifiers, GDK_KEY_Alt_R, KEY_RIGHTALT)); + } + if (test_event.modifier_state & KM_KBP_MODIFIER_CTRL || test_event.modifier_state & KM_KBP_MODIFIER_LCTRL) { + result.push_back(get_key_event_for_modifier(IBUS_CONTROL_MASK, modifiers, GDK_KEY_Control_L, KEY_LEFTCTRL)); + } + if (test_event.modifier_state & KM_KBP_MODIFIER_RCTRL) { + result.push_back(get_key_event_for_modifier(IBUS_CONTROL_MASK, modifiers, GDK_KEY_Control_R, KEY_RIGHTCTRL)); + } + + // process key + guint keyval = (test_event.modifier_state & KM_KBP_MODIFIER_SHIFT) ? gdk_keyval_to_upper(test_event.vk) + : gdk_keyval_to_lower(test_event.vk); + gdk_key_event event; + event.modifiers_keydown = modifiers; + event.modifiers_keyup = modifiers | IBUS_RELEASE_MASK; + event.keyval = keyval; + event.keycode = vk_to_keycode(test_event.vk); + event.is_modifier = 0; + result.push_back(event); + + return result; +} + +static std::list +get_context_keys(std::u16string context) { + std::list result; + for (auto c = context.begin(); c != context.end(); c++) { + gchar utf8[6]; + if (g_unichar_to_utf8((gunichar)*c, utf8) != 1 || utf8[0] < 32 || utf8[0] > 127) { + g_assertion_message_cmpstr( + G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, "We can only deal with ASCII characters in the context", utf8, "", ""); + } + result.push_back( + {km::kbp::kmx::s_char_to_vkey[(int)utf8[0] - 32].vk, + (uint16_t)(km::kbp::kmx::s_char_to_vkey[(int)utf8[0] - 32].shifted ? KM_KBP_MODIFIER_SHIFT : 0)}); + } + return result; +} + +static void +press_keys(IBusKeymanTestsFixture *fixture, km::tests::KmxTestSource & test_source,km::tests::key_event key_event) { + auto involved_keys = get_involved_keys(test_source, key_event); + + // press and hold the individual modifier keys, finally the actual key + std::stack keys_to_release; + for (auto key = involved_keys.begin(); key != involved_keys.end(); key++) { + GdkEventKey keyEvent = { + .type = GDK_KEY_PRESS, + .window = window, + .send_event = 0, + .time = 0, + .state = key->modifiers_keydown, + .keyval = key->keyval, + .length = 0, + .string = NULL, + .hardware_keycode = key->keycode, + .group = 0, + .is_modifier = key->is_modifier}; + gtk_im_context_filter_keypress(fixture->context, &keyEvent); + + keyEvent.type = GDK_KEY_RELEASE; + keyEvent.state = key->modifiers_keyup; + keys_to_release.push(keyEvent); + } + + // then release the keys in reverse order + while (!keys_to_release.empty()) { + auto keyEvent = keys_to_release.top(); + gtk_im_context_filter_keypress(fixture->context, &keyEvent); + keys_to_release.pop(); + } +} + +static void test_source(IBusKeymanTestsFixture *fixture, gconstpointer user_data) { + auto data = (TestData*)user_data; + auto sourcefile = string_format("%s.kmn", data->test_path); + auto kmxfile = string_format("und:%s.kmx", data->test_path); + + km::tests::KmxTestSource test_source; + std::string keys = ""; + std::u16string expected = u"", context = u""; + km::tests::kmx_options options; + bool expected_beep = false; + g_assert_cmpint(test_source.load_source(sourcefile.c_str(), keys, expected, context, options, expected_beep), ==, 0); + + for (auto & option : options) { + if (option.type == km::tests::KOT_INPUT) { + auto key = g_utf16_to_utf8((gunichar2 *)option.key.c_str(), option.key.length(), NULL, NULL, NULL); + auto value = g_utf16_to_utf8((gunichar2 *)option.value.c_str(), option.value.length(), NULL, NULL, NULL); + keyman_put_options_todconf(data->test_name, data->test_name, key, value); + } + } + + switch_keyboard(fixture, kmxfile.c_str()); + + auto contextKeys = get_context_keys(context); + for (auto k = contextKeys.begin(); k != contextKeys.end(); k++) { + press_keys(fixture, test_source, *k); + } + + gtk_im_context_focus_in(fixture->context); + + for (auto p = test_source.next_key(keys); p.vk != 0; p = test_source.next_key(keys)) { + press_keys(fixture, test_source, p); + } + + auto expectedText = g_utf16_to_utf8((gunichar2*)expected.c_str(), expected.length(), NULL, NULL, NULL); + g_assert_cmpstr(ibus_im_test_get_text(fixture->ibuscontext), ==, expectedText); +} + +void +print_usage() { + printf("Usage: %s --directory test1 [test2 ...]", g_get_prgname()); +} + +int +main(int argc, char *argv[]) { + gtk_init(&argc, &argv); + g_test_init(&argc, &argv, NULL); + g_test_set_nonfatal_assertions(); + + if (argc < 4) { + print_usage(); + return 1; + } + + if (strcmp(argv[1], "--directory") != 0) { + print_usage(); + return 2; + } + + char *directory = argv[2]; + int nTests = argc - 3; + char **tests = &argv[3]; + + // Add tests + for (int i = 0; i < nTests; i++) + { + auto filename = tests[i]; + if (strstr(filename, ".kmx") || strstr(filename, ".kmn")) { + filename[strlen(filename) - 4] = '\0'; + } + auto file = g_file_new_for_commandline_arg(filename); + auto testfilebase = g_file_get_basename(file); + auto testname = g_string_new(NULL); + g_string_append_printf(testname, "/%s", testfilebase); + auto testfile = g_file_new_build_filename(directory, testfilebase, NULL); + TestData testdata; + testdata.test_name = filename; + testdata.test_path = g_file_get_parse_name(testfile); + g_test_add( + testname->str, IBusKeymanTestsFixture, &testdata, ibus_keyman_tests_fixture_set_up, test_source, + ibus_keyman_tests_fixture_tear_down); + g_object_unref(file); + g_object_unref(testfile); + g_string_free(testname, TRUE); + } + + // Run tests + int retVal = g_test_run(); + + test_module_unuse(module); + return retVal; +} diff --git a/linux/ibus-keyman/tests/testmodule.c b/linux/ibus-keyman/tests/testmodule.c new file mode 100644 index 0000000000..4e32573d94 --- /dev/null +++ b/linux/ibus-keyman/tests/testmodule.c @@ -0,0 +1,64 @@ +#include "ibusimcontext.h" +#include +#include +#include +#include +#include "testmodule.h" + +static gboolean +test_module_load(GTypeModule *module) { + TestModule *test_module = TEST_MODULE(module); + + test_module->register_func(module); + + return TRUE; +} + +static void +test_module_unload(GTypeModule *module) { +} + +static void +test_module_class_init(TestModuleClass *class) { + GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS(class); + + module_class->load = test_module_load; + module_class->unload = test_module_unload; +} + +static GType +test_module_get_type(void) { + static GType object_type = 0; + + if (!object_type) { + static const GTypeInfo object_info = { + sizeof(TestModuleClass), (GBaseInitFunc)NULL, + (GBaseFinalizeFunc)NULL, (GClassInitFunc)test_module_class_init, + (GClassFinalizeFunc)NULL, NULL, + sizeof(TestModule), 0, + (GInstanceInitFunc)NULL, NULL, + }; + object_type = g_type_register_static(G_TYPE_TYPE_MODULE, "TestModule", &object_info, 0); + } + return object_type; +} + +GTypeModule * +test_module_new(TestModuleRegisterFunc register_func) { + TestModule *test_module = g_object_new(TEST_TYPE_MODULE, NULL); + GTypeModule *module = G_TYPE_MODULE(test_module); + + test_module->register_func = register_func; + + /* Register the types initially */ + g_type_module_set_name(module, "FOO"); + g_type_module_use(module); + //g_type_module_unuse(module); + + return G_TYPE_MODULE(module); +} + +void +test_module_unuse(GTypeModule *test_module) { + g_type_module_unuse(test_module); +} diff --git a/linux/ibus-keyman/tests/testmodule.h b/linux/ibus-keyman/tests/testmodule.h new file mode 100644 index 0000000000..465ec6e6be --- /dev/null +++ b/linux/ibus-keyman/tests/testmodule.h @@ -0,0 +1,33 @@ +#ifndef __TESTMODULE_H__ +#define __TESTMODULE_H__ + +G_BEGIN_DECLS + +typedef struct _TestModule TestModule; +typedef struct _TestModuleClass TestModuleClass; + +#define TEST_TYPE_MODULE (test_module_get_type()) +#define TEST_MODULE(module) (G_TYPE_CHECK_INSTANCE_CAST((module), TEST_TYPE_MODULE, TestModule)) +#define TEST_MODULE_CLASS(class) (G_TYPE_CHECK_CLASS_CAST((class), TEST_TYPE_MODULE, TestModuleClass)) +#define TEST_IS_MODULE(module) (G_TYPE_CHECK_INSTANCE_TYPE((module), TEST_TYPE_MODULE)) +#define TEST_IS_MODULE_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE((class), TEST_TYPE_MODULE)) +#define TEST_MODULE_GET_CLASS(module) (G_TYPE_INSTANCE_GET_CLASS((module), TEST_TYPE_MODULE, TestModuleClass)) +typedef void (*TestModuleRegisterFunc)(GTypeModule* module); + +struct _TestModule { + GTypeModule parent_instance; + + TestModuleRegisterFunc register_func; +}; + +struct _TestModuleClass { + GTypeModuleClass parent_class; +}; + +static GType test_module_get_type(void); +GTypeModule* test_module_new(TestModuleRegisterFunc register_func); +void test_module_unuse(GTypeModule* test_module); + +G_END_DECLS + +#endif // __TESTMODULE_H__ diff --git a/linux/keyman-config/locale/bwr_NT.po b/linux/keyman-config/locale/bwr_NT.po new file mode 100644 index 0000000000..da7a50f138 --- /dev/null +++ b/linux/keyman-config/locale/bwr_NT.po @@ -0,0 +1,331 @@ +msgid "" +msgstr "" +"Project-Id-Version: keyman\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-08-19 19:17+0200\n" +"PO-Revision-Date: 2021-10-19 09:04\n" +"Last-Translator: \n" +"Language-Team: Bura-Pabir\n" +"Language: bwr_NT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Crowdin-Project: keyman\n" +"X-Crowdin-Project-ID: 386703\n" +"X-Crowdin-Language: bwr-ng\n" +"X-Crowdin-File: /master/linux/keyman-config.pot\n" +"X-Crowdin-File-ID: 504\n" + +#: keyman_config/__init__.py:68 +msgid "Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting." +msgstr "Aɗi sentry-sdk aɗi raven tsuwa anaɗi. Aɗena zhar hamta labar ar dametǝdzi na ar Sentry wa." + +#: keyman_config/downloadkeyboard.py:23 +msgid "Download Keyman keyboards" +msgstr "Dawunlodi ar keyboardyeri ar Keyman" + +#: keyman_config/downloadkeyboard.py:37 keyman_config/keyboard_details.py:49 +#: keyman_config/keyboard_details.py:340 keyman_config/view_installed.py:205 +msgid "_Close" +msgstr "_Harta" + +#: keyman_config/install_kmp.py:99 +msgid "You do not have permissions to install the keyboard files to the shared area /usr/local/share/keyman" +msgstr "Ga aɗika dunar hara instal ar fayilyeri ar keyboard akwa vǝi tǝ mda ata ndǝka wa na ar /usr/local/share/keyman" + +#: keyman_config/install_kmp.py:103 +msgid "You do not have permissions to install the documentation to the shared documentation area /usr/local/share/doc/keyman" +msgstr "Ga aɗika dunar hara instal ar wujir rubutuyeri akwa vǝi tǝ mda ata ndǝkǝ wujir rubutuyeri wa na ar /usr/local/share/doc/keyman" + +#: keyman_config/install_kmp.py:107 +msgid "You do not have permissions to install the font files to the shared font area /usr/local/share/fonts" +msgstr "Ga aɗika dunar hara instal ar fayilyeri ar rubutu akwa vǝi tǝ mda ata ndǝka rubutu wa na ar/usr/local/share/fonts" + +#: keyman_config/install_kmp.py:179 +#, python-brace-format +msgid "install_kmp.py: error: No kmp.json or kmp.inf found in {package}" +msgstr "install_kmp.py: error: No kmp.json or kmp.inf found in {package}" + +#: keyman_config/install_kmp.py:246 +#, python-brace-format +msgid "install_kmp.py: error: No kmp.json or kmp.inf found in {packageFile}" +msgstr "install_kmp.py: error: No kmp.json or kmp.inf found in {packageFile}" + +#: keyman_config/install_window.py:54 +#, python-brace-format +msgid "Installing keyboard/package {keyboardid}" +msgstr "Akwa hara instal ar keyboard/package {keyboardid}" + +#: keyman_config/install_window.py:72 keyman_config/install_window.py:93 +msgid "Keyboard is installed already" +msgstr "Ku kurtǝ ka hara instal ar keyboard" + +#: keyman_config/install_window.py:74 +#, python-brace-format +msgid "The {name} keyboard is already installed at version {version}. Do you want to uninstall then reinstall it?" +msgstr "Keyboard {name} ni ku kurtǝ ka hara instal akwa jili {version}. Ga ata bara kara kǝlɓǝlani ya ambǝla gǝ jaktǝ hara instal ni ya?" + +#: keyman_config/install_window.py:95 +#, python-brace-format +msgid "The {name} keyboard is already installed with a newer version {installedversion}. Do you want to uninstall it and install the older version {version}?" +msgstr "Keyboard {name} ku kurtǝ ka hara instal ka jili na bǝlinnari {installedversion}. Ga ata bara kara kǝlɓǝlani ya ambǝla gǝ hara instal ar jili na waci ni ya version {version}?" + +#: keyman_config/install_window.py:128 +msgid "Keyboard layouts: " +msgstr "Keyboard layoutyeri: " + +#: keyman_config/install_window.py:147 +msgid "Fonts: " +msgstr "Rubutuyeri: " + +#: keyman_config/install_window.py:167 keyman_config/keyboard_details.py:96 +msgid "Package version: " +msgstr "Jili ar package: " + +#: keyman_config/install_window.py:179 +msgid "Author: " +msgstr "Mdǝna baɗitǝ namari: " + +#: keyman_config/install_window.py:197 +msgid "Website: " +msgstr "Websaitu: " + +#: keyman_config/install_window.py:211 +msgid "Copyright: " +msgstr "Kopirait: " + +#: keyman_config/install_window.py:245 +msgid "Details" +msgstr "Suayeri atakǝr" + +#: keyman_config/install_window.py:248 +msgid "README" +msgstr "KARATƏRA" + +#: keyman_config/install_window.py:256 keyman_config/view_installed.py:200 +msgid "_Install" +msgstr "_Instal" + +#: keyman_config/install_window.py:260 +msgid "_Cancel" +msgstr "_Awo" + +#: keyman_config/install_window.py:305 +#, python-brace-format +msgid "Keyboard {name} installed" +msgstr "Keyboard {name} ku hara instal" + +#: keyman_config/install_window.py:310 keyman_config/install_window.py:315 +#, python-brace-format +msgid "Keyboard {name} could not be installed." +msgstr "Keyboard {name} aɗi wutǝ ka hara instal wa." + +#: keyman_config/install_window.py:311 +msgid "Error Message:" +msgstr "Labar na ar dametǝdzi aɗi:" + +#: keyman_config/install_window.py:316 +msgid "Warning Message:" +msgstr "Labar na ar vǝdzǝ hankal:" + +#: keyman_config/keyboard_details.py:37 +#, python-brace-format +msgid "{name} keyboard" +msgstr "{name} keyboard" + +#: keyman_config/keyboard_details.py:53 +msgid "ERROR: Keyboard metadata is damaged.\n" +"Please \"Uninstall\" and then \"Install\" the keyboard." +msgstr "DAMETƏDZI: Keyboard metadata kǝ hili.\n" +"Usa \"Kǝlɓǝla\" amblula gǝ jaktǝ hara \"Instal\" ar keyboard ni." + +#: keyman_config/keyboard_details.py:74 +msgid "Package name: " +msgstr "Thlǝma Package: " + +#: keyman_config/keyboard_details.py:85 +msgid "Package id: " +msgstr "Package id: " + +#: keyman_config/keyboard_details.py:108 +msgid "Package description: " +msgstr "Su ata kǝra Package: " + +#: keyman_config/keyboard_details.py:121 +msgid "Package author: " +msgstr "Mdǝ na nam Package ni: " + +#: keyman_config/keyboard_details.py:133 +msgid "Package copyright: " +msgstr "Kopirait ar package: " + +#: keyman_config/keyboard_details.py:174 +msgid "Keyboard filename: " +msgstr "Thlǝma fayil ar keyboard: " + +#: keyman_config/keyboard_details.py:187 +msgid "Keyboard name: " +msgstr "Thlǝma keyboard: " + +#: keyman_config/keyboard_details.py:198 +msgid "Keyboard id: " +msgstr "Keyboard id: " + +#: keyman_config/keyboard_details.py:209 +msgid "Keyboard version: " +msgstr "Jili Keyboard ni: " + +#: keyman_config/keyboard_details.py:221 +msgid "Keyboard author: " +msgstr "Thlǝma mdǝ na nam keyboard: " + +#: keyman_config/keyboard_details.py:232 +msgid "Keyboard license: " +msgstr "Lasens ar keyboard: " + +#: keyman_config/keyboard_details.py:243 +msgid "Keyboard description: " +msgstr "Su ata kǝra keyboard: " + +#: keyman_config/keyboard_details.py:334 +#, python-brace-format +msgid "Scan this code to load this keyboard\n" +"on another device or share online" +msgstr "Kǝtǝ fwator kod ngǝni kamnyar hara lodi ar keyboard nǝni akwa na'úra laga wala kara tǝra akwa onlayin" + +#: keyman_config/options.py:24 +#, python-brace-format +msgid "{packageId} Settings" +msgstr "{packageId} Setiyeri" + +#: keyman_config/view_installed.py:30 +msgid "Keyman Configuration" +msgstr "Keyman Configuration" + +#: keyman_config/view_installed.py:60 +msgid "Choose a kmp file..." +msgstr "Ccarɓǝla fayil ar kmp..." + +#. i18n: file type in file selection dialog +#: keyman_config/view_installed.py:65 +msgid "KMP files" +msgstr "Fayilyeri ar KMP" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:141 +msgid "Icon" +msgstr "Fwato" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:145 +msgid "Name" +msgstr "Thlǝm" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:148 +msgid "Version" +msgstr "Jili" + +#: keyman_config/view_installed.py:161 +msgid "_Uninstall" +msgstr "_Kǝlɓǝla" + +#: keyman_config/view_installed.py:162 keyman_config/view_installed.py:304 +msgid "Uninstall keyboard" +msgstr "Kǝlɓǝla keyboard" + +#: keyman_config/view_installed.py:167 +msgid "_About" +msgstr "_Atakǝra" + +#: keyman_config/view_installed.py:168 keyman_config/view_installed.py:306 +msgid "About keyboard" +msgstr "Atakǝra keyboard" + +#: keyman_config/view_installed.py:173 +msgid "_Help" +msgstr "_Dlara" + +#: keyman_config/view_installed.py:174 keyman_config/view_installed.py:305 +msgid "Help for keyboard" +msgstr "Dlara ar keyboard" + +#: keyman_config/view_installed.py:179 +msgid "_Options" +msgstr "_Suayeri natǝ mda ata cara" + +#: keyman_config/view_installed.py:180 keyman_config/view_installed.py:307 +msgid "Settings for keyboard" +msgstr "Setiyeri na ar keyboard" + +#: keyman_config/view_installed.py:190 +msgid "_Refresh" +msgstr "_Jaktǝ kǝlar ni" + +#: keyman_config/view_installed.py:191 +msgid "Refresh keyboard list" +msgstr "Jaktǝ kǝlar list ar keyboard" + +#: keyman_config/view_installed.py:195 +msgid "_Download" +msgstr "_Dawunlodi" + +#: keyman_config/view_installed.py:196 +msgid "Download and install a keyboard from the Keyman website" +msgstr "Kara dawunlodi ka hara instal ar keyboard akwa websaitu ar Keyman" + +#: keyman_config/view_installed.py:201 +msgid "Install a keyboard from a file" +msgstr "Hara instal ar keyboard atar fayil" + +#: keyman_config/view_installed.py:206 +msgid "Close window" +msgstr "Hartǝ windo ni" + +#: keyman_config/view_installed.py:278 +#, python-brace-format +msgid "Uninstall keyboard {package}" +msgstr "Kǝlɓǝla keyboard {package}" + +#: keyman_config/view_installed.py:280 +#, python-brace-format +msgid "Help for keyboard {package}" +msgstr "Dlara na ar keyboard {package}" + +#: keyman_config/view_installed.py:282 +#, python-brace-format +msgid "About keyboard {package}" +msgstr "Atakǝra keyboard {package}" + +#: keyman_config/view_installed.py:284 +#, python-brace-format +msgid "Settings for keyboard {package}" +msgstr "Setiyeri ar keyboard {package}" + +#: keyman_config/view_installed.py:349 +msgid "Uninstall keyboard package?" +msgstr "Kǝlɓǝla package ar keyboard?" + +#: keyman_config/view_installed.py:351 +#, python-brace-format +msgid "Are you sure that you want to uninstall the {keyboard} keyboard and its fonts?" +msgstr "Gǝ tamata abbǝr ga ata kǝlɓǝla keyboard ni {keyboard} ka rubutǝyeri ni ya?" + +#: keyman_config/welcome.py:22 +#, python-brace-format +msgid "{name} installed" +msgstr "{name} instal" + +#: keyman_config/welcome.py:40 +msgid "Open in _Web browser" +msgstr "Kǝlari akwa _Web brauza" + +#: keyman_config/welcome.py:42 +msgid "Open in the default web browser to do things like printing" +msgstr "Kǝlari akwa web brauza natǝ tsa an default kamnyar hara suayeri apa printin" + +#: keyman_config/welcome.py:45 +msgid "_OK" +msgstr "_OK" + diff --git a/linux/keyman-config/locale/ff_NG.po b/linux/keyman-config/locale/ff_NG.po new file mode 100644 index 0000000000..55690f4abd --- /dev/null +++ b/linux/keyman-config/locale/ff_NG.po @@ -0,0 +1,331 @@ +msgid "" +msgstr "" +"Project-Id-Version: keyman\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-08-19 19:17+0200\n" +"PO-Revision-Date: 2021-10-20 03:48\n" +"Last-Translator: \n" +"Language-Team: Fulfulde (Nigeria)\n" +"Language: ff_NG\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Crowdin-Project: keyman\n" +"X-Crowdin-Project-ID: 386703\n" +"X-Crowdin-Language: ff-ng\n" +"X-Crowdin-File: /master/linux/keyman-config.pot\n" +"X-Crowdin-File-ID: 504\n" + +#: keyman_config/__init__.py:68 +msgid "Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting." +msgstr "Naa sentiri-sdk naa raven ba ɗo feesi. Walaa holla Sentiri ɗo wulla ko naa dayday ba. " + +#: keyman_config/downloadkeyboard.py:23 +msgid "Download Keyman keyboards" +msgstr "A` jippina Kibods Keyman" + +#: keyman_config/downloadkeyboard.py:37 keyman_config/keyboard_details.py:49 +#: keyman_config/keyboard_details.py:340 keyman_config/view_installed.py:205 +msgid "_Close" +msgstr "_Maɓɓu" + +#: keyman_config/install_kmp.py:99 +msgid "You do not have permissions to install the keyboard files to the shared area /usr/local/share/keyman" +msgstr "A` walaa baawal resigo kibod man haa babal sendol bee jamaaje /usr/local/share/keyman" + +#: keyman_config/install_kmp.py:103 +msgid "You do not have permissions to install the documentation to the shared documentation area /usr/local/share/doc/keyman" +msgstr "A` walaa baawal resigo ɗereji man haa babal sendol ɗereji /usr/share/doc/keyman" + +#: keyman_config/install_kmp.py:107 +msgid "You do not have permissions to install the font files to the shared font area /usr/local/share/fonts" +msgstr "A` walaa baawal resigo bindi man haa babal sendol bindi /usr/local/share/fonts" + +#: keyman_config/install_kmp.py:179 +#, python-brace-format +msgid "install_kmp.py: error: No kmp.json or kmp.inf found in {package}" +msgstr "resigo_kmp.py: error: No kmp.json or kmp.inf found in {package}" + +#: keyman_config/install_kmp.py:246 +#, python-brace-format +msgid "install_kmp.py: error: No kmp.json or kmp.inf found in {packageFile}" +msgstr "resigol_kmp.py: error: No kmp.json or kmp.inf found in {packageFile}" + +#: keyman_config/install_window.py:54 +#, python-brace-format +msgid "Installing keyboard/package {keyboardid}" +msgstr "Resigo kibod/pakej man {keyboardid}" + +#: keyman_config/install_window.py:72 keyman_config/install_window.py:93 +msgid "Keyboard is installed already" +msgstr "Kibod man arti nasti" + +#: keyman_config/install_window.py:74 +#, python-brace-format +msgid "The {name} keyboard is already installed at version {version}. Do you want to uninstall then reinstall it?" +msgstr "Inde {name} kibod man arti nasti haa nonde ko mara haaje {version}. A` ɗo mari haaje a` itta nden a` lora waɗa ɗum na?" + +#: keyman_config/install_window.py:95 +#, python-brace-format +msgid "The {name} keyboard is already installed with a newer version {installedversion}. Do you want to uninstall it and install the older version {version}?" +msgstr "{name} kibod man arti nastini bee nanta nonde kesum {installedversion}. A` ɗo yiɗi a` itta ɗum nden a` waata jee ɓoyma {version}?" + +#: keyman_config/install_window.py:128 +msgid "Keyboard layouts: " +msgstr "Yanayu kibod: " + +#: keyman_config/install_window.py:147 +msgid "Fonts: " +msgstr "Mawnugo bindi: " + +#: keyman_config/install_window.py:167 keyman_config/keyboard_details.py:96 +msgid "Package version: " +msgstr "Ko woni kesum ha pakej man: " + +#: keyman_config/install_window.py:179 +msgid "Author: " +msgstr "Mo windi: " + +#: keyman_config/install_window.py:197 +msgid "Website: " +msgstr "Lenguru: " + +#: keyman_config/install_window.py:211 +msgid "Copyright: " +msgstr "Hakke dow man: " + +#: keyman_config/install_window.py:245 +msgid "Details" +msgstr "Ko woni nder dalla-dalla" + +#: keyman_config/install_window.py:248 +msgid "README" +msgstr "JANNGAM" + +#: keyman_config/install_window.py:256 keyman_config/view_installed.py:200 +msgid "_Install" +msgstr "_Jo`unu" + +#: keyman_config/install_window.py:260 +msgid "_Cancel" +msgstr "_Wurtu" + +#: keyman_config/install_window.py:305 +#, python-brace-format +msgid "Keyboard {name} installed" +msgstr "Kibod {name} sigaama" + +#: keyman_config/install_window.py:310 keyman_config/install_window.py:315 +#, python-brace-format +msgid "Keyboard {name} could not be installed." +msgstr "Kibod {name} joinntaa ko." + +#: keyman_config/install_window.py:311 +msgid "Error Message:" +msgstr "Lellal Naa daidai ba:" + +#: keyman_config/install_window.py:316 +msgid "Warning Message:" +msgstr "Lellal dasugo noppi:" + +#: keyman_config/keyboard_details.py:37 +#, python-brace-format +msgid "{name} keyboard" +msgstr "{name} kibod" + +#: keyman_config/keyboard_details.py:53 +msgid "ERROR: Keyboard metadata is damaged.\n" +"Please \"Uninstall\" and then \"Install\" the keyboard." +msgstr "NAA DAYDAY BA: Ko joggata kibod wonni. Useni \"A` itta\" nden bo a` \"Waata\" kibod man." + +#: keyman_config/keyboard_details.py:74 +msgid "Package name: " +msgstr "Inde pakej: " + +#: keyman_config/keyboard_details.py:85 +msgid "Package id: " +msgstr "id pakej: " + +#: keyman_config/keyboard_details.py:108 +msgid "Package description: " +msgstr "Sapporɗe pakej: " + +#: keyman_config/keyboard_details.py:121 +msgid "Package author: " +msgstr "Mo windi Pakej: " + +#: keyman_config/keyboard_details.py:133 +msgid "Package copyright: " +msgstr "jawmo hakke pakej: " + +#: keyman_config/keyboard_details.py:174 +msgid "Keyboard filename: " +msgstr "Inde ko joggi kibod: " + +#: keyman_config/keyboard_details.py:187 +msgid "Keyboard name: " +msgstr "Inde kibod: " + +#: keyman_config/keyboard_details.py:198 +msgid "Keyboard id: " +msgstr "id kibod: " + +#: keyman_config/keyboard_details.py:209 +msgid "Keyboard version: " +msgstr "Nonde kibod: " + +#: keyman_config/keyboard_details.py:221 +msgid "Keyboard author: " +msgstr "Mo bindi kibod: " + +#: keyman_config/keyboard_details.py:232 +msgid "Keyboard license: " +msgstr "Ɗereji hakke kibod: " + +#: keyman_config/keyboard_details.py:243 +msgid "Keyboard description: " +msgstr "Sapporɗe kibod: " + +#: keyman_config/keyboard_details.py:334 +#, python-brace-format +msgid "Scan this code to load this keyboard\n" +"on another device or share online" +msgstr "A` waɗa sikanin kuungel ngel ngam a` nasta nder kibod ɗo\n" +"dow komputa feere ko bo senda ɗun haa online" + +#: keyman_config/options.py:24 +#, python-brace-format +msgid "{packageId} Settings" +msgstr "Mo`innol {packageId} " + +#: keyman_config/view_installed.py:30 +msgid "Keyman Configuration" +msgstr "Konfigoreshon Keyman" + +#: keyman_config/view_installed.py:60 +msgid "Choose a kmp file..." +msgstr "Cuptu fayel kmp..." + +#. i18n: file type in file selection dialog +#: keyman_config/view_installed.py:65 +msgid "KMP files" +msgstr "fayel KMP" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:141 +msgid "Icon" +msgstr "Alaama" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:145 +msgid "Name" +msgstr "Inde" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:148 +msgid "Version" +msgstr "Nonde" + +#: keyman_config/view_installed.py:161 +msgid "_Uninstall" +msgstr "_Ittu" + +#: keyman_config/view_installed.py:162 keyman_config/view_installed.py:304 +msgid "Uninstall keyboard" +msgstr "A` itta kibod" + +#: keyman_config/view_installed.py:167 +msgid "_About" +msgstr "_Dow man" + +#: keyman_config/view_installed.py:168 keyman_config/view_installed.py:306 +msgid "About keyboard" +msgstr "Dow kibod man" + +#: keyman_config/view_installed.py:173 +msgid "_Help" +msgstr "_Mballinde" + +#: keyman_config/view_installed.py:174 keyman_config/view_installed.py:305 +msgid "Help for keyboard" +msgstr "Mballinde dow kibod" + +#: keyman_config/view_installed.py:179 +msgid "_Options" +msgstr "_Cuɓol" + +#: keyman_config/view_installed.py:180 keyman_config/view_installed.py:307 +msgid "Settings for keyboard" +msgstr "Woinnol jee kibod" + +#: keyman_config/view_installed.py:190 +msgid "_Refresh" +msgstr "_Lorugo waɗa kesum" + +#: keyman_config/view_installed.py:191 +msgid "Refresh keyboard list" +msgstr "Loru waɗu kesitol limngal kibod " + +#: keyman_config/view_installed.py:195 +msgid "_Download" +msgstr "_Jippingo" + +#: keyman_config/view_installed.py:196 +msgid "Download and install a keyboard from the Keyman website" +msgstr "Jippunu nden a` wa`ina kibod man hedi lenguru Keyman" + +#: keyman_config/view_installed.py:201 +msgid "Install a keyboard from a file" +msgstr "Wa`unu kibod man diga fayel" + +#: keyman_config/view_installed.py:206 +msgid "Close window" +msgstr "Maɓɓu lenguru man" + +#: keyman_config/view_installed.py:278 +#, python-brace-format +msgid "Uninstall keyboard {package}" +msgstr "A` itta kibod man {package}" + +#: keyman_config/view_installed.py:280 +#, python-brace-format +msgid "Help for keyboard {package}" +msgstr "Mballa jee kibod {package}" + +#: keyman_config/view_installed.py:282 +#, python-brace-format +msgid "About keyboard {package}" +msgstr "Dow kibod man {package}" + +#: keyman_config/view_installed.py:284 +#, python-brace-format +msgid "Settings for keyboard {package}" +msgstr "Wo`innol jee kibod {package}" + +#: keyman_config/view_installed.py:349 +msgid "Uninstall keyboard package?" +msgstr "A` itta kuuje kibod man?" + +#: keyman_config/view_installed.py:351 +#, python-brace-format +msgid "Are you sure that you want to uninstall the {keyboard} keyboard and its fonts?" +msgstr "A` tabbiti dow a` yiɗi ittugo {keyboard} kibod man bee nanta bindi man?" + +#: keyman_config/welcome.py:22 +#, python-brace-format +msgid "{name} installed" +msgstr "{name} wa`inna ma" + +#: keyman_config/welcome.py:40 +msgid "Open in _Web browser" +msgstr "Maɓɓutu nder _Lenguru" + +#: keyman_config/welcome.py:42 +msgid "Open in the default web browser to do things like printing" +msgstr "Maɓɓutu haa lenguru difolt ngam waɗa kuujeeji bana wurtungo ɗum" + +#: keyman_config/welcome.py:45 +msgid "_OK" +msgstr "_OK" + diff --git a/linux/keyman-config/locale/ha_HG.po b/linux/keyman-config/locale/ha_HG.po new file mode 100644 index 0000000000..5070dd0c6a --- /dev/null +++ b/linux/keyman-config/locale/ha_HG.po @@ -0,0 +1,330 @@ +msgid "" +msgstr "" +"Project-Id-Version: keyman\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-08-19 19:17+0200\n" +"PO-Revision-Date: 2021-09-29 03:35\n" +"Last-Translator: \n" +"Language-Team: Hausa\n" +"Language: ha_HG\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: keyman\n" +"X-Crowdin-Project-ID: 386703\n" +"X-Crowdin-Language: ha\n" +"X-Crowdin-File: /master/linux/keyman-config.pot\n" +"X-Crowdin-File-ID: 504\n" + +#: keyman_config/__init__.py:68 +msgid "Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting." +msgstr "Babu daya daga cikin sentry-sdk ko raven. Baya bada damar bada rahoton kuskure na Sentry." + +#: keyman_config/downloadkeyboard.py:23 +msgid "Download Keyman keyboards" +msgstr "A sauke madannin Keyman" + +#: keyman_config/downloadkeyboard.py:37 keyman_config/keyboard_details.py:49 +#: keyman_config/keyboard_details.py:340 keyman_config/view_installed.py:205 +msgid "_Close" +msgstr "_Rufe" + +#: keyman_config/install_kmp.py:99 +msgid "You do not have permissions to install the keyboard files to the shared area /usr/local/share/keyman" +msgstr "Baka da izinin saka ma'ajiyar madannai zuwa sashin rabiya /usr/local/share/keyman" + +#: keyman_config/install_kmp.py:103 +msgid "You do not have permissions to install the documentation to the shared documentation area /usr/local/share/doc/keyman" +msgstr "Baka da izinin saka takardu a wajen da ake saka takardu /usr/local/share/doc/keyman" + +#: keyman_config/install_kmp.py:107 +msgid "You do not have permissions to install the font files to the shared font area /usr/local/share/fonts" +msgstr "Baka da izinin saka ƙunshin rubutu a wajen ajiye rubutu /usr/local/share/fonts" + +#: keyman_config/install_kmp.py:179 +#, python-brace-format +msgid "install_kmp.py: error: No kmp.json or kmp.inf found in {package}" +msgstr "shigar da_kmp.py: error: No kmp.json or kmp.inf da aka samu cikin {package}" + +#: keyman_config/install_kmp.py:246 +#, python-brace-format +msgid "install_kmp.py: error: No kmp.json or kmp.inf found in {packageFile}" +msgstr "shigar da_kmp.py: error: No kmp.json or kmp.inf da aka samu cikin {packageFile}" + +#: keyman_config/install_window.py:54 +#, python-brace-format +msgid "Installing keyboard/package {keyboardid}" +msgstr "Saka madanni/ƙunshi {keyboardid}" + +#: keyman_config/install_window.py:72 keyman_config/install_window.py:93 +msgid "Keyboard is installed already" +msgstr "A riga an saka madanni" + +#: keyman_config/install_window.py:74 +#, python-brace-format +msgid "The {name} keyboard is already installed at version {version}. Do you want to uninstall then reinstall it?" +msgstr "An riga an shigar da {name} madannin a nau'in {version}. Ana son a cire a sake shigarwa?" + +#: keyman_config/install_window.py:95 +#, python-brace-format +msgid "The {name} keyboard is already installed with a newer version {installedversion}. Do you want to uninstall it and install the older version {version}?" +msgstr "An riga an saka madannin {name} sabon nau'i {installedversion}. Ana son a cire a shigar da tsohon {version}?" + +#: keyman_config/install_window.py:128 +msgid "Keyboard layouts: " +msgstr "Tsarin madannai: " + +#: keyman_config/install_window.py:147 +msgid "Fonts: " +msgstr "Fonts: " + +#: keyman_config/install_window.py:167 keyman_config/keyboard_details.py:96 +msgid "Package version: " +msgstr "Tsarin ƙunshin: " + +#: keyman_config/install_window.py:179 +msgid "Author: " +msgstr "Mawallafi: " + +#: keyman_config/install_window.py:197 +msgid "Website: " +msgstr "Yanar gizo: " + +#: keyman_config/install_window.py:211 +msgid "Copyright: " +msgstr "Haƙƙin mallaka: " + +#: keyman_config/install_window.py:245 +msgid "Details" +msgstr "Cikakken bayani" + +#: keyman_config/install_window.py:248 +msgid "README" +msgstr "KARANTA" + +#: keyman_config/install_window.py:256 keyman_config/view_installed.py:200 +msgid "_Install" +msgstr "_shigar" + +#: keyman_config/install_window.py:260 +msgid "_Cancel" +msgstr "_Sokewa" + +#: keyman_config/install_window.py:305 +#, python-brace-format +msgid "Keyboard {name} installed" +msgstr "Madannan {name} da aka shigar" + +#: keyman_config/install_window.py:310 keyman_config/install_window.py:315 +#, python-brace-format +msgid "Keyboard {name} could not be installed." +msgstr "Ba zai yiwu a saka {name} madanni ba." + +#: keyman_config/install_window.py:311 +msgid "Error Message:" +msgstr "Saƙon kuskure:" + +#: keyman_config/install_window.py:316 +msgid "Warning Message:" +msgstr "Saƙon gargaɗi:" + +#: keyman_config/keyboard_details.py:37 +#, python-brace-format +msgid "{name} keyboard" +msgstr "{name} madanni" + +#: keyman_config/keyboard_details.py:53 +msgid "ERROR: Keyboard metadata is damaged.\n" +"Please \"Uninstall\" and then \"Install\" the keyboard." +msgstr "KUSKURE: metadata ta madannai ta lalace. \"A cire\" kuma a sake \"Saka\" madanni." + +#: keyman_config/keyboard_details.py:74 +msgid "Package name: " +msgstr "Sunan ƙunshin: " + +#: keyman_config/keyboard_details.py:85 +msgid "Package id: " +msgstr "Lambar ƙunshin: " + +#: keyman_config/keyboard_details.py:108 +msgid "Package description: " +msgstr "Bayanin ƙunshin: " + +#: keyman_config/keyboard_details.py:121 +msgid "Package author: " +msgstr "Mawallafin ƙunshi: " + +#: keyman_config/keyboard_details.py:133 +msgid "Package copyright: " +msgstr "Mallakar ƙunshin: " + +#: keyman_config/keyboard_details.py:174 +msgid "Keyboard filename: " +msgstr "Sunan ma'ajiyar madanni: " + +#: keyman_config/keyboard_details.py:187 +msgid "Keyboard name: " +msgstr "Sunan madanni: " + +#: keyman_config/keyboard_details.py:198 +msgid "Keyboard id: " +msgstr "Id na Madanni: " + +#: keyman_config/keyboard_details.py:209 +msgid "Keyboard version: " +msgstr "Nau'in madanni: " + +#: keyman_config/keyboard_details.py:221 +msgid "Keyboard author: " +msgstr "Mawallafin madanni: " + +#: keyman_config/keyboard_details.py:232 +msgid "Keyboard license: " +msgstr "Lasisin madanni: " + +#: keyman_config/keyboard_details.py:243 +msgid "Keyboard description: " +msgstr "Bayanan madanni: " + +#: keyman_config/keyboard_details.py:334 +#, python-brace-format +msgid "Scan this code to load this keyboard\n" +"on another device or share online" +msgstr "Yi sikanin na wannan code domin loda wannan madanni kan wata na'urar ko share online" + +#: keyman_config/options.py:24 +#, python-brace-format +msgid "{packageId} Settings" +msgstr "Saitin {packageId}" + +#: keyman_config/view_installed.py:30 +msgid "Keyman Configuration" +msgstr "Haɗa keyman" + +#: keyman_config/view_installed.py:60 +msgid "Choose a kmp file..." +msgstr "A zabi fayil na kmp..." + +#. i18n: file type in file selection dialog +#: keyman_config/view_installed.py:65 +msgid "KMP files" +msgstr "KMP files" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:141 +msgid "Icon" +msgstr "Alama" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:145 +msgid "Name" +msgstr "Suna" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:148 +msgid "Version" +msgstr "Nau'i" + +#: keyman_config/view_installed.py:161 +msgid "_Uninstall" +msgstr "_A cire" + +#: keyman_config/view_installed.py:162 keyman_config/view_installed.py:304 +msgid "Uninstall keyboard" +msgstr "A cire madanni" + +#: keyman_config/view_installed.py:167 +msgid "_About" +msgstr "_Game da" + +#: keyman_config/view_installed.py:168 keyman_config/view_installed.py:306 +msgid "About keyboard" +msgstr "Game da madanni" + +#: keyman_config/view_installed.py:173 +msgid "_Help" +msgstr "_Taimako" + +#: keyman_config/view_installed.py:174 keyman_config/view_installed.py:305 +msgid "Help for keyboard" +msgstr "Taimako don madanni" + +#: keyman_config/view_installed.py:179 +msgid "_Options" +msgstr "Zaɓuɓɓuka" + +#: keyman_config/view_installed.py:180 keyman_config/view_installed.py:307 +msgid "Settings for keyboard" +msgstr "Saiti don madanni" + +#: keyman_config/view_installed.py:190 +msgid "_Refresh" +msgstr "_Sabunta" + +#: keyman_config/view_installed.py:191 +msgid "Refresh keyboard list" +msgstr "Sabunta jerin madanni" + +#: keyman_config/view_installed.py:195 +msgid "_Download" +msgstr "_Saukewa" + +#: keyman_config/view_installed.py:196 +msgid "Download and install a keyboard from the Keyman website" +msgstr "A sauke kuma a shigar da madanni daga adireshin yanar gizo na Keyman" + +#: keyman_config/view_installed.py:201 +msgid "Install a keyboard from a file" +msgstr "A sauke madanni daga ma'ajiya" + +#: keyman_config/view_installed.py:206 +msgid "Close window" +msgstr "Rufe window" + +#: keyman_config/view_installed.py:278 +#, python-brace-format +msgid "Uninstall keyboard {package}" +msgstr "A cire {package} madanni" + +#: keyman_config/view_installed.py:280 +#, python-brace-format +msgid "Help for keyboard {package}" +msgstr "Taimako akan madannai {package}" + +#: keyman_config/view_installed.py:282 +#, python-brace-format +msgid "About keyboard {package}" +msgstr "Game da madanni {package}" + +#: keyman_config/view_installed.py:284 +#, python-brace-format +msgid "Settings for keyboard {package}" +msgstr "Saiti na madanni {package}" + +#: keyman_config/view_installed.py:349 +msgid "Uninstall keyboard package?" +msgstr "Sauke ƙunshin madanni?" + +#: keyman_config/view_installed.py:351 +#, python-brace-format +msgid "Are you sure that you want to uninstall the {keyboard} keyboard and its fonts?" +msgstr "Shin an tabbata ana son cire madannin {keyboard} da font ɗin sa?" + +#: keyman_config/welcome.py:22 +#, python-brace-format +msgid "{name} installed" +msgstr "{name} an shigar" + +#: keyman_config/welcome.py:40 +msgid "Open in _Web browser" +msgstr "A buɗe a _Web browser" + +#: keyman_config/welcome.py:42 +msgid "Open in the default web browser to do things like printing" +msgstr "A buɗe a browser ta asali don yin abubuwa kamar ɗab'i" + +#: keyman_config/welcome.py:45 +msgid "_OK" +msgstr "_OK" + diff --git a/linux/keyman-config/locale/mfi_NG.po b/linux/keyman-config/locale/mfi_NG.po new file mode 100644 index 0000000000..4b95694d78 --- /dev/null +++ b/linux/keyman-config/locale/mfi_NG.po @@ -0,0 +1,332 @@ +msgid "" +msgstr "" +"Project-Id-Version: keyman\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-08-19 19:17+0200\n" +"PO-Revision-Date: 2021-10-26 08:15\n" +"Last-Translator: \n" +"Language-Team: Mandara (Wandala)\n" +"Language: mfi_NG\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Crowdin-Project: keyman\n" +"X-Crowdin-Project-ID: 386703\n" +"X-Crowdin-Language: mfi\n" +"X-Crowdin-File: /master/linux/keyman-config.pot\n" +"X-Crowdin-File-ID: 504\n" + +#: keyman_config/__init__.py:68 +msgid "Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting." +msgstr "Ɓáaka zlage sentry-sdk mtu raven am táte ŋanna. Ɓáaka a ɓilanahe Sentry basha kuskure." + +#: keyman_config/downloadkeyboard.py:23 +msgid "Download Keyman keyboards" +msgstr "Tsukwana-tsukwa Keyman keyboards" + +#: keyman_config/downloadkeyboard.py:37 keyman_config/keyboard_details.py:49 +#: keyman_config/keyboard_details.py:340 keyman_config/view_installed.py:205 +msgid "_Close" +msgstr "_Hiɗante" + +#: keyman_config/install_kmp.py:99 +msgid "You do not have permissions to install the keyboard files to the shared area /usr/local/share/keyman" +msgstr "Ɓáaka izini gé femhe keyboard files gé diga maga slera /usr/local/share/keyman" + +#: keyman_config/install_kmp.py:103 +msgid "You do not have permissions to install the documentation to the shared documentation area /usr/local/share/doc/keyman" +msgstr "Ɓáaka izini gé femhe bayani kaɗkaɗe gé mátá diga bayani ŋanna /usr/local/share/doc/keyman" + +#: keyman_config/install_kmp.py:107 +msgid "You do not have permissions to install the font files to the shared font area /usr/local/share/fonts" +msgstr "Ɓáaka izini gé femhe gé font files am mátá diga font /usr/local/share/fonts" + +#: keyman_config/install_kmp.py:179 +#, python-brace-format +msgid "install_kmp.py: error: No kmp.json or kmp.inf found in {package}" +msgstr "femfe_kmp.py: error: No kmp.json or kmp.inf found in {package}" + +#: keyman_config/install_kmp.py:246 +#, python-brace-format +msgid "install_kmp.py: error: No kmp.json or kmp.inf found in {packageFile}" +msgstr "femfe_kmp.py: error: No kmp.json or kmp.inf am {packageFile}" + +#: keyman_config/install_window.py:54 +#, python-brace-format +msgid "Installing keyboard/package {keyboardid}" +msgstr "Femhe keyboard/pakeji {keyboardid}" + +#: keyman_config/install_window.py:72 keyman_config/install_window.py:93 +msgid "Keyboard is installed already" +msgstr "Keyboard aŋkwa femfande" + +#: keyman_config/install_window.py:74 +#, python-brace-format +msgid "The {name} keyboard is already installed at version {version}. Do you want to uninstall then reinstall it?" +msgstr "Ŋane {name} keyboard aŋkwa an fa am version {version}. Ká kátáhe tiɗase antara femhe adaliya?" + +#: keyman_config/install_window.py:95 +#, python-brace-format +msgid "The {name} keyboard is already installed with a newer version {installedversion}. Do you want to uninstall it and install the older version {version}?" +msgstr "Ŋane {name} keyboard aŋkwa an fa am daula version {installedversion}. Ká kátáhe tiɗase antara femhe emgyigwa version {version}?" + +#: keyman_config/install_window.py:128 +msgid "Keyboard layouts: " +msgstr "Ndraha Keyboard: " + +#: keyman_config/install_window.py:147 +msgid "Fonts: " +msgstr "Fonts: " + +#: keyman_config/install_window.py:167 keyman_config/keyboard_details.py:96 +msgid "Package version: " +msgstr "Pakeji version: " + +#: keyman_config/install_window.py:179 +msgid "Author: " +msgstr "Dadda vinda: " + +#: keyman_config/install_window.py:197 +msgid "Website: " +msgstr "Website: " + +#: keyman_config/install_window.py:211 +msgid "Copyright: " +msgstr "Hakkin malaka: " + +#: keyman_config/install_window.py:245 +msgid "Details" +msgstr "Baɗumma bayani" + +#: keyman_config/install_window.py:248 +msgid "README" +msgstr "Karanta-karata ya" + +#: keyman_config/install_window.py:256 keyman_config/view_installed.py:200 +msgid "_Install" +msgstr "Femhe" + +#: keyman_config/install_window.py:260 +msgid "_Cancel" +msgstr "Tsanahe" + +#: keyman_config/install_window.py:305 +#, python-brace-format +msgid "Keyboard {name} installed" +msgstr "Keyboard {name} an fa" + +#: keyman_config/install_window.py:310 keyman_config/install_window.py:315 +#, python-brace-format +msgid "Keyboard {name} could not be installed." +msgstr "Keyboard {name} a magavka femhe." + +#: keyman_config/install_window.py:311 +msgid "Error Message:" +msgstr "Elva kuskure:" + +#: keyman_config/install_window.py:316 +msgid "Warning Message:" +msgstr "Elva sawari gé fa taɗa:" + +#: keyman_config/keyboard_details.py:37 +#, python-brace-format +msgid "{name} keyboard" +msgstr "{name} keyboard" + +#: keyman_config/keyboard_details.py:53 +msgid "ERROR: Keyboard metadata is damaged.\n" +"Please \"Uninstall\" and then \"Install\" the keyboard." +msgstr "KUSKURE: Keyboard metadata badza-badze.\n" +"Tazlaŋa \"tiɗase-tiɗe\" antara ká \"Femhe\" keyboard ŋanna." + +#: keyman_config/keyboard_details.py:74 +msgid "Package name: " +msgstr "Zhera pakeji: " + +#: keyman_config/keyboard_details.py:85 +msgid "Package id: " +msgstr "Id pakeji: " + +#: keyman_config/keyboard_details.py:108 +msgid "Package description: " +msgstr "Bayani a pakeji: " + +#: keyman_config/keyboard_details.py:121 +msgid "Package author: " +msgstr "Dadda vinda pakeji: " + +#: keyman_config/keyboard_details.py:133 +msgid "Package copyright: " +msgstr "Hakkin malaka pakeji: " + +#: keyman_config/keyboard_details.py:174 +msgid "Keyboard filename: " +msgstr "Keyboard zherafile: " + +#: keyman_config/keyboard_details.py:187 +msgid "Keyboard name: " +msgstr "Keyboard zherafile: " + +#: keyman_config/keyboard_details.py:198 +msgid "Keyboard id: " +msgstr "Keyboard id: " + +#: keyman_config/keyboard_details.py:209 +msgid "Keyboard version: " +msgstr "Keyboard version: " + +#: keyman_config/keyboard_details.py:221 +msgid "Keyboard author: " +msgstr "Dadda vinda Keyboard: " + +#: keyman_config/keyboard_details.py:232 +msgid "Keyboard license: " +msgstr "Keyboard lasisi: " + +#: keyman_config/keyboard_details.py:243 +msgid "Keyboard description: " +msgstr "Bayani Keyboard: " + +#: keyman_config/keyboard_details.py:334 +#, python-brace-format +msgid "Scan this code to load this keyboard\n" +"on another device or share online" +msgstr "Ksa fatawa code gé femhe una gé keyboard\n" +"am nazu umbulé gé nawura mtu share online" + +#: keyman_config/options.py:24 +#, python-brace-format +msgid "{packageId} Settings" +msgstr "{packageId} Saitin" + +#: keyman_config/view_installed.py:30 +msgid "Keyman Configuration" +msgstr "Keyman Configuration" + +#: keyman_config/view_installed.py:60 +msgid "Choose a kmp file..." +msgstr "Ksa kmp file..." + +#. i18n: file type in file selection dialog +#: keyman_config/view_installed.py:65 +msgid "KMP files" +msgstr "KMP files" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:141 +msgid "Icon" +msgstr "Ikon" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:145 +msgid "Name" +msgstr "Zhere" + +#. i18n: column header in table displaying installed keyboards +#: keyman_config/view_installed.py:148 +msgid "Version" +msgstr "Version" + +#: keyman_config/view_installed.py:161 +msgid "_Uninstall" +msgstr "Tiɗase ashigashe" + +#: keyman_config/view_installed.py:162 keyman_config/view_installed.py:304 +msgid "Uninstall keyboard" +msgstr "Tiɗase ashigashe keyboard" + +#: keyman_config/view_installed.py:167 +msgid "_About" +msgstr "_Aɗaba" + +#: keyman_config/view_installed.py:168 keyman_config/view_installed.py:306 +msgid "About keyboard" +msgstr "Aɗaba keyboard" + +#: keyman_config/view_installed.py:173 +msgid "_Help" +msgstr "_Mla" + +#: keyman_config/view_installed.py:174 keyman_config/view_installed.py:305 +msgid "Help for keyboard" +msgstr "Mla gé keyboard" + +#: keyman_config/view_installed.py:179 +msgid "_Options" +msgstr "Naza ksa gergre" + +#: keyman_config/view_installed.py:180 keyman_config/view_installed.py:307 +msgid "Settings for keyboard" +msgstr "Saitin aza keyboard" + +#: keyman_config/view_installed.py:190 +msgid "_Refresh" +msgstr "_Refresh" + +#: keyman_config/view_installed.py:191 +msgid "Refresh keyboard list" +msgstr "Refresh keyboard list" + +#: keyman_config/view_installed.py:195 +msgid "_Download" +msgstr "_Tsukwanahe" + +#: keyman_config/view_installed.py:196 +msgid "Download and install a keyboard from the Keyman website" +msgstr "Tsukwanahe antara femhe keyboard a sawam Keyman website" + +#: keyman_config/view_installed.py:201 +msgid "Install a keyboard from a file" +msgstr "Femhe keyboard a sawam file" + +#: keyman_config/view_installed.py:206 +msgid "Close window" +msgstr "Hiɗante window" + +#: keyman_config/view_installed.py:278 +#, python-brace-format +msgid "Uninstall keyboard {package}" +msgstr "Tiɗase keyboard {package}" + +#: keyman_config/view_installed.py:280 +#, python-brace-format +msgid "Help for keyboard {package}" +msgstr "Mla aza keyboard {package}" + +#: keyman_config/view_installed.py:282 +#, python-brace-format +msgid "About keyboard {package}" +msgstr "Aɗaba keyboard {package}" + +#: keyman_config/view_installed.py:284 +#, python-brace-format +msgid "Settings for keyboard {package}" +msgstr "Saitin gé keyboard {package}" + +#: keyman_config/view_installed.py:349 +msgid "Uninstall keyboard package?" +msgstr "Tiɗase keyboard pakeji?" + +#: keyman_config/view_installed.py:351 +#, python-brace-format +msgid "Are you sure that you want to uninstall the {keyboard} keyboard and its fonts?" +msgstr "Ba jirahe ká kátá tiɗase {keyboard} keyboard antara fonts ne ŋanna?" + +#: keyman_config/welcome.py:22 +#, python-brace-format +msgid "{name} installed" +msgstr "{name} an fa" + +#: keyman_config/welcome.py:40 +msgid "Open in _Web browser" +msgstr "Wurante-wura _Web browser" + +#: keyman_config/welcome.py:42 +msgid "Open in the default web browser to do things like printing" +msgstr "Wurante-wura am default web browser gé shafte magahe bárá seke fatawe" + +#: keyman_config/welcome.py:45 +msgid "_OK" +msgstr "_Bastuwa" + diff --git a/mac/Cartfile.private b/mac/Cartfile.private index 3a70652b4d..9efa3fdcfb 100644 --- a/mac/Cartfile.private +++ b/mac/Cartfile.private @@ -1 +1 @@ -github "erikdoe/ocmock" ~> 3.4.1 \ No newline at end of file +github "erikdoe/ocmock" ~> 3.9.1 diff --git a/mac/Cartfile.resolved b/mac/Cartfile.resolved index f914031435..c743c88996 100644 --- a/mac/Cartfile.resolved +++ b/mac/Cartfile.resolved @@ -1 +1 @@ -github "erikdoe/ocmock" "v3.4.1" +github "erikdoe/ocmock" "v3.9.1" diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index bbeb9f75df..4978b10031 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -7,9 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 293EA3E627140D8100545EED /* KMAboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 293EA3E827140D8100545EED /* KMAboutWindowController.xib */; }; + 293EA3EB27140DEC00545EED /* preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 293EA3ED27140DEC00545EED /* preferences.xib */; }; + 293EA3F427181FDA00545EED /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 293EA3F627181FDA00545EED /* Localizable.strings */; }; + 29B42A572728339900EDD5D3 /* KMInfoWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29B42A592728339900EDD5D3 /* KMInfoWindowController.xib */; }; + 29B42A602728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29B42A622728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib */; }; 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37A245C02565DFA6000BBF92 /* Assets.xcassets */; }; 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */ = {isa = PBXBuildFile; fileRef = 37AE5C9C239A7B770086CC7C /* qrcode.min.js */; }; 37C2B0CB25FF2C350092E16A /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 37C2B0CA25FF2C340092E16A /* Help */; }; + 37CA0FD12746010300266259 /* OCMock.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37CA0FCE2745DCFE00266259 /* OCMock.xcframework */; }; 9800EC5A1C02940300BF0FB5 /* keyman-for-mac-os-license.html in Resources */ = {isa = PBXBuildFile; fileRef = 9800EC591C02940300BF0FB5 /* keyman-for-mac-os-license.html */; }; 9803C9D81B0EFB3B0082A6F9 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9803C9D71B0EFB3B0082A6F9 /* WebKit.framework */; }; 983247281A9EA28F0010B90C /* KeymanEngine4Mac.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = 984C2B211A79DF1B0023F89D /* KeymanEngine4Mac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -32,7 +38,6 @@ 98A778C41A8C53BF00CF809D /* KMInputMethodAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 98A778C31A8C53BF00CF809D /* KMInputMethodAppDelegate.m */; }; 98BCD1F61B4E5998004E1AFD /* NSString+SuppMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = 98BCD1F51B4E5998004E1AFD /* NSString+SuppMethods.m */; }; 98BF92441BF01CBE0002126A /* KMAboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 98BF92421BF01CBE0002126A /* KMAboutWindowController.m */; }; - 98BF92451BF01CBE0002126A /* KMAboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98BF92431BF01CBE0002126A /* KMAboutWindowController.xib */; }; 98BF92471BF01D890002126A /* image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 98BF92461BF01D890002126A /* image.jpg */; }; 98BF924F1BF02DC20002126A /* KMBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 98BF924E1BF02DC20002126A /* KMBarView.m */; }; 98BF92521BF03D4D0002126A /* KMAboutBGView.m in Sources */ = {isa = PBXBuildFile; fileRef = 98BF92511BF03D4D0002126A /* KMAboutBGView.m */; }; @@ -44,12 +49,9 @@ 98E6729F1B532F5E00DBDE2F /* KMDownloadKBWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 98E6729D1B532F5D00DBDE2F /* KMDownloadKBWindowController.m */; }; 98E672A01B532F5E00DBDE2F /* KMDownloadKBWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98E6729E1B532F5E00DBDE2F /* KMDownloadKBWindowController.xib */; }; 98E672A41B532F9900DBDE2F /* KMKeyboardHelpWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 98E672A21B532F9900DBDE2F /* KMKeyboardHelpWindowController.m */; }; - 98E672A51B532F9900DBDE2F /* KMKeyboardHelpWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98E672A31B532F9900DBDE2F /* KMKeyboardHelpWindowController.xib */; }; 98F3C8931A8D739500F675E3 /* KMConfigurationWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 98F3C8911A8D739500F675E3 /* KMConfigurationWindowController.m */; }; - 98F3C8941A8D739500F675E3 /* preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98F3C8921A8D739500F675E3 /* preferences.xib */; }; 98FE105E1B4DE8F400525F54 /* NSWindow+SuppMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = 98FE105D1B4DE8F400525F54 /* NSWindow+SuppMethods.m */; }; 98FE10631B4DEE5600525F54 /* KMInfoWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 98FE10611B4DEE5600525F54 /* KMInfoWindowController.m */; }; - 98FE10641B4DEE5600525F54 /* KMInfoWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98FE10621B4DEE5600525F54 /* KMInfoWindowController.xib */; }; 9A3D6C5D221531B0008785A3 /* KMOSVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A3D6C5C221531B0008785A3 /* KMOSVersion.m */; }; B90818AF7ED302187DE0E026 /* Pods_Keyman.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B644E63217FFE54B91C71C94 /* Pods_Keyman.framework */; }; E211769D20E182DD00F8065D /* NoContextTestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E211769C20E182DD00F8065D /* NoContextTestClient.m */; }; @@ -65,14 +67,11 @@ E2585A7D20DD68BA00CBB994 /* KMInputMethodEventHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2585A7C20DD68BA00CBB994 /* KMInputMethodEventHandlerTests.m */; }; E2585A7F20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx in Resources */ = {isa = PBXBuildFile; fileRef = E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */; }; E2585A8120DD7CF100CBB994 /* TestAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E2585A8020DD7CF100CBB994 /* TestAppDelegate.m */; }; - E27BA9CE2020322000D273E7 /* OCMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E27BA9CD2020322000D273E7 /* OCMock.framework */; }; - E27BA9D1202036E200D273E7 /* OCMock.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = E27BA9D0202036E200D273E7 /* OCMock.framework.dSYM */; }; E27BA9D32020D8BF00D273E7 /* SimpleTest.kmx in Resources */ = {isa = PBXBuildFile; fileRef = E27BA9D22020D8BF00D273E7 /* SimpleTest.kmx */; }; E28914D32024D1B80048E71E /* KeymanEngine4Mac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 984C2B211A79DF1B0023F89D /* KeymanEngine4Mac.framework */; }; E28914D52024E3DF0048E71E /* KMInputMethodBrowserClientEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E24C79E91FFFCC7500D8E46F /* KMInputMethodBrowserClientEventHandler.m */; }; E28914D62024E4010048E71E /* KMInputMethodEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */; }; E28914D72024E6220048E71E /* KeymanEngine4Mac.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 984C2B211A79DF1B0023F89D /* KeymanEngine4Mac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E28914D92028A78C0048E71E /* OCMock.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = E28914D82028A78C0048E71E /* OCMock.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E29F22F4201A59B1005EA234 /* KMInputMethodBrowserClientEventHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E29F22F3201A59B1005EA234 /* KMInputMethodBrowserClientEventHandlerTests.m */; }; E2D5529D20DD8E0200D77CFB /* LegacyTestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E2D5529C20DD8E0200D77CFB /* LegacyTestClient.m */; }; F9B9814A51575496B5DA2948 /* Pods_KeymanTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A71FD8BA8404531DFBD861B6 /* Pods_KeymanTests.framework */; }; @@ -116,15 +115,30 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - E28914D92028A78C0048E71E /* OCMock.framework in CopyFiles */, E28914D72024E6220048E71E /* KeymanEngine4Mac.framework in CopyFiles */, - E27BA9D1202036E200D273E7 /* OCMock.framework.dSYM in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 293EA3E527140D3A00545EED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 293EA3E727140D8100545EED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/KMAboutWindowController.xib; sourceTree = ""; }; + 293EA3EA27140D8E00545EED /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/KMAboutWindowController.strings; sourceTree = ""; }; + 293EA3EC27140DEC00545EED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/preferences.xib; sourceTree = ""; }; + 293EA3EF27140DFA00545EED /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/preferences.strings; sourceTree = ""; }; + 293EA3F02714158600545EED /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = ""; }; + 293EA3F527181FDA00545EED /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 29B348AA2726F1C1009B1C82 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/KMAboutWindowController.strings; sourceTree = ""; }; + 29B348AB2726F1C2009B1C82 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/preferences.strings; sourceTree = ""; }; + 29B348AC2726F1C2009B1C82 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/MainMenu.strings; sourceTree = ""; }; + 29B348AE2726F1C7009B1C82 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/Localizable.strings; sourceTree = ""; }; + 29B42A582728339900EDD5D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/KMInfoWindowController.xib; sourceTree = ""; }; + 29B42A5B2728339E00EDD5D3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/KMInfoWindowController.strings; sourceTree = ""; }; + 29B42A5F272833A600EDD5D3 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/KMInfoWindowController.strings; sourceTree = ""; }; + 29B42A612728343B00EDD5D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/KMKeyboardHelpWindowController.xib; sourceTree = ""; }; + 29B42A642728343F00EDD5D3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/KMKeyboardHelpWindowController.strings; sourceTree = ""; }; + 29B42A682728344600EDD5D3 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/KMKeyboardHelpWindowController.strings; sourceTree = ""; }; 370E44542564E1EE00642929 /* Sentry.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Sentry.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 37589AD422E52DA20097D39D /* Keyman.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Keyman.entitlements; sourceTree = ""; }; 37A00ED4239AA25700D2C837 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -132,7 +146,6 @@ 37A00ED7239AA25700D2C837 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 37A00ED8239AA25700D2C837 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 37A00EDA239AA25D00D2C837 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 37A00EDD239AA25D00D2C837 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 37A00EDF239AA25D00D2C837 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37A00EE0239AA25D00D2C837 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 37A00EE2239AA25D00D2C837 /* ConfigurationHost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ConfigurationHost.entitlements; sourceTree = ""; }; @@ -141,6 +154,7 @@ 37A245C02565DFA6000BBF92 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37AE5C9C239A7B770086CC7C /* qrcode.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = qrcode.min.js; sourceTree = ""; }; 37C2B0CA25FF2C340092E16A /* Help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Help; path = Keyman4MacIM/Help; sourceTree = ""; }; + 37CA0FCE2745DCFE00266259 /* OCMock.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OCMock.xcframework; path = ../Carthage/Build/OCMock.xcframework; sourceTree = ""; }; 7844A655E38A234DE5D29DF9 /* Pods-Keyman.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Keyman.release.xcconfig"; path = "Pods/Target Support Files/Pods-Keyman/Pods-Keyman.release.xcconfig"; sourceTree = ""; }; 8577C0C788461AFE564DDF4F /* Pods-KeymanTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KeymanTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KeymanTests/Pods-KeymanTests.debug.xcconfig"; sourceTree = ""; }; 878287E1B18ED4EE910C4BC7 /* Pods-Keyman.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Keyman.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Keyman/Pods-Keyman.debug.xcconfig"; sourceTree = ""; }; @@ -165,7 +179,6 @@ 984B8F421AF1C3D900E096A8 /* OSKWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OSKWindowController.m; path = OnScreenKeyboard/OSKWindowController.m; sourceTree = ""; }; 984B8F431AF1C3D900E096A8 /* OSKWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = OSKWindowController.xib; path = OnScreenKeyboard/OSKWindowController.xib; sourceTree = ""; }; 984C2B211A79DF1B0023F89D /* KeymanEngine4Mac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KeymanEngine4Mac.framework; path = Frameworks/KeymanEngine4Mac.framework; sourceTree = ""; }; - 985D566F1A79F8EA0090ACC6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 9874C31E1B536847000BB543 /* info.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = info.png; sourceTree = ""; }; 9874C31F1B536847000BB543 /* keyman.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = keyman.png; sourceTree = ""; }; 9874C3201B536847000BB543 /* SideImage.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = SideImage.bmp; sourceTree = ""; }; @@ -180,7 +193,6 @@ 98BDD3681BC3511200FAC7C4 /* Keyman4MacIM.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Keyman4MacIM.entitlements; sourceTree = ""; }; 98BF92411BF01CBE0002126A /* KMAboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMAboutWindowController.h; sourceTree = ""; }; 98BF92421BF01CBE0002126A /* KMAboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMAboutWindowController.m; sourceTree = ""; }; - 98BF92431BF01CBE0002126A /* KMAboutWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = KMAboutWindowController.xib; sourceTree = ""; }; 98BF92461BF01D890002126A /* image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = image.jpg; sourceTree = ""; }; 98BF924D1BF02DC20002126A /* KMBarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMBarView.h; sourceTree = ""; }; 98BF924E1BF02DC20002126A /* KMBarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMBarView.m; sourceTree = ""; }; @@ -198,15 +210,12 @@ 98E6729E1B532F5E00DBDE2F /* KMDownloadKBWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = KMDownloadKBWindowController.xib; path = Keyman4MacIM/KMDownloadKeyboard/KMDownloadKBWindowController.xib; sourceTree = ""; }; 98E672A11B532F9900DBDE2F /* KMKeyboardHelpWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KMKeyboardHelpWindowController.h; path = Keyman4MacIM/KMKeyboardHelpWindow/KMKeyboardHelpWindowController.h; sourceTree = ""; }; 98E672A21B532F9900DBDE2F /* KMKeyboardHelpWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KMKeyboardHelpWindowController.m; path = Keyman4MacIM/KMKeyboardHelpWindow/KMKeyboardHelpWindowController.m; sourceTree = ""; }; - 98E672A31B532F9900DBDE2F /* KMKeyboardHelpWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = KMKeyboardHelpWindowController.xib; path = Keyman4MacIM/KMKeyboardHelpWindow/KMKeyboardHelpWindowController.xib; sourceTree = ""; }; 98F3C8901A8D739500F675E3 /* KMConfigurationWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KMConfigurationWindowController.h; path = Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.h; sourceTree = ""; }; 98F3C8911A8D739500F675E3 /* KMConfigurationWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KMConfigurationWindowController.m; path = Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m; sourceTree = ""; }; - 98F3C8921A8D739500F675E3 /* preferences.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = preferences.xib; path = Keyman4MacIM/KMConfiguration/preferences.xib; sourceTree = ""; }; 98FE105C1B4DE8F400525F54 /* NSWindow+SuppMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSWindow+SuppMethods.h"; path = "Categories/NSWindow+SuppMethods.h"; sourceTree = ""; }; 98FE105D1B4DE8F400525F54 /* NSWindow+SuppMethods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSWindow+SuppMethods.m"; path = "Categories/NSWindow+SuppMethods.m"; sourceTree = ""; }; 98FE10601B4DEE5600525F54 /* KMInfoWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KMInfoWindowController.h; path = Keyman4MacIM/KMInfoWindow/KMInfoWindowController.h; sourceTree = ""; }; 98FE10611B4DEE5600525F54 /* KMInfoWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KMInfoWindowController.m; path = Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m; sourceTree = ""; }; - 98FE10621B4DEE5600525F54 /* KMInfoWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = KMInfoWindowController.xib; path = Keyman4MacIM/KMInfoWindow/KMInfoWindowController.xib; sourceTree = ""; }; 9A3D6C5B221531B0008785A3 /* KMOSVersion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMOSVersion.h; sourceTree = ""; }; 9A3D6C5C221531B0008785A3 /* KMOSVersion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMOSVersion.m; sourceTree = ""; }; A71FD8BA8404531DFBD861B6 /* Pods_KeymanTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KeymanTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -234,9 +243,7 @@ E2585A8020DD7CF100CBB994 /* TestAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestAppDelegate.m; sourceTree = ""; }; E2585A8220DD7D3F00CBB994 /* TestAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestAppDelegate.h; sourceTree = ""; }; E27BA9CD2020322000D273E7 /* OCMock.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCMock.framework; path = ../Carthage/Build/Mac/OCMock.framework; sourceTree = ""; }; - E27BA9D0202036E200D273E7 /* OCMock.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = OCMock.framework.dSYM; path = ../Carthage/Build/Mac/OCMock.framework.dSYM; sourceTree = ""; }; E27BA9D22020D8BF00D273E7 /* SimpleTest.kmx */ = {isa = PBXFileReference; lastKnownFileType = file; path = SimpleTest.kmx; sourceTree = ""; }; - E28914D82028A78C0048E71E /* OCMock.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCMock.framework; path = ../Carthage/Build/Mac/OCMock.framework; sourceTree = ""; }; E29F22F1201A59B1005EA234 /* KeymanTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KeymanTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E29F22F3201A59B1005EA234 /* KMInputMethodBrowserClientEventHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodBrowserClientEventHandlerTests.m; sourceTree = ""; }; E29F22F5201A59B2005EA234 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -264,8 +271,8 @@ buildActionMask = 2147483647; files = ( E28914D32024D1B80048E71E /* KeymanEngine4Mac.framework in Frameworks */, - E27BA9CE2020322000D273E7 /* OCMock.framework in Frameworks */, F9B9814A51575496B5DA2948 /* Pods_KeymanTests.framework in Frameworks */, + 37CA0FD12746010300266259 /* OCMock.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -291,7 +298,6 @@ 37A00ED7239AA25700D2C837 /* ViewController.h */, 37A00ED8239AA25700D2C837 /* ViewController.m */, 37A00EDA239AA25D00D2C837 /* Assets.xcassets */, - 37A00EDC239AA25D00D2C837 /* Main.storyboard */, 37A00EDF239AA25D00D2C837 /* Info.plist */, 37A00EE0239AA25D00D2C837 /* main.m */, 37A00EE2239AA25D00D2C837 /* ConfigurationHost.entitlements */, @@ -309,7 +315,7 @@ 983247B11A9EB9690010B90C /* KMConfigColumn3CellView.m */, 98F3C8901A8D739500F675E3 /* KMConfigurationWindowController.h */, 98F3C8911A8D739500F675E3 /* KMConfigurationWindowController.m */, - 98F3C8921A8D739500F675E3 /* preferences.xib */, + 293EA3ED27140DEC00545EED /* preferences.xib */, ); name = KMConfiguration; path = ..; @@ -381,8 +387,6 @@ 37589AD422E52DA20097D39D /* Keyman.entitlements */, E2C92D4720B46CEA00742A7D /* KeymanEngine4Mac.framework.dSYM */, E211CCF520B600A500505C36 /* KeymanEngine4Mac.framework.dSYM */, - E28914D82028A78C0048E71E /* OCMock.framework */, - E27BA9D0202036E200D273E7 /* OCMock.framework.dSYM */, 989C9C0A1A7876DE00A20425 /* Keyman4MacIM */, E29F22F2201A59B1005EA234 /* KeymanTests */, 37A00ED3239AA25700D2C837 /* ConfigurationHost */, @@ -433,6 +437,7 @@ E240F598202DED740000067D /* KMPackage.m */, 9A3D6C5B221531B0008785A3 /* KMOSVersion.h */, 9A3D6C5C221531B0008785A3 /* KMOSVersion.m */, + 293EA3F627181FDA00545EED /* Localizable.strings */, ); path = Keyman4MacIM; sourceTree = ""; @@ -461,7 +466,7 @@ 98BF92561BF0431F0002126A /* KMAboutWindow.m */, 98BF92411BF01CBE0002126A /* KMAboutWindowController.h */, 98BF92421BF01CBE0002126A /* KMAboutWindowController.m */, - 98BF92431BF01CBE0002126A /* KMAboutWindowController.xib */, + 293EA3E827140D8100545EED /* KMAboutWindowController.xib */, E213601D2142D7BF00A043B7 /* keyman-88.png */, E23380FA21407AA100B90591 /* SILInBlue76.png */, 98BF92461BF01D890002126A /* image.jpg */, @@ -485,7 +490,7 @@ children = ( 98E672A11B532F9900DBDE2F /* KMKeyboardHelpWindowController.h */, 98E672A21B532F9900DBDE2F /* KMKeyboardHelpWindowController.m */, - 98E672A31B532F9900DBDE2F /* KMKeyboardHelpWindowController.xib */, + 29B42A622728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib */, ); name = KMKeyboardHelpWindow; path = ..; @@ -507,7 +512,7 @@ children = ( 98FE10601B4DEE5600525F54 /* KMInfoWindowController.h */, 98FE10611B4DEE5600525F54 /* KMInfoWindowController.m */, - 98FE10621B4DEE5600525F54 /* KMInfoWindowController.xib */, + 29B42A592728339900EDD5D3 /* KMInfoWindowController.xib */, ); name = KMInfoWindow; path = ..; @@ -516,6 +521,7 @@ E27BA9CC2020321F00D273E7 /* Frameworks */ = { isa = PBXGroup; children = ( + 37CA0FCE2745DCFE00266259 /* OCMock.xcframework */, 370E44542564E1EE00642929 /* Sentry.framework */, 37A00F2E239AD4D000D2C837 /* KeymanEngine4Mac.framework */, 37A00F2C239AD4C300D2C837 /* KeymanEngine4Mac.framework */, @@ -624,12 +630,12 @@ }; buildConfigurationList = 989C9C031A7876DE00A20425 /* Build configuration list for PBXProject "Keyman4MacIM" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, + km, ); mainGroup = 989C9BFF1A7876DE00A20425; productRefGroup = 989C9C091A7876DE00A20425 /* Products */; @@ -658,15 +664,16 @@ 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */, 37C2B0CB25FF2C350092E16A /* Help in Resources */, 984B8F451AF1C3D900E096A8 /* OSKWindowController.xib in Resources */, - 98FE10641B4DEE5600525F54 /* KMInfoWindowController.xib in Resources */, + 29B42A572728339900EDD5D3 /* KMInfoWindowController.xib in Resources */, E213601E2142D7C000A043B7 /* keyman-88.png in Resources */, + 293EA3F427181FDA00545EED /* Localizable.strings in Resources */, 98BF92471BF01D890002126A /* image.jpg in Resources */, - 98BF92451BF01CBE0002126A /* KMAboutWindowController.xib in Resources */, + 293EA3E627140D8100545EED /* KMAboutWindowController.xib in Resources */, 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */, - 98F3C8941A8D739500F675E3 /* preferences.xib in Resources */, + 293EA3EB27140DEC00545EED /* preferences.xib in Resources */, 9800EC5A1C02940300BF0FB5 /* keyman-for-mac-os-license.html in Resources */, 989C9C161A7876DE00A20425 /* MainMenu.xib in Resources */, - 98E672A51B532F9900DBDE2F /* KMKeyboardHelpWindowController.xib in Resources */, + 29B42A602728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib in Resources */, 98BF92541BF040A50002126A /* title.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -852,18 +859,64 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 37A00EDC239AA25D00D2C837 /* Main.storyboard */ = { + 293EA3E827140D8100545EED /* KMAboutWindowController.xib */ = { isa = PBXVariantGroup; children = ( - 37A00EDD239AA25D00D2C837 /* Base */, + 293EA3E727140D8100545EED /* Base */, + 293EA3EA27140D8E00545EED /* en */, + 29B348AA2726F1C1009B1C82 /* km */, ); - name = Main.storyboard; + name = KMAboutWindowController.xib; + sourceTree = ""; + }; + 293EA3ED27140DEC00545EED /* preferences.xib */ = { + isa = PBXVariantGroup; + children = ( + 293EA3EC27140DEC00545EED /* Base */, + 293EA3EF27140DFA00545EED /* en */, + 29B348AB2726F1C2009B1C82 /* km */, + ); + name = preferences.xib; + path = Keyman4MacIM/KMConfiguration; + sourceTree = ""; + }; + 293EA3F627181FDA00545EED /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 293EA3F527181FDA00545EED /* en */, + 29B348AE2726F1C7009B1C82 /* km */, + ); + name = Localizable.strings; + sourceTree = ""; + }; + 29B42A592728339900EDD5D3 /* KMInfoWindowController.xib */ = { + isa = PBXVariantGroup; + children = ( + 29B42A582728339900EDD5D3 /* Base */, + 29B42A5B2728339E00EDD5D3 /* en */, + 29B42A5F272833A600EDD5D3 /* km */, + ); + name = KMInfoWindowController.xib; + path = Keyman4MacIM/KMInfoWindow; + sourceTree = ""; + }; + 29B42A622728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib */ = { + isa = PBXVariantGroup; + children = ( + 29B42A612728343B00EDD5D3 /* Base */, + 29B42A642728343F00EDD5D3 /* en */, + 29B42A682728344600EDD5D3 /* km */, + ); + name = KMKeyboardHelpWindowController.xib; + path = Keyman4MacIM/KMKeyboardHelpWindow; sourceTree = ""; }; 989C9C141A7876DE00A20425 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( - 985D566F1A79F8EA0090ACC6 /* en */, + 293EA3E527140D3A00545EED /* Base */, + 293EA3F02714158600545EED /* en */, + 29B348AC2726F1C2009B1C82 /* km */, ); name = MainMenu.xib; sourceTree = ""; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/MainMenu.xib b/mac/Keyman4MacIM/Keyman4MacIM/Base.lproj/MainMenu.xib similarity index 86% rename from mac/Keyman4MacIM/Keyman4MacIM/en.lproj/MainMenu.xib rename to mac/Keyman4MacIM/Keyman4MacIM/Base.lproj/MainMenu.xib index 3f6143b5f9..6d755ac672 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/MainMenu.xib +++ b/mac/Keyman4MacIM/Keyman4MacIM/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - - + + - + @@ -31,6 +31,7 @@ + diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Info.plist b/mac/Keyman4MacIM/Keyman4MacIM/Info.plist index d87a7a7a9b..e2e495ea96 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Info.plist +++ b/mac/Keyman4MacIM/Keyman4MacIM/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - English + en CFBundleDocumentTypes diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib new file mode 100644 index 0000000000..b6c018d1a5 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/KMAboutWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/KMAboutWindowController.m index e4e9045c2d..ba7d1a030f 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/KMAboutWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/KMAboutWindowController.m @@ -32,9 +32,10 @@ [self.window setBackgroundColor:[NSColor whiteColor]]; KeymanVersionInfo versionInfo = [[self AppDelegate] versionInfo]; - [self.versionLabel setStringValue:[NSString stringWithFormat:@"Version %@", versionInfo.versionWithTag]]; - - NSMutableString *copyrightInfo = [[NSMutableString alloc] initWithString: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"NSHumanReadableCopyright"]]; + NSString *versionString = NSLocalizedString(@"version-label-text", nil); + [self.versionLabel setStringValue:[NSString localizedStringWithFormat:versionString, versionInfo.versionWithTag]]; + + NSMutableString *copyrightInfo = [[NSMutableString alloc] initWithString: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"NSHumanReadableCopyright"]]; [self.copyrightLabel setStringValue:copyrightInfo]; NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:[self.licenseButton bounds] diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/KMAboutWindowController.xib b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/KMAboutWindowController.xib deleted file mode 100644 index 8e341aca3a..0000000000 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/KMAboutWindowController.xib +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/en.lproj/KMAboutWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/en.lproj/KMAboutWindowController.strings new file mode 100644 index 0000000000..26f04eb8bd --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/en.lproj/KMAboutWindowController.strings @@ -0,0 +1,9 @@ + +/* button text to close the About window */ +"Kab-Up-fYH.title" = "Close"; + +/* text of link to the license agreement */ +"hYC-Bx-aoP.title" = "License Agreement"; + +/* button text to open Configuration window */ +"vkh-b5-vO7.title" = "Configuration..."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/km.lproj/KMAboutWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/km.lproj/KMAboutWindowController.strings new file mode 100644 index 0000000000..436e9eed41 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/km.lproj/KMAboutWindowController.strings @@ -0,0 +1,9 @@ + +/* Class = "NSButtonCell"; title = "Close"; ObjectID = "Kab-Up-fYH"; */ +"Kab-Up-fYH.title" = "បិទ"; + +/* Class = "NSButtonCell"; title = "License Agreement"; ObjectID = "hYC-Bx-aoP"; */ +"hYC-Bx-aoP.title" = "កិច្ចព្រមព្រៀងអាជ្ញាបណ្ណ"; + +/* Class = "NSButtonCell"; title = "Configuration"; ObjectID = "vkh-b5-vO7"; */ +"vkh-b5-vO7.title" = "ការកំណត់រចនាសម្ព័ន្ធ"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/preferences.xib b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/Base.lproj/preferences.xib similarity index 87% rename from mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/preferences.xib rename to mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/Base.lproj/preferences.xib index a545b5ba92..c327ce4ad2 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/preferences.xib +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/Base.lproj/preferences.xib @@ -1,9 +1,9 @@ - + - - + + @@ -26,7 +26,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -56,11 +56,11 @@ - + - + @@ -79,7 +79,7 @@ - + @@ -190,11 +190,11 @@ - - + - + YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T -S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCgpMDM2PD9VJG51bGzWDQ4PEBESExQVFhcYVk5T +S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCkqMTQ3PUBVJG51bGzWDQ4PEBESExQVFhcYVk5T U2l6ZV5OU1Jlc2l6aW5nTW9kZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAIQAIAN -EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdIlDyYnXxAU -TlNUSUZGUmVwcmVzZW50YXRpb26AB4AITxEIjE1NACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAAB -AAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEGAAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAAB -AAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEWAAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAAB -AAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFTAAMAAAACAAEAAYdzAAcAAAe8AAAA0AAAAAAAAAe8 -YXBwbAIgAABtbnRyR1JBWVhZWiAH0AACAA4ADAAAAABhY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAA -AAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAVkZXNjAAAAwAAAAG9kc2NtAAABMAAABi5jcHJ0AAAHYAAAADh3dHB0AAAHmAAAABRrVFJD -AAAHrAAAAA5kZXNjAAAAAAAAABVHZW5lcmljIEdyYXkgUHJvZmlsZQAAAAAAAAAAAAAAFUdlbmVyaWMg -R3JheSBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -bWx1YwAAAAAAAAAeAAAADHNrU0sAAAAqAAABeGRhREsAAAA0AAABomNhRVMAAAAsAAAB1nB0QlIAAAAq -AAACAnVrVUEAAAAsAAACLGZyRlUAAAAqAAACWGh1SFUAAAAuAAACgnpoVFcAAAAQAAACsG5iTk8AAAAs -AAACwGNzQ1oAAAAkAAAC7GhlSUwAAAAgAAADEGl0SVQAAAAuAAADMHJvUk8AAAAkAAADXmRlREUAAAA6 -AAADgmtvS1IAAAAYAAADvHN2U0UAAAAuAAAD1HpoQ04AAAAQAAAEAmphSlAAAAAWAAAEEmVsR1IAAAAk -AAAEKHB0UE8AAAA4AAAETG5sTkwAAAAqAAAEhGVzRVMAAAAoAAAErnRoVEgAAAAkAAAE1nRyVFIAAAAi -AAAE+mZpRkkAAAAsAAAFHGhySFIAAAA6AAAFSHBsUEwAAAA2AAAFgnJ1UlUAAAAmAAAFuGFyRUcAAAAo -AAAF3mVuVVMAAAAoAAAGBgBWAWEAZQBvAGIAZQBjAG4A/QAgAHMAaQB2AP0AIABwAHIAbwBmAGkAbABH -AGUAbgBlAHIAZQBsACAAZwByAOUAdABvAG4AZQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBp -AGwAIABkAGUAIABnAHIAaQBzACAAZwBlAG4A6AByAGkAYwBQAGUAcgBmAGkAbAAgAEMAaQBuAHoAYQAg -AEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8EQAQ+BEQEMAQ5BDsAIABHAHIAYQB5 -AFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1AGUAIABnAHIAaQBzAMEAbAB0AGEAbADhAG4AbwBz -ACAAcwB6APwAcgBrAGUAIABwAHIAbwBmAGkAbJAadShwcJaOgnJfaWPPj/AARwBlAG4AZQByAGkAcwBr -ACAAZwByAOUAdABvAG4AZQBwAHIAbwBmAGkAbABPAGIAZQBjAG4A/QAgAWEAZQBkAP0AIABwAHIAbwBm -AGkAbAXkBegF1QXkBdkF3AAgAEcAcgBhAHkAIAXbBdwF3AXZAFAAcgBvAGYAaQBsAG8AIABnAHIAaQBn -AGkAbwAgAGcAZQBuAGUAcgBpAGMAbwBQAHIAbwBmAGkAbAAgAGcAcgBpACAAZwBlAG4AZQByAGkAYwBB -AGwAbABnAGUAbQBlAGkAbgBlAHMAIABHAHIAYQB1AHMAdAB1AGYAZQBuAC0AUAByAG8AZgBpAGzHfLwY -ACAARwByAGEAeQAg1QS4XNMMx3wARwBlAG4AZQByAGkAcwBrACAAZwByAOUAcwBrAGEAbABlAHAAcgBv -AGYAaQBsZm6QGnBwXqZjz4/wZYdO9k4AgiwwsDDsMKQw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPA -A8EDvwPGA68DuwAgA7MDugPBA7kAUABlAHIAZgBpAGwAIABnAGUAbgDpAHIAaQBjAG8AIABkAGUAIABj -AGkAbgB6AGUAbgB0AG8AcwBBAGwAZwBlAG0AZQBlAG4AIABnAHIAaQBqAHMAcAByAG8AZgBpAGUAbABQ -AGUAcgBmAGkAbAAgAGcAcgBpAHMAIABnAGUAbgDpAHIAaQBjAG8OQg4bDiMORA4fDiUOTA4qDjUOQA4X -DjIOFw4xDkgOJw5EDhsARwBlAG4AZQBsACAARwByAGkAIABQAHIAbwBmAGkAbABpAFkAbABlAGkAbgBl -AG4AIABoAGEAcgBtAGEAYQBwAHIAbwBmAGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABwAHIAbwBm -AGkAbAAgAHMAaQB2AGkAaAAgAHQAbwBuAG8AdgBhAFUAbgBpAHcAZQByAHMAYQBsAG4AeQAgAHAAcgBv -AGYAaQBsACAAcwB6AGEAcgBvAVsAYwBpBB4EMQRJBDgEOQAgBEEENQRABEsEOQAgBD8EQAQ+BEQEOAQ7 -BEwGRQZEBkEAIAYqBjkGMQZKBkEAIABHAHIAYQB5ACAGJwZEBjkGJwZFAEcAZQBuAGUAcgBpAGMAIABH -AHIAYQB5ACAAUAByAG8AZgBpAGwAZQAAdGV4dAAAAABDb3B5cmlnaHQgMjAwNyBBcHBsZSBJbmMuLCBh -bGwgcmlnaHRzIHJlc2VydmVkLgBYWVogAAAAAAAA81EAAQAAAAEWzGN1cnYAAAAAAAAAAQHNAADSKiss -LVokY2xhc3NuYW1lWCRjbGFzc2VzXxAQTlNCaXRtYXBJbWFnZVJlcKMsLi9aTlNJbWFnZVJlcFhOU09i -amVjdNIqKzEyV05TQXJyYXmiMS/SKis0NV5OU011dGFibGVBcnJheaM0MS/TNzgPOTo7V05TV2hpdGVc -TlNDb2xvclNwYWNlRDAgMAAQA4AM0iorPT5XTlNDb2xvcqI9L9IqK0BBV05TSW1hZ2WiQC8ACAARABoA -JAApADIANwBJAEwAUQBTAGIAaAB1AHwAiwCSAJ8ApgCuALAAsgC0ALkAuwC9AMQAyQDUANYA2ADaAN8A -4gDkAOYA6ADtAQQBBgEICZgJnQmoCbEJxAnICdMJ3AnhCekJ7AnxCgAKBAoLChMKIAolCicKKQouCjYK -OQo+CkYAAAAAAAACAQAAAAAAAABCAAAAAAAAAAAAAAAAAAAKSQ +EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdMPJSYnKBRf +EBROU1RJRkZSZXByZXNlbnRhdGlvbl8QGU5TSW50ZXJuYWxMYXlvdXREaXJlY3Rpb26ACIAHTxEIjE1N +ACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEG +AAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEW +AAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFT +AAMAAAACAAEAAYdzAAcAAAe8AAAA0AAAAAAAAAe8YXBwbAIgAABtbnRyR1JBWVhZWiAH0AACAA4ADAAA +AABhY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAG9kc2NtAAABMAAA +Bi5jcHJ0AAAHYAAAADh3dHB0AAAHmAAAABRrVFJDAAAHrAAAAA5kZXNjAAAAAAAAABVHZW5lcmljIEdy +YXkgUHJvZmlsZQAAAAAAAAAAAAAAFUdlbmVyaWMgR3JheSBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAqAAABeGRh +REsAAAA0AAABomNhRVMAAAAsAAAB1nB0QlIAAAAqAAACAnVrVUEAAAAsAAACLGZyRlUAAAAqAAACWGh1 +SFUAAAAuAAACgnpoVFcAAAAQAAACsG5iTk8AAAAsAAACwGNzQ1oAAAAkAAAC7GhlSUwAAAAgAAADEGl0 +SVQAAAAuAAADMHJvUk8AAAAkAAADXmRlREUAAAA6AAADgmtvS1IAAAAYAAADvHN2U0UAAAAuAAAD1Hpo +Q04AAAAQAAAEAmphSlAAAAAWAAAEEmVsR1IAAAAkAAAEKHB0UE8AAAA4AAAETG5sTkwAAAAqAAAEhGVz +RVMAAAAoAAAErnRoVEgAAAAkAAAE1nRyVFIAAAAiAAAE+mZpRkkAAAAsAAAFHGhySFIAAAA6AAAFSHBs +UEwAAAA2AAAFgnJ1UlUAAAAmAAAFuGFyRUcAAAAoAAAF3mVuVVMAAAAoAAAGBgBWAWEAZQBvAGIAZQBj +AG4A/QAgAHMAaQB2AP0AIABwAHIAbwBmAGkAbABHAGUAbgBlAHIAZQBsACAAZwByAOUAdABvAG4AZQBi +AGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABkAGUAIABnAHIAaQBzACAAZwBlAG4A6ABy +AGkAYwBQAGUAcgBmAGkAbAAgAEMAaQBuAHoAYQAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9 +BDgEOQAgBD8EQAQ+BEQEMAQ5BDsAIABHAHIAYQB5AFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1 +AGUAIABnAHIAaQBzAMEAbAB0AGEAbADhAG4AbwBzACAAcwB6APwAcgBrAGUAIABwAHIAbwBmAGkAbJAa +dShwcJaOgnJfaWPPj/AARwBlAG4AZQByAGkAcwBrACAAZwByAOUAdABvAG4AZQBwAHIAbwBmAGkAbABP +AGIAZQBjAG4A/QAgAWEAZQBkAP0AIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAEcAcgBhAHkAIAXb +BdwF3AXZAFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBpAGMAbwBQAHIAbwBm +AGkAbAAgAGcAcgBpACAAZwBlAG4AZQByAGkAYwBBAGwAbABnAGUAbQBlAGkAbgBlAHMAIABHAHIAYQB1 +AHMAdAB1AGYAZQBuAC0AUAByAG8AZgBpAGzHfLwYACAARwByAGEAeQAg1QS4XNMMx3wARwBlAG4AZQBy +AGkAcwBrACAAZwByAOUAcwBrAGEAbABlAHAAcgBvAGYAaQBsZm6QGnBwXqZjz4/wZYdO9k4AgiwwsDDs +MKQw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgA7MDugPBA7kAUABlAHIAZgBp +AGwAIABnAGUAbgDpAHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwBBAGwAZwBlAG0AZQBl +AG4AIABnAHIAaQBqAHMAcAByAG8AZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAcgBpAHMAIABnAGUAbgDp +AHIAaQBjAG8OQg4bDiMORA4fDiUOTA4qDjUOQA4XDjIOFw4xDkgOJw5EDhsARwBlAG4AZQBsACAARwBy +AGkAIABQAHIAbwBmAGkAbABpAFkAbABlAGkAbgBlAG4AIABoAGEAcgBtAGEAYQBwAHIAbwBmAGkAaQBs +AGkARwBlAG4AZQByAGkBDQBrAGkAIABwAHIAbwBmAGkAbAAgAHMAaQB2AGkAaAAgAHQAbwBuAG8AdgBh +AFUAbgBpAHcAZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAcwB6AGEAcgBvAVsAYwBpBB4EMQRJ +BDgEOQAgBEEENQRABEsEOQAgBD8EQAQ+BEQEOAQ7BEwGRQZEBkEAIAYqBjkGMQZKBkEAIABHAHIAYQB5 +ACAGJwZEBjkGJwZFAEcAZQBuAGUAcgBpAGMAIABHAHIAYQB5ACAAUAByAG8AZgBpAGwAZQAAdGV4dAAA +AABDb3B5cmlnaHQgMjAwNyBBcHBsZSBJbmMuLCBhbGwgcmlnaHRzIHJlc2VydmVkLgBYWVogAAAAAAAA +81EAAQAAAAEWzGN1cnYAAAAAAAAAAQHNAADSKywtLlokY2xhc3NuYW1lWCRjbGFzc2VzXxAQTlNCaXRt +YXBJbWFnZVJlcKMtLzBaTlNJbWFnZVJlcFhOU09iamVjdNIrLDIzV05TQXJyYXmiMjDSKyw1Nl5OU011 +dGFibGVBcnJheaM1MjDTODkPOjs8V05TV2hpdGVcTlNDb2xvclNwYWNlRDAgMAAQA4AM0issPj9XTlND +b2xvcqI+MNIrLEFCV05TSW1hZ2WiQTAACAARABoAJAApADIANwBJAEwAUQBTAGIAaAB1AHwAiwCSAJ8A +pgCuALAAsgC0ALkAuwC9AMQAyQDUANYA2ADaAN8A4gDkAOYA6ADvAQYBIgEkASYJtgm7CcYJzwniCeYJ +8Qn6Cf8KBwoKCg8KHgoiCikKMQo+CkMKRQpHCkwKVApXClwKZAAAAAAAAAIBAAAAAAAAAEMAAAAAAAAA +AAAAAAAAAApnA diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 858ece8cf6..638b3bf367 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m @@ -390,13 +390,13 @@ - (void)removeAction:(id)sender { NSButton *removeButton = (NSButton *)sender; NSDictionary *info = [self.tableContents objectAtIndex:removeButton.tag]; - NSString *msg; - if ([info objectForKey:@"HeaderTitle"] != nil) - msg = [NSString stringWithFormat:@"This action will permanently delete '%@'.", [info objectForKey:@"HeaderTitle"]]; - else - msg = [NSString stringWithFormat:@"This action will permanently delete '%@'.", [info objectForKey:kKMKeyboardNameKey]]; + NSString *deleteKeyboardMessage = NSLocalizedString(@"message-confirm-delete-keyboard", nil); - [self.deleteAlertView setMessageText:msg]; + if ([info objectForKey:@"HeaderTitle"] != nil) + [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, [info objectForKey:@"HeaderTitle"]]]; + else + [self.deleteAlertView setMessageText:[NSString localizedStringWithFormat:deleteKeyboardMessage, [info objectForKey:kKMKeyboardNameKey]]]; + [self.deleteAlertView beginSheetModalForWindow:self.window modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) @@ -428,9 +428,9 @@ } - (void)handleRequestToInstallPackage:(KMPackage *) package { - NSString *infoFmt = NSLocalizedString(@"Do you want the Keyman Input Method to install this Package?\rFile: %@", @"Alert informative text when user double-clicks a KMP file. Parameter is the name of the KMP file."); - [self.confirmKmpInstallAlertView setInformativeText:[NSString localizedStringWithFormat:infoFmt, package.getOrigKmpFilename]]; - + NSString *keyboardInfoString = NSLocalizedString(@"info-install-keyboard-filename", nil); + [self.confirmKmpInstallAlertView setInformativeText:[NSString localizedStringWithFormat:keyboardInfoString, package.getOrigKmpFilename]]; + if ([self.AppDelegate debugMode]) { NSLog(@"Asking user to confirm installation of %@...", package.getOrigKmpFilename); NSLog(@"KMP - temp file name: %@", package.getTempKmpFilename); @@ -453,8 +453,11 @@ if (!didUnzip) { NSAlert *failure = [[NSAlert alloc] init]; - [failure addButtonWithTitle:NSLocalizedString(@"OK", @"Alert button")]; - [failure setMessageText:NSLocalizedString(@"Failed to unzip Keyman Package!", @"Alert message when user double-clicks a KMP file that cannot be unzipped.")]; + [failure addButtonWithTitle:NSLocalizedString(@"button-keyboard-file-unreadable", @"Alert button")]; + + NSString *errorString = NSLocalizedString(@"message-keyboard-file-unreadable", nil); + [failure setMessageText:[NSString localizedStringWithFormat:errorString, kmpFile.lastPathComponent]]; + [failure setIcon:[[NSBundle mainBundle] imageForResource:@"logo.png"]]; [failure setAlertStyle:NSWarningAlertStyle]; [failure beginSheetModalForWindow:self.window @@ -516,10 +519,9 @@ - (NSAlert *)deleteAlertView { if (_deleteAlertView == nil) { _deleteAlertView = [[NSAlert alloc] init]; - [_deleteAlertView setMessageText:@"This action will permanently delete this keyboard."]; - [_deleteAlertView setInformativeText:@"Press OK to continue."]; - [_deleteAlertView addButtonWithTitle:@"OK"]; - [_deleteAlertView addButtonWithTitle:@"Cancel"]; + [_deleteAlertView setInformativeText:NSLocalizedString(@"info-cannot-undo-delete-keyboard", nil)]; + [_deleteAlertView addButtonWithTitle:NSLocalizedString(@"button-delete-keyboard", nil)]; + [_deleteAlertView addButtonWithTitle:NSLocalizedString(@"button-cancel-delete-keyboard", nil)]; [_deleteAlertView setAlertStyle:NSWarningAlertStyle]; [_deleteAlertView setIcon:[[NSBundle mainBundle] imageForResource:@"logo.png"]]; } @@ -530,9 +532,9 @@ - (NSAlert *)confirmKmpInstallAlertView { if (_confirmKmpInstallAlertView == nil) { _confirmKmpInstallAlertView = [[NSAlert alloc] init]; - [_confirmKmpInstallAlertView addButtonWithTitle:NSLocalizedString(@"Yes", @"Alert button")]; - [_confirmKmpInstallAlertView addButtonWithTitle:NSLocalizedString(@"No", @"Alert button")]; - [_confirmKmpInstallAlertView setMessageText:NSLocalizedString(@"Install Keyman Package?", @"Alert message text when user double-clicks a KMP file.")]; + [_confirmKmpInstallAlertView addButtonWithTitle:NSLocalizedString(@"button-install-keyboard", nil)]; + [_confirmKmpInstallAlertView addButtonWithTitle:NSLocalizedString(@"button-cancel-install-keyboard", nil)]; + [_confirmKmpInstallAlertView setMessageText:NSLocalizedString(@"message-confirm-install-keyboard", nil)]; [_confirmKmpInstallAlertView setAlertStyle:NSInformationalAlertStyle]; [_confirmKmpInstallAlertView setIcon:[[NSBundle mainBundle] imageForResource:@"logo.png"]]; } @@ -545,7 +547,7 @@ NSLog(@"User responded to NSAlert"); } if (alert == _deleteAlertView) { - if (returnCode == NSAlertFirstButtonReturn) { // OK + if (returnCode == NSAlertFirstButtonReturn) { // Delete [self deleteFileAtIndex:(__bridge NSNumber *)contextInfo]; } @@ -556,7 +558,7 @@ if ([self.AppDelegate debugMode]) { NSLog(@"KMP - Temp file: %@", package.getTempKmpFilename); } - if (returnCode == NSAlertFirstButtonReturn) { // Yes + if (returnCode == NSAlertFirstButtonReturn) { // Install [self installPackageFile: package.getTempKmpFilename]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/en.lproj/preferences.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/en.lproj/preferences.strings new file mode 100644 index 0000000000..9dcd956516 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/en.lproj/preferences.strings @@ -0,0 +1,39 @@ + +/* Checkbox text to always show on-screen keyboard */ +"4UX-80-0Vo.title" = "Always show on-screen keyboard"; + +/* Checkbox text to enable verbose Console logging */ +"7J6-zy-R20.title" = "Use verbose Console logging"; + +/* Support buton text */ +"93O-x6-RLF.label" = "Support"; + +/* Download Keyboard button text */ +"CTw-kf-WNS.title" = "Download Keyboard..."; + +/* Keyman Configuration window title */ +"F0z-JX-Cv5.title" = "Keyman Configuration"; + +/* button text to go back to previous page */ +"JOK-JV-n8w.title" = "Back"; + +/* Class = "NSTabViewItem"; label = "Keyboard Layouts"; ObjectID = "MPN-9N-wWc"; */ +"MPN-9N-wWc.label" = "Keyboard Layouts"; + +/* text to explain verbose Console logging option */ +"MrI-GM-7d6.title" = "When the verbose logging option is on, Keyman will log actions that might help Keyman Support diagnose a problem. The Console program can be used to see a log of messages from Keyman. In Console, filter to show all messages from the \"Keyman\" process. This log can be exported and sent to Keyman support if needed."; + +/* button text to move forward to the next page */ +"eXr-8V-h1g.title" = "Forward"; + +/* button text to return to the home help page */ +"fXS-aC-CMH.title" = "Home"; + +/* Options button text */ +"frd-No-seV.label" = "Options"; + +/* Installed Keyboard column heading */ +"jex-Nd-Qzg.headerCell.title" = "Installed Keyboard"; + +/* Verbose logging tooltip text */ +"uWx-3J-U0D.ibShadowedToolTip" = "Turn this on if you are having problems with a specific keyboard or with Keyman in general."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/km.lproj/preferences.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/km.lproj/preferences.strings new file mode 100644 index 0000000000..739c243913 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/km.lproj/preferences.strings @@ -0,0 +1,39 @@ + +/* Class = "NSButtonCell"; title = "Always show on-screen keyboard"; ObjectID = "4UX-80-0Vo"; */ +"4UX-80-0Vo.title" = "បង្ហាញក្ដារចុចលើអេក្រង់ជានិច្ច"; + +/* Class = "NSButtonCell"; title = "Use verbose Console logging"; ObjectID = "7J6-zy-R20"; */ +"7J6-zy-R20.title" = "ប្រើកំណត់ហេតុកុងសូលលម្អិត"; + +/* Class = "NSTabViewItem"; label = "Support"; ObjectID = "93O-x6-RLF"; */ +"93O-x6-RLF.label" = "គាំទ្រ"; + +/* Class = "NSButtonCell"; title = "Download keyboard..."; ObjectID = "CTw-kf-WNS"; */ +"CTw-kf-WNS.title" = "ទាញយកក្ដារចុច..."; + +/* Class = "NSWindow"; title = "Keyman Configuration"; ObjectID = "F0z-JX-Cv5"; */ +"F0z-JX-Cv5.title" = "ការកំណត់រចនាសម្ព័ន្ធ Keyman"; + +/* Class = "NSButtonCell"; title = "< Back"; ObjectID = "JOK-JV-n8w"; */ +"JOK-JV-n8w.title" = "ថយក្រោយ"; + +/* Class = "NSTabViewItem"; label = "Keyboard Layouts"; ObjectID = "MPN-9N-wWc"; */ +"MPN-9N-wWc.label" = "ប្លង់ក្ដារចុច"; + +/* Class = "NSTextFieldCell"; title = "When the verbose logging option is on, Keyman will log actions that might help Keyman Support diagnose a problem. The Console program can be used to see a log of messages from Keyman. In Console, filter to show all messages from the \"Keyman\" process. This log can be exported and sent to Keyman support if needed."; ObjectID = "MrI-GM-7d6"; */ +"MrI-GM-7d6.title" = "នៅពេលបើកជម្រើសកំណត់ហេតុលម្អិត Keyman នឹងរក្សារទុកកំណត់ហេតុសកម្មភាពដែលអាចជួយក្រុមការងារ​ Keyman វិភាគបញ្ហា។ កម្មវិធីកុងសូលអាចប្រើបើកមើលសារកំណត់ហេតុពី Keyman។ ក្នុងកុងសូល ត្រងដើម្បីបង្ហាញសារទាំងអស់ពីដំណើរការរបស់ \"Keyman\"។ បើចាំបាច់ អ្នកអាចនាំចេញ ឬផ្ញើកំណត់ហេតុនេទៅក្រុមការងារ Keyman។"; + +/* Class = "NSButtonCell"; title = "Forward >"; ObjectID = "eXr-8V-h1g"; */ +"eXr-8V-h1g.title" = "ទៅមុខ"; + +/* Class = "NSButtonCell"; title = "Home"; ObjectID = "fXS-aC-CMH"; */ +"fXS-aC-CMH.title" = "ដើម"; + +/* Class = "NSTabViewItem"; label = "Options"; ObjectID = "frd-No-seV"; */ +"frd-No-seV.label" = "ជម្រើស"; + +/* Class = "NSTableColumn"; headerCell.title = "Installed Keyboard"; ObjectID = "jex-Nd-Qzg"; */ +"jex-Nd-Qzg.headerCell.title" = "បានដំឡើង​ក្ដារចុច"; + +/* Class = "NSButton"; ibShadowedToolTip = "Turn this on if you are having problems with a specific keyboard or with Keyman in general."; ObjectID = "uWx-3J-U0D"; */ +"uWx-3J-U0D.ibShadowedToolTip" = "បើកចុះ ប្រសិនបើអ្នកកំពុងមានបញ្ហាជាមួយ Keyman ឬក្ដារចុចណាមួយជាក់លាក់។"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.xib b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/Base.lproj/KMInfoWindowController.xib similarity index 100% rename from mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.xib rename to mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/Base.lproj/KMInfoWindowController.xib diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m index 7667d12801..ef9e3ad03e 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/KMInfoWindowController.m @@ -27,7 +27,6 @@ - (void)windowDidLoad { [super windowDidLoad]; - [self.window setTitle:@"Keyboard/Package Info"]; [self.tabView setDelegate:self]; [self.detailsView setFrameLoadDelegate:(id)self]; [self.detailsView setPolicyDelegate:(id)self]; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/en.lproj/KMInfoWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/en.lproj/KMInfoWindowController.strings new file mode 100644 index 0000000000..b741f26b0f --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/en.lproj/KMInfoWindowController.strings @@ -0,0 +1,9 @@ + +/* Keyboard Information window title */ +"F0z-JX-Cv5.title" = "Keyboard Information"; + +/* button text to show Details */ +"Fvy-XJ-s38.label" = "Details"; + +/* button text to show Read Me */ +"waA-IW-Qyn.label" = "Read Me"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/km.lproj/KMInfoWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/km.lproj/KMInfoWindowController.strings new file mode 100644 index 0000000000..531a90c5d7 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/km.lproj/KMInfoWindowController.strings @@ -0,0 +1,9 @@ + +/* Class = "NSWindow"; title = "Window"; ObjectID = "F0z-JX-Cv5"; */ +"F0z-JX-Cv5.title" = "ព័ត៌មានក្ដារចុច"; + +/* Class = "NSTabViewItem"; label = "Details"; ObjectID = "Fvy-XJ-s38"; */ +"Fvy-XJ-s38.label" = "លម្អិត"; + +/* Class = "NSTabViewItem"; label = "Read Me"; ObjectID = "waA-IW-Qyn"; */ +"waA-IW-Qyn.label" = "អានខ្ញុំ"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index c01f2286c7..9eed4d5897 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -1178,9 +1178,9 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (NSAlert *)downloadInfoView { if (_downloadInfoView == nil) { _downloadInfoView = [[NSAlert alloc] init]; - [_downloadInfoView setMessageText:@"Downloading..."]; + [_downloadInfoView setMessageText:NSLocalizedString(@"message-keyboard-downloading", nil)]; [_downloadInfoView setInformativeText:@""]; - [_downloadInfoView addButtonWithTitle:@"Cancel"]; + [_downloadInfoView addButtonWithTitle:NSLocalizedString(@"button-cancel-downloading", nil)]; [_downloadInfoView setAlertStyle:NSInformationalAlertStyle]; [_downloadInfoView setAccessoryView:self.progressIndicator]; } @@ -1239,9 +1239,9 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } if (_connection == nil) { - [_downloadInfoView setMessageText:@"Downloading..."]; + [_downloadInfoView setMessageText:NSLocalizedString(@"message-keyboard-downloading", nil)]; NSButton *button = (NSButton *)[_downloadInfoView.buttons objectAtIndex:0]; - [button setTitle:@"Cancel"]; + [button setTitle:NSLocalizedString(@"button-cancel-downloading", nil)]; [button setTag:-1]; [self.progressIndicator setDoubleValue:0]; NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60]; @@ -1285,9 +1285,10 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef [self.receivedData writeToFile:filePath atomically:YES]; [self unzipFile:filePath]; [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; - [_downloadInfoView setMessageText:@"Download Complete"]; + + [_downloadInfoView setMessageText:NSLocalizedString(@"message-keyboard-download-complete", nil)]; NSButton *button = (NSButton *)[_downloadInfoView.buttons objectAtIndex:0]; - [button setTitle:@"Done"]; + [button setTitle:NSLocalizedString(@"button-download-complete", nil)]; [button setTag:1]; [[NSNotificationCenter defaultCenter] postNotificationName:kKeymanKeyboardDownloadCompletedNotification object:self diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/KMKeyboardHelpWindowController.xib b/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/Base.lproj/KMKeyboardHelpWindowController.xib similarity index 94% rename from mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/KMKeyboardHelpWindowController.xib rename to mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/Base.lproj/KMKeyboardHelpWindowController.xib index 446f8cb23d..56de81359c 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/KMKeyboardHelpWindowController.xib +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/Base.lproj/KMKeyboardHelpWindowController.xib @@ -1,9 +1,9 @@ - + - - + + @@ -15,11 +15,11 @@ - + - + diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/en.lproj/KMKeyboardHelpWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/en.lproj/KMKeyboardHelpWindowController.strings new file mode 100644 index 0000000000..71b015fece --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/en.lproj/KMKeyboardHelpWindowController.strings @@ -0,0 +1,9 @@ + +/* Keyboard Help window title */ +"F0z-JX-Cv5.title" = "Keyboard Help"; + +/* OK button text */ +"Zla-QF-m0K.title" = "OK"; + +/* Print button text */ +"fYY-Uj-y4S.title" = "Print..."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/km.lproj/KMKeyboardHelpWindowController.strings b/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/km.lproj/KMKeyboardHelpWindowController.strings new file mode 100644 index 0000000000..4d0c436a02 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/km.lproj/KMKeyboardHelpWindowController.strings @@ -0,0 +1,9 @@ + +/* Class = "NSWindow"; title = "Window"; ObjectID = "F0z-JX-Cv5"; */ +"F0z-JX-Cv5.title" = "Window - វីនដូ"; + +/* Class = "NSButtonCell"; title = "OK"; ObjectID = "Zla-QF-m0K"; */ +"Zla-QF-m0K.title" = "យល់ព្រម"; + +/* Class = "NSButtonCell"; title = "Print..."; ObjectID = "fYY-Uj-y4S"; */ +"fYY-Uj-y4S.title" = "ព្រីន..."; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings b/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings new file mode 100644 index 0000000000..c92ddd8037 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings @@ -0,0 +1,45 @@ + +/* Message displayed to confirm delete of the Keyman keyboard selected by the user from the keyboard list */ +"message-confirm-delete-keyboard" = "Are you sure you want to delete the keyboard '%@'?"; + +/* Delete keyboard confirmation info indicating that the delete action cannot be undone */ +"info-cannot-undo-delete-keyboard" = "You cannot undo this action."; + +/* Button text to cancel delete of the specified installed Keyman keyboard */ +"button-cancel-delete-keyboard" = "Cancel"; + +/* Button text to confirm delete of the specified installed Keyman keyboard */ +"button-delete-keyboard" = "Delete"; + +/* Message displayed to confirm installation of a keyboard from the specifed .kmp file double-clicked by the user */ +"message-confirm-install-keyboard" = "Install Keyman keyboard?"; + +/* Message displayed to confirm installation of a keyboard from the specifed .kmp file double-clicked by the user */ +"info-install-keyboard-filename" = "Do you want Keyman to install the keyboard from the file '%@'?"; + +/* Button text to cancel installation of the double-clicked Keyman file */ +"button-cancel-install-keyboard" = "Cancel"; + +/* Button text to confirm installation of the double-clicked Keyman file */ +"button-install-keyboard" = "Install"; + +/* Message displayed to inform user that .kmp file could not be read/unzipped */ +"message-keyboard-file-unreadable" = "Could not read the Keyman file '%@'."; + +/* Button text to acknowledge that .kmp file could not be read */ +"button-keyboard-file-unreadable" = "OK"; + +/* label text to identify Keyman version */ +"version-label-text" = "Version %@"; + +/* Status text for keyboard downloading */ +"message-keyboard-downloading" = "Downloading..."; + +/* Status text for keyboard download complete */ +"message-keyboard-download-complete" = "Download complete."; + +/* Button text to cancel downloading keyboard */ +"button-cancel-downloading" = "Cancel"; + +/* Button text keyboard download complete */ +"button-download-complete" = "Done"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/MainMenu.strings b/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/MainMenu.strings new file mode 100644 index 0000000000..6ec1c87820 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/MainMenu.strings @@ -0,0 +1,15 @@ + +/* Configuration window menu text */ +"P1b-lE-yFw.title" = "Configuration..."; + +/* Keyboards menu text */ +"bQa-j9-nHe.title" = "Keyboards"; + +/* Keyboards menu text */ +"goP-aK-3WB.title" = "Keyboards"; + +/* About menu text */ +"kb2-ww-RS3.title" = "About"; + +/* On-Screen Keyboard menu text */ +"s96-JI-YDh.title" = "On-Screen Keyboard"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/km.lproj/Localizable.strings b/mac/Keyman4MacIM/Keyman4MacIM/km.lproj/Localizable.strings new file mode 100644 index 0000000000..bf84b4bf74 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/km.lproj/Localizable.strings @@ -0,0 +1,45 @@ + +/* Message displayed to confirm delete of the Keyman keyboard selected by the user from the keyboard list */ +"message-confirm-delete-keyboard" = "តើអ្នកពិតជាចង់លុបក្ដារចុច '%@' នេះឬ?"; + +/* Delete keyboard confirmation info indicating that the delete action cannot be undone */ +"info-cannot-undo-delete-keyboard" = "អ្នកមិនអាចត្រឡប់សកម្មភាពវិញបានទេ។"; + +/* Button text to cancel delete of the specified installed Keyman keyboard */ +"button-cancel-delete-keyboard" = "កុំលុប"; + +/* Button text to confirm delete of the specified installed Keyman keyboard */ +"button-delete-keyboard" = "លុប"; + +/* Message displayed to confirm installation of a keyboard from the specifed .kmp file double-clicked by the user */ +"message-confirm-install-keyboard" = "ដំឡើងក្ដារចុច Keyman?"; + +/* Message displayed to confirm installation of a keyboard from the specifed .kmp file double-clicked by the user */ +"info-install-keyboard-filename" = "តើអ្នកចង់ឱ្យ Keyman ដំឡើងក្ដារចុចពីឯកសារ '%@' នេះឬ?"; + +/* Button text to cancel installation of the double-clicked Keyman file */ +"button-cancel-install-keyboard" = "បោះបង់"; + +/* Button text to confirm installation of the double-clicked Keyman file */ +"button-install-keyboard" = "ដំឡើង"; + +/* Message displayed to inform user that .kmp file could not be read/unzipped */ +"message-keyboard-file-unreadable" = "មិនអាចអានឯកសារ Keyman '%@' នេះបានទេ។"; + +/* Button text to acknowledge that .kmp file could not be read */ +"button-keyboard-file-unreadable" = "យល់ព្រម"; + +/* label text to identify Keyman version */ +"version-label-text" = "ជំនាន់ %@"; + +/* Status text for keyboard downloading */ +"message-keyboard-downloading" = "កំពុងទាញយក..."; + +/* Status text for keyboard download complete */ +"message-keyboard-download-complete" = "បានទាញយករួចរាល់។"; + +/* Button text to cancel downloading keyboard */ +"button-cancel-downloading" = "បោះបង់"; + +/* Button text keyboard download complete */ +"button-download-complete" = "រួចរាល់"; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/km.lproj/MainMenu.strings b/mac/Keyman4MacIM/Keyman4MacIM/km.lproj/MainMenu.strings new file mode 100644 index 0000000000..c511890ef3 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/km.lproj/MainMenu.strings @@ -0,0 +1,15 @@ + +/* Class = "NSMenuItem"; title = "Configuration..."; ObjectID = "P1b-lE-yFw"; */ +"P1b-lE-yFw.title" = "ការកំណត់រចនាសម្ព័ន្ធ..."; + +/* Class = "NSMenuItem"; title = "Keyboards"; ObjectID = "bQa-j9-nHe"; */ +"bQa-j9-nHe.title" = "ក្ដារចុច"; + +/* Class = "NSMenu"; title = "Keyboards"; ObjectID = "goP-aK-3WB"; */ +"goP-aK-3WB.title" = "ក្ដារចុច"; + +/* Class = "NSMenuItem"; title = "About"; ObjectID = "kb2-ww-RS3"; */ +"kb2-ww-RS3.title" = "អំពី"; + +/* Class = "NSMenuItem"; title = "On-Screen Keyboard"; ObjectID = "s96-JI-YDh"; */ +"s96-JI-YDh.title" = "ក្ដារចុចលើអេក្រង់"; diff --git a/mac/Keyman4MacIM/Podfile.lock b/mac/Keyman4MacIM/Podfile.lock index 41aa6bf262..a161db72ea 100644 --- a/mac/Keyman4MacIM/Podfile.lock +++ b/mac/Keyman4MacIM/Podfile.lock @@ -21,4 +21,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 521a56f741ba869ecf0fd64a7773173265973269 -COCOAPODS: 1.10.0 +COCOAPODS: 1.11.2 diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h index 4e8288c002..0f4614c1c9 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h @@ -42,12 +42,10 @@ struct COMP_KEYBOARD { #define VERSION_80 0x00000800 #define VERSION_90 0x00000900 #define VERSION_100 0x00000A00 -#define VERSION_110 0x00000B00 -#define VERSION_120 0x00000C00 -#define VERSION_130 0x00000D00 #define VERSION_140 0x00000E00 +#define VERSION_150 0x00000F00 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_140 +#define VERSION_MAX VERSION_150 struct COMP_GROUP { DWORD dpName; // string (only debug) @@ -122,7 +120,7 @@ struct COMP_STORE { #define K_CTRLFLAG 0x0020 // Either ctrl flag #define K_ALTFLAG 0x0040 // Either alt flag //#define K_METAFLAG 0x0080 // Either Meta-key flag (tentative). Not usable in keyboard rules; - // Used internally (currently, only by KMW) to ensure Meta-key + // Used internally (currently, only by KMW) to ensure Meta-key // shortcuts safely bypass rules // Meta key = Command key on macOS, Windows key on Windows #define CAPITALFLAG 0x0100 // Caps lock on diff --git a/mac/build.sh b/mac/build.sh index ba68164924..438edb57d1 100755 --- a/mac/build.sh +++ b/mac/build.sh @@ -291,7 +291,7 @@ if $CLEAN ; then fi if [ "$TEST_ACTION" == "test" ]; then - carthage bootstrap + carthage bootstrap --use-xcframeworks fi execBuildCommand() { diff --git a/resources/devbox/iis-https/README.md b/resources/devbox/iis-https/README.md index b23c538286..f684671c2b 100644 --- a/resources/devbox/iis-https/README.md +++ b/resources/devbox/iis-https/README.md @@ -8,9 +8,13 @@ The script must be run as Administrator and can safely be run multiple times. Once configured, the certificates will work for the following sites (configured in v3.ext): - `localhost` +- `keyman-local.com` +- `*.keyman-local.com` - `keyman.com.local` - `*.keyman.com.local` - `keymanweb.com.local` - `keymankeyboards.com.local` - `keyman.dev.local` - `*.keymanweb.com.local` +- `keyman.blog.local` +- `*.keyman.blog.local` diff --git a/resources/devbox/iis-https/v3.ext b/resources/devbox/iis-https/v3.ext index 4a84d48e48..5b49ccd106 100644 --- a/resources/devbox/iis-https/v3.ext +++ b/resources/devbox/iis-https/v3.ext @@ -11,3 +11,7 @@ DNS.4 = keymanweb.com.local DNS.5 = keymankeyboards.com.local DNS.6 = keyman.dev.local DNS.7 = *.keymanweb.com.local +DNS.8 = *.keyman-local.com +DNS.9 = keyman.blog.local +DNS.10 = *.keyman.blog.local +DNS.11 = keyman-local.com diff --git a/resources/git-hooks/commit-msg b/resources/git-hooks/commit-msg index 6260b29495..187a7bbe22 100755 --- a/resources/git-hooks/commit-msg +++ b/resources/git-hooks/commit-msg @@ -90,7 +90,11 @@ function build_regex() { remessage=".{$min_length,$max_length}[^.]" refixes="(\. Fixes #[[:digit:]]+)?" - regexp="^${retypes}${rescope}: ${remessage}${refixes}[[:space:]]*$" + # Regex for the commit message title. It's ok if the commit message starts with "fixup!". + # (This allows to run `git commit --fixup=` without complaining about the commit + # message not being in conventional commit format. This helps in squashing with a previous + # commit.) + regexp="^(fixup\! )?${retypes}${rescope}: ${remessage}${refixes}[[:space:]]*$" } # Print out a standard error message which explains diff --git a/resources/gosh/package-lock.json b/resources/gosh/package-lock.json index 8820f70d9f..3a1dab8f95 100644 --- a/resources/gosh/package-lock.json +++ b/resources/gosh/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 1 } diff --git a/resources/gosh/package.json b/resources/gosh/package.json index 479e941cd8..feb8b8e004 100644 --- a/resources/gosh/package.json +++ b/resources/gosh/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.129", + "version": "15.0.157", "description": "Good Ol' Shell", "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ diff --git a/resources/web-environment/package-lock.json b/resources/web-environment/package-lock.json index a999e57312..562c864878 100644 --- a/resources/web-environment/package-lock.json +++ b/resources/web-environment/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 1 } diff --git a/resources/web-environment/package.json b/resources/web-environment/package.json index 641197e704..adafb16935 100644 --- a/resources/web-environment/package.json +++ b/resources/web-environment/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.129", + "version": "15.0.157", "description": "A compilation utility for passing select environment variables into our TS builds", "author": "Joshua Horton (https://github.com/jahorton)", "homepage": "https://github.com/keymanapp/keyman", @@ -17,6 +17,6 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.129" + "@keymanapp/resources-gosh": "^15.0.157" } } diff --git a/web/package-lock.json b/web/package-lock.json index 10b9413172..c1e74d5d6c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.129", + "version": "15.0.157", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/web/package.json b/web/package.json index a7e72b9a99..79ec69b79d 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.129", + "version": "15.0.157", "description": "Facilitates text input in any language.", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.129", - "@keymanapp/web-sentry-manager": "^15.0.129", + "@keymanapp/resources-gosh": "^15.0.157", + "@keymanapp/web-sentry-manager": "^15.0.157", "@sentry/cli": "^1.52.3", "chai": "^4.3.4", "google-closure-compiler-java": "^20200224.0.0", @@ -52,13 +52,13 @@ "sentry-cli": "sentry-cli" }, "dependencies": { - "@keymanapp/input-processor": "^15.0.129", - "@keymanapp/keyboard-processor": "^15.0.129", - "@keymanapp/lexical-model-layer": "^15.0.129", - "@keymanapp/models-types": "^15.0.129", - "@keymanapp/recorder-core": "^15.0.129", - "@keymanapp/web-environment": "^15.0.129", - "@keymanapp/web-utils": "^15.0.129", + "@keymanapp/input-processor": "^15.0.157", + "@keymanapp/keyboard-processor": "^15.0.157", + "@keymanapp/lexical-model-layer": "^15.0.157", + "@keymanapp/models-types": "^15.0.157", + "@keymanapp/recorder-core": "^15.0.157", + "@keymanapp/web-environment": "^15.0.157", + "@keymanapp/web-utils": "^15.0.157", "@types/node": "^11.9.4", "es6-shim": "^0.35.5", "eventemitter3": "^4.0.0" diff --git a/web/source/dom/domEventHandlers.ts b/web/source/dom/domEventHandlers.ts index aa362e3a3f..e3491d8c58 100644 --- a/web/source/dom/domEventHandlers.ts +++ b/web/source/dom/domEventHandlers.ts @@ -3,19 +3,19 @@ namespace com.keyman.dom { /* - * Note that for many of the actual events represented by methods in this file, `this` is replaced + * Note that for many of the actual events represented by methods in this file, `this` is replaced * automatically by JavaScript's event handling system. As such, many 'wrapper' variants of the events * exist to restore the object-oriented hierarchy below. - * + * */ export class CommonDOMStates { _DisableInput: boolean = false; // Should input be disabled? - _IgnoreNextSelChange: number = 0; // when a visual keyboard key is mouse-down, ignore the next sel change because this stuffs up our history + _IgnoreNextSelChange: number = 0; // when a visual keyboard key is mouse-down, ignore the next sel change because this stuffs up our history _IgnoreBlurFocus: boolean = false; // Used to temporarily ignore focus changes _Selection = null; _SelectionControl: any = null; // Type behavior is as with activeElement and the like. - + _activeElement: HTMLElement; _lastActiveElement: HTMLElement; @@ -32,7 +32,7 @@ namespace com.keyman.dom { this.focusTimer = window.setTimeout(function() { this.focusing=false; - }.bind(this), 1000) + }.bind(this), 50) } } @@ -43,7 +43,7 @@ namespace com.keyman.dom { // TODO: resolve/refactor out! protected keyman: KeymanBase; - // This is only static within a given initialization of KeymanWeb. Perhaps it would be best as an initialization + // This is only static within a given initialization of KeymanWeb. Perhaps it would be best as an initialization // parameter and member field? static states: CommonDOMStates = new CommonDOMStates(); @@ -53,12 +53,12 @@ namespace com.keyman.dom { /** * Handle receiving focus by simulated input field - */ + */ setFocus: (e?: TouchEvent|MSPointerEvent) => void = function(e?: TouchEvent|MSPointerEvent): void { // Touch-only handler. }.bind(this); - - /** + + /** * Handles touch-based loss of focus events. */ setBlur: (e: FocusEvent) => void = function(e: FocusEvent) { @@ -71,8 +71,8 @@ namespace com.keyman.dom { //TODO: add more complete description of what ControlFocus really does /** - * Respond to KeymanWeb-aware input element receiving focus - */ + * Respond to KeymanWeb-aware input element receiving focus + */ _ControlFocus: (e: FocusEvent) => boolean = function(this: DOMEventHandlers, e: FocusEvent): boolean { var Ltarg: HTMLElement; var device = this.keyman.util.device; @@ -87,7 +87,7 @@ namespace com.keyman.dom { if(Ltarg['body']) { Ltarg = Ltarg['body']; // Occurs in Firefox for design-mode iframes. } - + // Prevent any action if a protected input field if(device.touchable && (Ltarg.className == null || Ltarg.className.indexOf('keymanweb-input') < 0)) { return true; @@ -102,7 +102,7 @@ namespace com.keyman.dom { } } else if(Ltarg.ownerDocument && Ltarg.ownerDocument.designMode == 'on') { // continue; don't block this one! - } else if((device.touchable || !Ltarg.isContentEditable) + } else if((device.touchable || !Ltarg.isContentEditable) && !(Ltarg.ownerDocument && Ltarg instanceof Ltarg.ownerDocument.defaultView.HTMLTextAreaElement)) { return true; } @@ -113,7 +113,7 @@ namespace com.keyman.dom { if (Ltarg.nodeType == 3) { // defeat Safari bug Ltarg = Ltarg.parentNode as HTMLElement; } - + var LfocusTarg = Ltarg; // Ensure that focussed element is visible above the keyboard @@ -122,7 +122,7 @@ namespace com.keyman.dom { (this as DOMTouchHandlers).scrollBody(Ltarg); } } - + if(Ltarg.ownerDocument && Ltarg instanceof Ltarg.ownerDocument.defaultView.HTMLIFrameElement) { //**TODO: check case reference this.keyman.domManager._AttachToIframe(Ltarg as HTMLIFrameElement); Ltarg=Ltarg.contentWindow.document.body; @@ -131,8 +131,8 @@ namespace com.keyman.dom { // Must set before _Blur / _Focus to avoid infinite recursion due to complications // in setActiveKeyboard behavior with managed keyboard settings. this.keyman.domManager.lastActiveElement = Ltarg; - this.keyman.domManager.activeElement = Ltarg; // I3363 (Build 301) - + this.keyman.domManager.activeElement = Ltarg; // I3363 (Build 301) + if(this.keyman.uiManager.justActivated) { this._BlurKeyboardSettings(Ltarg); } else { @@ -160,9 +160,9 @@ namespace com.keyman.dom { * Scope Private * @param {Object} _target element gaining focus * @param {Object} _activeControl currently active control - * @return {boolean} + * @return {boolean} * Description Execute external (UI) code needed on focus - */ + */ doControlFocused(_target: HTMLElement, _activeControl: HTMLElement): boolean { var p={}; p['target']=_target; @@ -173,9 +173,9 @@ namespace com.keyman.dom { /** * Respond to KMW losing focus on event - */ + */ _ControlBlur: (e: FocusEvent) => boolean = function(this: DOMEventHandlers, e: FocusEvent): boolean { - var Ltarg: HTMLElement; + var Ltarg: HTMLElement; e = this.keyman._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs Ltarg = this.keyman.util.eventTarget(e) as HTMLElement; @@ -187,7 +187,7 @@ namespace com.keyman.dom { Ltarg = Ltarg['body']; // Occurs in Firefox for design-mode iframes. } - // Makes sure we properly detect the TouchAliasElement root, + // Makes sure we properly detect the TouchAliasElement root, // rather than one of its constituent children. if(this.keyman.util.device.touchable) { Ltarg = findTouchAliasTarget(Ltarg); @@ -197,6 +197,15 @@ namespace com.keyman.dom { } } + if(DOMEventHandlers.states._IgnoreNextSelChange) { + // If a keyboard calls saveFocus() (KSF), then ignore the + // next selection change + DOMEventHandlers.states._IgnoreNextSelChange--; + e.cancelBubble = true; + e.stopPropagation(); + return true; + } + if(DOMEventHandlers.states._IgnoreBlurFocus) { // Prevent triggering other blur-handling events (as possible) e.cancelBubble = true; @@ -209,7 +218,7 @@ namespace com.keyman.dom { let lastAlias = this.keyman.domManager.activeElement; lastAlias.hideCaret(); } - + if (Ltarg.nodeType == 3) { // defeat Safari bug Ltarg = Ltarg.parentNode as HTMLElement; } @@ -219,7 +228,7 @@ namespace com.keyman.dom { Ltarg=Ltarg.contentWindow.frameElement as HTMLElement; } } - + ////keymanweb._SelectionControl = null; if(this.keyman.domManager.lastActiveElement) { this._BlurKeyboardSettings(this.keyman.domManager.lastActiveElement); @@ -233,7 +242,7 @@ namespace com.keyman.dom { * the user is manually specifying languages on a per-control basis. */ this.keyman.uiManager.justActivated = false; - + var isActivating = this.keyman.uiManager.isActivating; let activeKeyboard = com.keyman.singleton.core.activeKeyboard; if(!isActivating && activeKeyboard) { @@ -255,9 +264,9 @@ namespace com.keyman.dom { * @param {Object} _target element losing focus * @param {Event} _event event object * @param {(boolean|number)} _isActivating activation state - * @return {boolean} + * @return {boolean} * Description Execute external (UI) code needed on blur - */ + */ doControlBlurred(_target: HTMLElement, _event: Event, _isActivating: boolean|number): boolean { var p={}; p['target']=_target; @@ -275,7 +284,7 @@ namespace com.keyman.dom { _BlurKeyboardSettings(lastElem: HTMLElement, PInternalName?: string, PLgCode?: string) { var keyboardID = this.keyman.core.activeKeyboard ? this.keyman.core.activeKeyboard.id : ''; var langCode = this.keyman.keyboardManager.getActiveLanguage(); - + if(PInternalName !== undefined && PLgCode !== undefined) { keyboardID = PInternalName; langCode = PLgCode; @@ -296,7 +305,7 @@ namespace com.keyman.dom { * Description Restores the newly active element's keyboard settings. Should be called * whenever a KMW-enabled page element gains control, but only once the prior * element's loss of control is guaranteed. - */ + */ _FocusKeyboardSettings(lastElem: HTMLElement, blockGlobalChange: boolean) { if(lastElem && lastElem._kmwAttachment.keyboard != null) { this.keyman.keyboardManager.setActiveKeyboard(lastElem._kmwAttachment.keyboard, @@ -314,10 +323,10 @@ namespace com.keyman.dom { /** * Function _CommonFocusHelper - * @param {Element} target + * @param {Element} target * @returns {boolean} - * Description Performs common state management for the various focus events of KeymanWeb. - * The return value indicates whether (true) or not (false) the calling event handler + * Description Performs common state management for the various focus events of KeymanWeb. + * The return value indicates whether (true) or not (false) the calling event handler * should be terminated immediately after the call. */ _CommonFocusHelper(target: HTMLElement): boolean { @@ -328,23 +337,23 @@ namespace com.keyman.dom { if(target.ownerDocument && target instanceof target.ownerDocument.defaultView.HTMLIFrameElement) { if(!this.keyman.domManager._IsIEEditableIframe(target, 1) || !this.keyman.domManager._IsMozillaEditableIframe(target, 1)) { - DOMEventHandlers.states._DisableInput = true; + DOMEventHandlers.states._DisableInput = true; return true; } } - DOMEventHandlers.states._DisableInput = false; + DOMEventHandlers.states._DisableInput = false; let activeKeyboard = keyman.core.activeKeyboard; if(!uiManager.justActivated) { if(target && Utils.getOutputTarget(target)) { Utils.getOutputTarget(target).deadkeys().clear(); } - + if(activeKeyboard) { activeKeyboard.notify(0, Utils.getOutputTarget(target), 1); // I2187 } } - + if(!uiManager.justActivated && DOMEventHandlers.states._SelectionControl != target) { uiManager.isActivating = false; } @@ -358,28 +367,15 @@ namespace com.keyman.dom { return false; } - /** - * Function _SelectionChange - * Scope Private - * Description Respond to selection change event - */ - _SelectionChange: () => boolean = function(this: DOMEventHandlers): boolean { - if(DOMEventHandlers.states._IgnoreNextSelChange) { - DOMEventHandlers.states._IgnoreNextSelChange--; - } - return true; - }.bind(this); - - /** * Function _KeyDown * Scope Private - * Description Processes keydown event and passes data to keyboard. - * + * Description Processes keydown event and passes data to keyboard. + * * Note that the test-case oriented 'recorder' stubs this method to facilitate keystroke * recording for use in test cases. If changing this function, please ensure the recorder is * not affected. - */ + */ _KeyDown: (e: KeyboardEvent) => boolean = function(this: DOMEventHandlers, e: KeyboardEvent): boolean { var activeKeyboard = this.keyman.core.activeKeyboard; var util = this.keyman.util; @@ -395,7 +391,7 @@ namespace com.keyman.dom { return true; } } else if(el && el.className.indexOf('kmw-disabled') >= 0) { - return true; + return true; } return PreProcessor.keyDown(e); @@ -425,7 +421,7 @@ namespace com.keyman.dom { * Function _KeyPress * Scope Private * Description Processes keypress event (does not pass data to keyboard) - */ + */ _KeyPress: (e: KeyboardEvent) => boolean = function(this: DOMEventHandlers, e: KeyboardEvent): boolean { if(DOMEventHandlers.states._DisableInput || this.keyman.core.activeKeyboard == null) { return true; @@ -438,7 +434,7 @@ namespace com.keyman.dom { * Function _KeyUp * Scope Private * Description Processes keyup event and passes event data to keyboard - */ + */ _KeyUp: (e: KeyboardEvent) => boolean = function(this: DOMEventHandlers, e: KeyboardEvent): boolean { var osk = this.keyman.osk; @@ -457,7 +453,7 @@ namespace com.keyman.dom { if(outputTarget instanceof inputEle.ownerDocument.defaultView.HTMLTextAreaElement) { ignore = true; } - + if(inputEle.base && inputEle.base instanceof inputEle.base.ownerDocument.defaultView.HTMLTextAreaElement) { ignore = true; } @@ -473,8 +469,8 @@ namespace com.keyman.dom { } return true; } - } - + } + return PreProcessor.keyUp(e); }.bind(this); } @@ -490,7 +486,7 @@ namespace com.keyman.dom { y: number; }; - + constructor(keyman: KeymanBase) { super(keyman); } @@ -510,7 +506,7 @@ namespace com.keyman.dom { // whether or not individual `Touch`es may be related to this specific event for // an ongoing multitouch scenario. let target = e.target; - + // Find the first touch affected by this event that matches the current target. for(let i=0; i < e.changedTouches.length; i++) { if(isValidTouch(e.changedTouches[i], target)) { @@ -524,9 +520,9 @@ namespace com.keyman.dom { } /** - * Handle receiving focus by simulated input field - * - */ + * Handle receiving focus by simulated input field + * + */ setFocus: (e?: TouchEvent|MSPointerEvent) => void = function(this: DOMTouchHandlers, e?: TouchEvent|MSPointerEvent): void { DOMEventHandlers.states.setFocusTimer(); @@ -580,7 +576,7 @@ namespace com.keyman.dom { // Some parts rely upon the scroller element. let scroller = target.firstChild as HTMLElement; - // Move the caret and refocus if necessary + // Move the caret and refocus if necessary if(this.keyman.domManager.activeElement != target) { // Hide the KMW caret let prevTarget = this.keyman.domManager.activeElement; @@ -600,26 +596,26 @@ namespace com.keyman.dom { // The issue here is that touching a DIV does not actually set the focus for iOS, even when enabled to accept focus (by setting tabIndex=0) // We must explicitly set the focus in order to remove focus from any non-KMW input target.focus(); //Android native browsers may not like this, but it is needed for Chrome, Safari - } - + } + // Correct element directionality if required - this.keyman.domManager._SetTargDir(target); - + this.keyman.domManager._SetTargDir(target); + // If clicked on DIV on the main element, rather than any part of the text representation, // set caret to end of text if(tTarg && tTarg == target) { var x,cp; - x=dom.Utils.getAbsoluteX(scroller.firstChild as HTMLElement); - if(target.dir == 'rtl') { - x += (scroller.firstChild as HTMLElement).offsetWidth; + x=dom.Utils.getAbsoluteX(scroller.firstChild as HTMLElement); + if(target.dir == 'rtl') { + x += (scroller.firstChild as HTMLElement).offsetWidth; cp=(touchX > x ? 0 : 100000); } else { cp=(touchX touchY && cp > cpMin && cp != cpMax) {cpMax=cp; cp=Math.round((cp+cpMin)/2);} else if(y < touchY-yRow && cp < cpMax && cp != cpMin) {cpMin=cp; cp=Math.round((cp+cpMax)/2);} else break; @@ -656,21 +652,21 @@ namespace com.keyman.dom { var snapOrder; if(target.dir == 'rtl') { // I would use arrow functions, but IE doesn't like 'em. snapOrder = function(a, b) { - return a < b; + return a < b; }; } else { - snapOrder = function(a, b) { - return a > b; + snapOrder = function(a, b) { + return a > b; }; } for(iLoop=0; iLoop<16; iLoop++) { - x=dom.Utils.getAbsoluteX(caret); //left of caret + x=dom.Utils.getAbsoluteX(caret); //left of caret if(snapOrder(x, touchX) && cp > cpMin && cp != cpMax) { - cpMax=cp; + cpMax=cp; cp=Math.round((cp+cpMin)/2); } else if(!snapOrder(x, touchX) && cp < cpMax && cp != cpMin) { - cpMin=cp; + cpMin=cp; cp=Math.round((cp+cpMax)/2); } else { break; @@ -691,7 +687,7 @@ namespace com.keyman.dom { * for controls, we have to act here to preserve the outgoing control's keyboard settings. * * If we 'just activated' the KeymanWeb UI, we need to save the new keyboard change as appropriate. - */ + */ if(this.keyman.domManager.lastActiveElement) { this._BlurKeyboardSettings(this.keyman.domManager.lastActiveElement); } @@ -705,7 +701,7 @@ namespace com.keyman.dom { * If not, we need to activate the control's preferred keyboard. */ this._FocusKeyboardSettings(target, false); - + // Always do the common focus stuff, instantly returning if we're in an editable iframe. // This parallels the if-statement in _ControlFocus - it may be needed as this if-statement in the future, // despite its present redundancy. @@ -716,15 +712,15 @@ namespace com.keyman.dom { /** * Close OSK and remove simulated caret on losing focus - */ + */ cancelInput(): void { - this.keyman.domManager.activeElement = null; + this.keyman.domManager.activeElement = null; this.keyman.domManager.lastActiveElement = null; this.keyman.osk.hideNow(); }; /** - * Handle losing focus from simulated input field + * Handle losing focus from simulated input field */ setBlur: (e: FocusEvent) => void = function(this: DOMTouchHandlers, e: FocusEvent) { // This works OK for iOS, but may need something else for other platforms @@ -734,7 +730,7 @@ namespace com.keyman.dom { elem = e.relatedTarget as HTMLElement; } - this.executeBlur(elem); + this.executeBlur(elem); }.bind(this); executeBlur(elem: HTMLElement) { @@ -743,7 +739,7 @@ namespace com.keyman.dom { if(elem) { this.doChangeEvent(elem); if(elem.nodeName != 'DIV' || elem.className.indexOf('keymanweb-input') == -1) { - this.cancelInput(); + this.cancelInput(); return; } } @@ -756,7 +752,7 @@ namespace com.keyman.dom { /** * Display and position a scrollbar in the input field if needed - * + * * @param {Object} e input DIV element (copy of INPUT or TEXTAREA) */ setScrollBar(e: HTMLElement) { @@ -767,21 +763,29 @@ namespace com.keyman.dom { sbs.width=100*(e.offsetWidth/scroller.offsetWidth)+'%'; sbs.left=100*(-scroller.offsetLeft/scroller.offsetWidth)+'%'; sbs.top='0'; - sbs.visibility='visible'; + sbs.visibility='visible'; } else if(scroller.offsetHeight > e.offsetHeight || scroller.offsetTop < 0) { sbs.width='4px'; sbs.height=100*(e.offsetHeight/scroller.offsetHeight)+'%'; sbs.top=100*(-scroller.offsetTop/scroller.offsetHeight)+'%'; - sbs.left='0'; - sbs.visibility='visible'; + sbs.left='0'; + sbs.visibility='visible'; } else { sbs.visibility='hidden'; } - } + } + + /** + * Handle the touch end event for an input element + */ + dragEnd: (e: TouchEvent|MouseEvent) => void = function(this: DOMTouchHandlers, e: TouchEvent|MouseEvent) { + e.stopPropagation(); + this.firstTouch = null; + }.bind(this); /** * Handle the touch move event for an input element - */ + */ dragInput: (e: TouchEvent|MouseEvent) => void = function(this: DOMTouchHandlers, e: TouchEvent|MouseEvent) { // Prevent dragging window if(e.cancelable) { @@ -789,12 +793,12 @@ namespace com.keyman.dom { // Tends to result in a spam of console errors when e.cancelable == false. e.preventDefault(); } - e.stopPropagation(); + e.stopPropagation(); // Identify the target from the touch list or the event argument (IE 10 only) var target: HTMLElement; let touch: Touch; - + if(dom.Utils.instanceof(e, "TouchEvent")) { try { touch=DOMTouchHandlers.selectTouch(e as TouchEvent); @@ -809,7 +813,7 @@ namespace com.keyman.dom { if(target == null) { return; } - + // Identify the input element from the touch event target (touched element may be contained by input) target = findTouchAliasTarget(target); @@ -826,22 +830,22 @@ namespace com.keyman.dom { x = (e as MouseEvent).screenX; y = (e as MouseEvent).screenY; } - + // Allow content of input elements to be dragged horizontally or vertically if(typeof this.firstTouch == 'undefined' || this.firstTouch == null) { this.firstTouch={x:x,y:y}; } else { var x0=this.firstTouch.x,y0=this.firstTouch.y, scroller=target.firstChild as HTMLElement,dx,dy,x1; - + if(target.base.nodeName == 'TEXTAREA') { var yOffset=parseInt(scroller.style.top,10); if(isNaN(yOffset)) yOffset=0; dy=y0-y; if(dy < -4 || dy > 4) { scroller.style.top=(yOffset xMin) x1=xMin; if(x1 < xMax) x1=xMax; scroller.style.left=x1+'px'; - this.firstTouch.x=x; - } + this.firstTouch.x=x; + } } } // Should refactor to use TouchAliasElement's version; target is an instance of the class. @@ -865,9 +869,9 @@ namespace com.keyman.dom { /** * Scroll the document body vertically to bring the active input into view - * + * * @param {Object} e simulated input field object being focussed - */ + */ scrollBody(e: HTMLElement): void { var osk = this.keyman.osk; @@ -882,7 +886,7 @@ namespace com.keyman.dom { } else { dy=y-t-(window.innerHeight-osk._Box.offsetHeight-s2.offsetHeight-2); if(dy < 0) dy=0; - } + } // Hide OSK, then scroll, then re-anchor OSK with absolute position (on end of scroll event) if(dy != 0) { window.scrollTo(0,dy+window.pageYOffset); diff --git a/web/source/dom/domManager.ts b/web/source/dom/domManager.ts index ea0053d9da..1b00d74d9c 100644 --- a/web/source/dom/domManager.ts +++ b/web/source/dom/domManager.ts @@ -58,8 +58,8 @@ namespace com.keyman.dom { enablementObserver: MutationObserver; /** - * Tracks a list of event-listening elements. - * + * Tracks a list of event-listening elements. + * * In touch mode, this should contain touch-aliasing DIVs, but will contain other elements in non-touch mode. */ inputList: HTMLElement[] = []; // List of simulated input divisions for touch-devices I3363 (Build 301) @@ -70,7 +70,7 @@ namespace com.keyman.dom { sortedInputs: HTMLElement[] = []; // List of all INPUT and TEXTAREA elements ordered top to bottom, left to right _BeepObjects: BeepData[] = []; // BeepObjects - maintains a list of active 'beep' visual feedback elements - _BeepTimeout: number = 0; // BeepTimeout - a flag indicating if there is an active 'beep'. + _BeepTimeout: number = 0; // BeepTimeout - a flag indicating if there is an active 'beep'. // Set to 1 if there is an active 'beep', otherwise leave as '0'. // Used for special touch-based page interactions re: element activation on touch devices. @@ -84,7 +84,7 @@ namespace com.keyman.dom { constructor(keyman: KeymanBase) { this.keyman = keyman; - + if(keyman.util.device.touchable) { this.touchHandlers = new DOMTouchHandlers(keyman); } @@ -101,7 +101,7 @@ namespace com.keyman.dom { if(this.attachmentObserver) { this.attachmentObserver.disconnect(); } - + for(let input of this.inputList) { this.disableInputElement(input); } @@ -127,7 +127,7 @@ namespace com.keyman.dom { * Scope Public * @param {Object} Pelem element to flash * Description Flash body as substitute for audible beep; notify embedded device to vibrate - */ + */ doBeep(outputTarget: targets.OutputTarget) { // Handles embedded-mode beeps. let keyman = com.keyman.singleton; @@ -149,7 +149,7 @@ namespace com.keyman.dom { if(!Pelem) { return; // There's no way to signal a 'beep' to null, so just cut everything short. } - + if(!Pelem.style || typeof(Pelem.style.backgroundColor)=='undefined') { return; } @@ -160,7 +160,7 @@ namespace com.keyman.dom { return; } } - + this._BeepObjects = com.keyman.singleton._push(this._BeepObjects, new BeepData(Pelem)); // TODO: This is probably a bad color choice if "dark mode" is enabled. A proper implementation // would probably require some 'fun' CSS work, though. @@ -172,10 +172,10 @@ namespace com.keyman.dom { } /** - * Function beepReset + * Function beepReset * Scope Public * Description Reset/terminate beep or flash (not currently used: Aug 2011) - */ + */ beepReset(): void { com.keyman.singleton.core.keyboardInterface.resetContextCache(); @@ -191,7 +191,7 @@ namespace com.keyman.dom { * Function getHandlers * Scope Private * @param {Element} Pelem An input, textarea, or touch-alias element from the page. - * @returns {Object} + * @returns {Object} */ getHandlers(Pelem: HTMLElement): DOMEventHandlers { var _attachObj = Pelem.base ? Pelem.base._kmwAttachment : Pelem._kmwAttachment; @@ -213,9 +213,9 @@ namespace com.keyman.dom { * an outer DIV, matching the position, size and style of the base element * a scrollable DIV within that outer element * two SPAN elements within the scrollable DIV, to hold the text before and after the caret - * - * The left border of the second SPAN is flashed on and off as a visible caret - * + * + * The left border of the second SPAN is flashed on and off as a visible caret + * * Also ensures the element is registered on keymanweb's internal input list. */ enableTouchElement(Pelem: HTMLElement) { @@ -229,9 +229,9 @@ namespace com.keyman.dom { return false; } else { // Initialize and protect input elements for touch-screen devices (but never for apps) - // NB: now set disabled=true rather than readonly, since readonly does not always + // NB: now set disabled=true rather than readonly, since readonly does not always // prevent element from getting focus, e.g. within a LABEL element. - // c.f. http://kreotekdev.wordpress.com/2007/11/08/disabled-vs-readonly-form-fields/ + // c.f. http://kreotekdev.wordpress.com/2007/11/08/disabled-vs-readonly-form-fields/ Pelem.kmwInput = true; } @@ -249,7 +249,7 @@ namespace com.keyman.dom { } this.inputList.push(Pelem['kmw_ip']); - + console.log("Unexpected state - this element's simulated input DIV should have been removed from the page!"); return true; // May need setup elsewhere since it's just been re-added! @@ -263,18 +263,18 @@ namespace com.keyman.dom { this.setupElementAttachment(x); // The touch-alias should have its own wrapper. } Pelem._kmwAttachment = x._kmwAttachment; // It's an object reference we need to alias. - + // Set font for base element this.enableInputElement(x, true); - // Superimpose custom input fields for each input or textarea, unless readonly or disabled - + // Superimpose custom input fields for each input or textarea, unless readonly or disabled + // On touch event, reposition the text caret and prepare for OSK input - // Removed 'onfocus=' as that resulted in handling the event twice (on iOS, anyway) + // Removed 'onfocus=' as that resulted in handling the event twice (on iOS, anyway) // We know this to be the correct set of handlers because we're setting up a touch element. var touchHandlers = this.touchHandlers; - + x.addEventListener('touchstart', touchHandlers.setFocus); x.onmspointerdown=function(e: MSPointerEvent) { e.preventDefault(); @@ -282,21 +282,19 @@ namespace com.keyman.dom { return touchHandlers.setFocus(e); }; - x.addEventListener('touchend', function(e) { - e.stopPropagation(); - }); + x.addEventListener('touchend', touchHandlers.dragEnd, false); x.onmspointerup=function(e) { e.stopPropagation(); }; - - // Disable internal scroll when input element in focus + + // Disable internal scroll when input element in focus x.addEventListener('touchmove', touchHandlers.dragInput, false); x.onmspointermove=touchHandlers.dragInput; - + // Hide keyboard and caret when losing focus from simulated input field x.onblur=touchHandlers.setBlur; - + // Note that touchend event propagates and is processed by body touchend handler // re-setting the first touch point for a drag @@ -331,7 +329,7 @@ namespace com.keyman.dom { // Disable touch-related handling code. this.disableInputElement(Pelem['kmw_ip']); Pelem._kmwAttachment.interface = dom.targets.wrapElement(Pelem); - + // We get weird repositioning errors if we don't remove our simulated input element - and permanently. if(Pelem.parentNode) { Pelem.parentNode.removeChild(Pelem['kmw_ip']); @@ -342,7 +340,7 @@ namespace com.keyman.dom { this.setupNonKMWTouchElement(Pelem); } - /** + /** * Function nonKMWTouchHandler * Scope Private * Description A handler for KMW-touch-disabled elements when operating on touch devices. @@ -377,8 +375,8 @@ namespace com.keyman.dom { * Note that this method is called for both desktop and touch control routes; the touch route calls it from within * enableTouchElement as it must first establish the simulated touch element to serve as the alias "input element" here. * Note that the 'kmw-disabled' property is managed by the MutationObserver and by the surface API calls. - */ - enableInputElement(Pelem: HTMLElement, isAlias?: boolean) { + */ + enableInputElement(Pelem: HTMLElement, isAlias?: boolean) { var baseElement = isAlias ? Pelem['base'] : Pelem; if(!this.isKMWDisabled(baseElement)) { @@ -398,10 +396,10 @@ namespace com.keyman.dom { // These need to be on the actual input element, as otherwise the keyboard will disappear on touch. Pelem.onkeypress = this.getHandlers(Pelem)._KeyPress; Pelem.onkeydown = this.getHandlers(Pelem)._KeyDown; - Pelem.onkeyup = this.getHandlers(Pelem)._KeyUp; + Pelem.onkeyup = this.getHandlers(Pelem)._KeyUp; } - } - }; + } + }; /** * Function disableInputElement @@ -410,18 +408,18 @@ namespace com.keyman.dom { * @param {boolean=} isAlias A flag that indicates if the element is a simulated input element for touch. * Description Inverts the process of enableInputElement, removing all event-handling from the element. * Note that the 'kmw-disabled' property is managed by the MutationObserver and by the surface API calls. - */ - disableInputElement(Pelem: HTMLElement, isAlias?: boolean) { + */ + disableInputElement(Pelem: HTMLElement, isAlias?: boolean) { if(!Pelem) { return; } - + var baseElement = isAlias ? Pelem['base'] : Pelem; // Do NOT test for pre-disabledness - we also use this to fully detach without officially 'disabling' via kmw-disabled. if((Pelem.ownerDocument.defaultView && Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement) || Pelem instanceof HTMLIFrameElement) { this._DetachFromIframe(Pelem); - } else { + } else { var cnIndex = baseElement.className.indexOf('keymanweb-font'); if(cnIndex > 0 && !isAlias) { // See note about the alias below. baseElement.className = baseElement.className.replace('keymanweb-font', '').trim(); @@ -440,7 +438,7 @@ namespace com.keyman.dom { // These need to be on the actual input element, as otherwise the keyboard will disappear on touch. Pelem.onkeypress = null; Pelem.onkeydown = null; - Pelem.onkeyup = null; + Pelem.onkeyup = null; } // If we're disabling an alias, we should fully enable the base version. (Thinking ahead to toggleable-touch mode.) @@ -449,7 +447,7 @@ namespace com.keyman.dom { baseElement.onkeypress = this.getHandlers(Pelem)._KeyPress; baseElement.onkeydown = this.getHandlers(Pelem)._KeyDown; - baseElement.onkeyup = this.getHandlers(Pelem)._KeyUp; + baseElement.onkeyup = this.getHandlers(Pelem)._KeyUp; } var lastElem = this.lastActiveElement; @@ -460,7 +458,7 @@ namespace com.keyman.dom { this.lastActiveElement = null; this.keyman.osk.startHide(false); } - + return; }; @@ -470,7 +468,7 @@ namespace com.keyman.dom { * @param {Element} x An element from the page. * @return {boolean} true if the element's properties indicate a 'disabled' state. * Description Examines attachable elements to determine their default enablement state. - */ + */ isKMWDisabled(x: HTMLElement): boolean { var c = x.className; @@ -481,15 +479,15 @@ namespace com.keyman.dom { return true; } - return false; + return false; } /** * Function attachToControl * Scope Public * @param {Element} Pelem Element to which KMW will be attached - * Description Attaches KMW to control (or IFrame) - */ + * Description Attaches KMW to control (or IFrame) + */ attachToControl(Pelem: HTMLElement) { var touchable = this.keyman.util.device.touchable; @@ -519,8 +517,8 @@ namespace com.keyman.dom { * Function detachFromControl * Scope Public * @param {Element} Pelem Element from which KMW will detach - * Description Detaches KMW from a control (or IFrame) - */ + * Description Detaches KMW from a control (or IFrame) + */ detachFromControl(Pelem: HTMLElement) { if(!(this.isAttached(Pelem) || Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement)) { return; // We never were attached. @@ -531,10 +529,10 @@ namespace com.keyman.dom { // Is it already disabled? if(!this.isKMWDisabled(Pelem)) { this._DisableControl(Pelem); - } + } } - // #2 - clear attachment data. + // #2 - clear attachment data. this.clearElementAttachment(Pelem); } @@ -555,7 +553,7 @@ namespace com.keyman.dom { * @return {boolean} true if the element is viable for KMW attachment. * Description Examines potential input elements to determine whether or not they are viable for KMW attachment. * Also filters elements not supported for touch devices when device.touchable == true. - */ + */ isKMWInput(x: HTMLElement): boolean { var touchable = this.keyman.util.device.touchable; @@ -573,8 +571,8 @@ namespace com.keyman.dom { } } // else nothing? } - catch(err) { - /* Do not attempt to access iframes outside this site */ + catch(err) { + /* Do not attempt to access iframes outside this site */ console.warn("Error during attachment to / detachment from iframe: "); console.warn(err); } @@ -582,7 +580,7 @@ namespace com.keyman.dom { return true; } - return false; + return false; } /** @@ -590,7 +588,7 @@ namespace com.keyman.dom { * Scope Private * @param {Element} x An element from the page valid for KMW attachment * Description Establishes the base KeymanWeb data for newly-attached elements. - * Does not establish input hooks, which are instead handled during enablement. + * Does not establish input hooks, which are instead handled during enablement. */ setupElementAttachment(x: HTMLElement) { // The `_kmwAttachment` property tag maintains all relevant KMW-maintained data regarding the element. @@ -617,7 +615,7 @@ namespace com.keyman.dom { * Scope Private * @param {Element} x An element from the page valid for KMW attachment * Description Establishes the base KeymanWeb data for newly-attached elements. - * Does not establish input hooks, which are instead handled during enablement. + * Does not establish input hooks, which are instead handled during enablement. */ clearElementAttachment(x: HTMLElement) { // We need to clear the object when de-attaching; helps prevent memory leaks. @@ -628,11 +626,11 @@ namespace com.keyman.dom { * Function _AttachToIframe * Scope Private * @param {Element} Pelem IFrame to which KMW will be attached - * Description Attaches KeymanWeb to IFrame - */ + * Description Attaches KeymanWeb to IFrame + */ _AttachToIframe(Pelem: HTMLIFrameElement) { var util = this.keyman.util; - + try { var Lelem=Pelem.contentWindow.document; /* editable Iframe */ @@ -662,15 +660,15 @@ namespace com.keyman.dom { catch(err) { // do not attempt to attach to the iframe as it is from another domain - XSS denied! - } + } } /** * Function _DetachFromIframe * Scope Private * @param {Element} Pelem IFrame to which KMW will be attached - * Description Detaches KeymanWeb from an IFrame - */ + * Description Detaches KeymanWeb from an IFrame + */ _DetachFromIframe(Pelem: HTMLIFrameElement) { var util = this.keyman.util; @@ -703,7 +701,7 @@ namespace com.keyman.dom { catch(err) { // do not attempt to attach to the iframe as it is from another domain - XSS denied! - } + } } /** @@ -735,9 +733,9 @@ namespace com.keyman.dom { * Function LiTmp * Scope Private * @param {string} _colon type of element - * @return {Array} array of elements of specified type + * @return {Array} array of elements of specified type * Description Local function to get list of editable controls - */ + */ var LiTmp = function(_colon: string): HTMLElement[] { return util.arrayFromNodeList(Pelem.getElementsByTagName(_colon)); }; @@ -745,12 +743,12 @@ namespace com.keyman.dom { // Note that isKMWInput() will block IFRAME elements as necessary for touch-based devices. possibleInputs = possibleInputs.concat(LiTmp('INPUT'), LiTmp('TEXTAREA'), LiTmp('IFRAME')); } - + // Not all active browsers may support the method, but only those that do would work with contenteditables anyway. if(Pelem.querySelectorAll) { possibleInputs = possibleInputs.concat(util.arrayFromNodeList(Pelem.querySelectorAll('[contenteditable]'))); } - + if(Pelem.ownerDocument && Pelem instanceof Pelem.ownerDocument.defaultView.HTMLElement && Pelem.isContentEditable) { possibleInputs.push(Pelem); } @@ -795,13 +793,13 @@ namespace com.keyman.dom { /** * Set target element text direction (LTR or RTL), but only if the element is empty - * + * * If the element base directionality is changed after it contains content, unless all the text * has the same directionality, text runs will be re-ordered which is confusing and causes * incorrect caret positioning - * + * * @param {Object} Ptarg Target element - */ + */ _SetTargDir(Ptarg: HTMLElement) { let activeKeyboard = com.keyman.singleton.core.activeKeyboard; var elDir=(activeKeyboard && activeKeyboard.isRTL) ? 'rtl' : 'ltr'; @@ -814,7 +812,7 @@ namespace com.keyman.dom { alias.setTextCaret(10000); } } else { - if(Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLInputElement + if(Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLInputElement || Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLTextAreaElement) { if((Ptarg as HTMLInputElement|HTMLTextAreaElement).value.length == 0) { Ptarg.dir=elDir; @@ -830,8 +828,8 @@ namespace com.keyman.dom { * Function _DisableControl * Scope Private * @param {Element} Pelem Element to be disabled - * Description Disable KMW control element - */ + * Description Disable KMW control element + */ _DisableControl(Pelem: HTMLElement) { // Only operate on attached elements! Non-design-mode IFrames don't get attachment markers, so we check them specifically instead. if(this.isAttached(Pelem) || Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement) { @@ -840,7 +838,7 @@ namespace com.keyman.dom { this.setupNonKMWTouchElement(Pelem); var keyman = this.keyman; - + // If a touch alias was removed, chances are it's gonna mess up our touch-based layout scheme, so let's update the touch elements. window.setTimeout(function() { this.listInputs(); @@ -854,7 +852,7 @@ namespace com.keyman.dom { } else { this.listInputs(); // Fix up our internal input ordering scheme. } - + this.disableInputElement(Pelem); } } @@ -863,8 +861,8 @@ namespace com.keyman.dom { * Function _EnableControl * Scope Private * @param {Element} Pelem Element to be enabled - * Description Enable KMW control element - */ + * Description Enable KMW control element + */ _EnableControl(Pelem: HTMLElement) { if(this.isAttached(Pelem)) { // Only operate on attached elements! if(this.keyman.util.device.touchable) { @@ -908,36 +906,36 @@ namespace com.keyman.dom { if(t1[i].className.indexOf('kmw-disabled') < 0) { eList.push({ip:t1[i], x: dom.Utils.getAbsoluteX(t1[i]), y: dom.Utils.getAbsoluteY(t1[i])}); } - break; + break; } } - for(i=0; i= 0 : false; var disabledAfter = (mutation.target as HTMLElement).className.indexOf('kmw-disabled') >= 0; - + if(disabledBefore && !disabledAfter) { this._EnableControl(mutation.target); } else if(!disabledBefore && disabledAfter) { @@ -981,10 +979,10 @@ namespace com.keyman.dom { for(var i=0; i < mutations.length; i++) { var mutation = mutations[i]; - + for(var j=0; j < mutation.addedNodes.length; j++) { inputElementAdditions = inputElementAdditions.concat(this._GetDocumentEditables(mutation.addedNodes[j])); - } + } for(j = 0; j < mutation.removedNodes.length; j++) { inputElementRemovals = inputElementRemovals.concat(this._GetDocumentEditables(mutation.removedNodes[j])); @@ -1024,12 +1022,12 @@ namespace com.keyman.dom { } }.bind(this); - /** + /** * Function _MutationAdditionObserved * Scope Private * @param {Element} Pelem A page input, textarea, or iframe element. * Description Used by the MutationObserver event handler to properly setup any elements dynamically added to the document post-initialization. - * + * */ _MutationAdditionObserved = function(Pelem: HTMLElement) { if(Pelem instanceof Pelem.ownerDocument.defaultView.HTMLIFrameElement && !this.keyman.util.device.touchable) { @@ -1039,7 +1037,7 @@ namespace com.keyman.dom { var attachFunctor = function() { // Triggers at the same time as iframe's onload property, after its internal document loads. // Provide a minor delay to allow 'load' event handlers to set the design-mode property. - window.setTimeout(function() { + window.setTimeout(function() { domManager.attachToControl(Pelem); }, 1); }; @@ -1047,11 +1045,11 @@ namespace com.keyman.dom { Pelem.addEventListener('load', attachFunctor); // The following block breaks for design-mode iframes, at least in Chrome; a blank document may exist - // before the load of the desired actual document. + // before the load of the desired actual document. // // /* If the iframe has somehow already loaded, we can't expect the onload event to be raised. We ought just // * go ahead and perform our callback's contents. - // * + // * // * keymanweb.domManager.attachToControl() is now idempotent, so even if our call 'whiffs', it won't cause long-lasting // * problems. // */ @@ -1060,7 +1058,7 @@ namespace com.keyman.dom { // } } else { this.attachToControl(Pelem); - } + } } // Used by the mutation event handler to properly decouple any elements dynamically removed from the document. @@ -1078,12 +1076,12 @@ namespace com.keyman.dom { * Function disableControl * Scope Public * @param {Element} Pelem Element to be disabled - * Description Disables a KMW control element - */ + * Description Disables a KMW control element + */ disableControl(Pelem: HTMLElement) { if(!this.isAttached(Pelem)) { console.warn("KeymanWeb is not attached to element " + Pelem); - } + } var cn = Pelem.className; if(cn.indexOf('kmw-disabled') < 0) { // if not already explicitly disabled... @@ -1098,12 +1096,12 @@ namespace com.keyman.dom { * Function enableControl * Scope Public * @param {Element} Pelem Element to be disabled - * Description Disables a KMW control element - */ + * Description Disables a KMW control element + */ enableControl = function(Pelem: HTMLElement) { if(!this.isAttached(Pelem)) { console.warn("KeymanWeb is not attached to element " + Pelem); - } + } var cn = Pelem.className; var tagIndex = cn.indexOf('kmw-disabled'); @@ -1129,12 +1127,12 @@ namespace com.keyman.dom { /** * Function _WindowUnload * Scope Private - * Description Remove handlers before detaching KMW window - */ + * Description Remove handlers before detaching KMW window + */ _WindowUnload: () => void = function(this: DOMManager) { // Allow the UI to release its own resources this.keyman.uiManager.doUnload(); - + // Allow the OSK to release its own resources if(this.keyman.osk) { this.keyman.osk.shutdown(); @@ -1142,7 +1140,7 @@ namespace com.keyman.dom { this.keyman.osk['_Unload'](); // I3363 (Build 301) } } - + this.lastActiveElement = null; }.bind(this); @@ -1150,12 +1148,12 @@ namespace com.keyman.dom { /** * Function setKeyboardForControl - * Scope Public - * @param {Element} Pelem Control element - * @param {string|null=} Pkbd Keyboard (Clears the set keyboard if set to null.) + * Scope Public + * @param {Element} Pelem Control element + * @param {string|null=} Pkbd Keyboard (Clears the set keyboard if set to null.) * @param {string|null=} Plc Language Code - * Description Set default keyboard for the control - */ + * Description Set default keyboard for the control + */ setKeyboardForControl(Pelem: HTMLElement, Pkbd?: string, Plc?: string) { /* pass null for kbd to specify no default, or '' to specify the default system keyboard. */ if(Pkbd !== null && Pkbd !== undefined) { @@ -1196,8 +1194,8 @@ namespace com.keyman.dom { /** * Function getKeyboardForControl - * Scope Public - * @param {Element} Pelem Control element + * Scope Public + * @param {Element} Pelem Control element * @return {string|null} The independently-managed keyboard for the control. * Description Returns the keyboard ID of the current independently-managed keyboard for this control. * If it is currently following the global keyboard setting, returns null instead. @@ -1210,11 +1208,11 @@ namespace com.keyman.dom { return Pelem._kmwAttachment.keyboard; } } - + /** * Function getLanguageForControl - * Scope Public - * @param {Element} Pelem Control element + * Scope Public + * @param {Element} Pelem Control element * @return {string|null} The independently-managed keyboard for the control. * Description Returns the language code used with the current independently-managed keyboard for this control. * If it is currently following the global keyboard setting, returns null instead. @@ -1232,7 +1230,7 @@ namespace com.keyman.dom { /** * Set focus to last active target element (browser-dependent) - */ + */ focusLastActiveElement() { var lastElem = this.lastActiveElement; if(!lastElem) { @@ -1247,9 +1245,9 @@ namespace com.keyman.dom { /** * Get the last active target element *before* KMW activated (I1297) - * - * @return {Element} - */ + * + * @return {Element} + */ get lastActiveElement(): HTMLElement { return DOMEventHandlers.states._lastActiveElement; } @@ -1298,10 +1296,10 @@ namespace com.keyman.dom { } /** - * Set the active input element directly optionally setting focus - * + * Set the active input element directly optionally setting focus + * * @param {Object|string} e element id or element - * @param {boolean=} setFocus optionally set focus (KMEW-123) + * @param {boolean=} setFocus optionally set focus (KMEW-123) **/ setActiveElement(e: string|HTMLElement, setFocus?: boolean) { if(typeof e == "string") { // Can't instanceof string, and String is a different type. @@ -1347,7 +1345,7 @@ namespace com.keyman.dom { clientY: 0, target: e as HTMLElement }; - + // Kinda hacky, but gets the job done. (this.keyman.touchAliasing as DOMTouchHandlers).setFocusWithTouch(tEvent); } else { @@ -1357,7 +1355,7 @@ namespace com.keyman.dom { } /** Sets the active input element only if it is presently null. - * + * * @param {Element} */ initActiveElement(Lelem: HTMLElement) { @@ -1369,16 +1367,16 @@ namespace com.keyman.dom { /** * Move focus to next (or previous) input or text area element on TAB * Uses list of actual input elements - * + * * Note that activeElement() on touch devices returns the DIV that overlays * the input element, not the element itself. - * + * * @param {number|boolean} bBack Direction to move (0 or 1) */ moveToNext(bBack: number|boolean) { var i,t=this.sortedInputs, activeBase = this.activeElement; var touchable = this.keyman.util.device.touchable; - + if(t.length == 0) { return; } @@ -1401,7 +1399,7 @@ namespace com.keyman.dom { // Move to the selected element if(touchable) { - // Set focusing flag to prevent OSK disappearing + // Set focusing flag to prevent OSK disappearing DOMEventHandlers.states.focusing=true; var target=t[i]['kmw_ip']; @@ -1422,17 +1420,17 @@ namespace com.keyman.dom { /** * Move focus to user-specified element - * + * * @param {string|Object} e element or element id - * + * **/ moveToElement(e:string|HTMLElement) { var i; - + if(typeof(e) == "string") { // Can't instanceof string, and String is a different type. e=document.getElementById(e); } - + if(this.keyman.util.device.touchable && e['kmw_ip']) { e['kmw_ip'].focus(); } else { @@ -1446,10 +1444,10 @@ namespace com.keyman.dom { * Function _IsIEEditableIframe * Scope Private * @param {Object} Pelem Iframe element - * {boolean|number} PtestOn 1 to test if frame content is editable (TODO: unclear exactly what this is doing!) + * {boolean|number} PtestOn 1 to test if frame content is editable (TODO: unclear exactly what this is doing!) * @return {boolean} - * Description Test if element is an IE editable IFrame - */ + * Description Test if element is an IE editable IFrame + */ _IsIEEditableIframe(Pelem: HTMLIFrameElement, PtestOn?: number) { var Ldv, Lvalid = Pelem && (Ldv=Pelem.tagName) && Ldv.toLowerCase() == 'body' && (Ldv=Pelem.ownerDocument) && Ldv.parentWindow; return (!PtestOn && Lvalid) || (PtestOn && (!Lvalid || Pelem.isContentEditable)); @@ -1459,29 +1457,29 @@ namespace com.keyman.dom { * Function _IsMozillaEditableIframe * Scope Private * @param {Object} Pelem Iframe element - * @param {boolean|number} PtestOn 1 to test if 'designMode' is 'ON' - * @return {boolean} - * Description Test if element is a Mozilla editable IFrame - */ + * @param {boolean|number} PtestOn 1 to test if 'designMode' is 'ON' + * @return {boolean} + * Description Test if element is a Mozilla editable IFrame + */ _IsMozillaEditableIframe(Pelem: HTMLIFrameElement, PtestOn?: number) { var Ldv, Lvalid = Pelem && (Ldv=(Pelem).defaultView) && Ldv.frameElement; // Probable bug! return (!PtestOn && Lvalid) || (PtestOn && (!Lvalid || Ldv.document.designMode.toLowerCase()=='on')); } /* ----------------------- Initialization methods ------------------ */ - + /** * Get the user-specified (or default) font for the first mapped input or textarea element * before applying any keymanweb styles or classes - * + * * @return {string} - **/ + **/ getBaseFont() { var util = this.keyman.util; var ipInput = document.getElementsByTagName<'input'>('input'), ipTextArea=document.getElementsByTagName<'textarea'>('textarea'), n=0,fs,fsDefault='Arial,sans-serif'; - + // Find the first input element (if it exists) if(ipInput.length == 0 && ipTextArea.length == 0) { n=0; @@ -1503,7 +1501,7 @@ namespace com.keyman.dom { n=2; } } - + // Grab that font! switch(n) { case 0: @@ -1516,7 +1514,7 @@ namespace com.keyman.dom { if(typeof(fs) == 'undefined' || fs == 'monospace') { fs=fsDefault; } - + return fs; } @@ -1524,9 +1522,9 @@ namespace com.keyman.dom { * Function Initialization * Scope Public * @param {com.keyman.OptionType} arg object of user-defined properties - * Description KMW window initialization - */ - init: (arg: com.keyman.OptionType) => Promise = function(this: DOMManager, arg): Promise { + * Description KMW window initialization + */ + init: (arg: com.keyman.OptionType) => Promise = function(this: DOMManager, arg): Promise { var p,opt,dTrailer,ds; var util = this.keyman.util; var device = util.device; @@ -1537,22 +1535,22 @@ namespace com.keyman.dom { this.keyman.core.keyboardProcessor.errorLogger = console.error.bind(console); // Local function to convert relative to absolute URLs - // with respect to the source path, server root and protocol + // with respect to the source path, server root and protocol var fixPath = function(p) { if(p.length == 0) return p; - + // Add delimiter if missing if(p.substr(p.length-1,1) != '/') p = p+'/'; // Absolute - if((p.replace(/^(http)s?:.*/,'$1') == 'http') + if((p.replace(/^(http)s?:.*/,'$1') == 'http') || (p.replace(/^(file):.*/,'$1') == 'file')) - return p; - + return p; + // Absolute (except for protocol) if(p.substr(0,2) == '//') return this.keyman.protocol+p; - + // Relative to server root if(p.substr(0,1) == '/') return this.keyman.rootPath+p.substr(1); @@ -1560,40 +1558,40 @@ namespace com.keyman.dom { // Otherwise, assume relative to source path return this.keyman.srcPath+p; }.bind(this); - - // Explicit (user-defined) parameter initialization + + // Explicit (user-defined) parameter initialization opt=this.keyman.options; if(typeof(arg) == 'object' && arg !== null) { for(p in opt) - { + { if(arg.hasOwnProperty(p)) opt[p] = arg[p]; } } - + // Get default paths and device options if(opt['root'] != '') { - this.keyman.rootPath = fixPath(opt['root']); + this.keyman.rootPath = fixPath(opt['root']); } - // Keyboards and fonts are located with respect to the server root by default + // Keyboards and fonts are located with respect to the server root by default //if(opt['keyboards'] == '') opt['keyboards'] = keymanweb.rootPath+'keyboard/'; //if(opt['fonts'] == '') opt['fonts'] = keymanweb.rootPath+'font/'; - - // Resources are located with respect to the engine by default + + // Resources are located with respect to the engine by default if(opt['resources'] == '') { opt['resources'] = this.keyman.srcPath; } - + // Convert resource, keyboard and font paths to absolute URLs opt['resources'] = fixPath(opt['resources']); opt['keyboards'] = fixPath(opt['keyboards']); - opt['fonts'] = fixPath(opt['fonts']); + opt['fonts'] = fixPath(opt['fonts']); // Set default device options - this.keyman.setDefaultDeviceOptions(opt); - - // Only do remainder of initialization once! + this.keyman.setDefaultDeviceOptions(opt); + + // Only do remainder of initialization once! if(this.keyman.initialized) { return Promise.resolve(); } @@ -1617,12 +1615,12 @@ namespace com.keyman.dom { this.keyman._MasterDocument = window.document; /** - * Initialization of touch devices and browser interfaces must be done + * Initialization of touch devices and browser interfaces must be done * after all resources are loaded, during final stage of initialization - * - */ - - // Treat Android devices as phones if either (reported) screen dimension is less than 4" + * + */ + + // Treat Android devices as phones if either (reported) screen dimension is less than 4" if(device.OS == 'Android') { // Determine actual device characteristics I3363 (Build 301) @@ -1630,8 +1628,8 @@ namespace com.keyman.dom { var dpi = device.getDPI(); //TODO: this will not work when called from HEAD!! device.formFactor=((screen.height < 4.0 * dpi) || (screen.width < 4.0 * dpi)) ? 'phone' : 'tablet'; } - - // Set exposed initialization flag member for UI (and other) code to use + + // Set exposed initialization flag member for UI (and other) code to use this.keyman.setInitialized(1); // Finish keymanweb and initialize the OSK once all necessary resources are available @@ -1641,7 +1639,7 @@ namespace com.keyman.dom { this.keyman.osk = new com.keyman.osk.FloatingOSKView(device.coreSpec); } const osk = this.keyman.osk; - + // Create and save the remote keyboard loading delay indicator util.prepareWait(); @@ -1650,7 +1648,7 @@ namespace com.keyman.dom { // Initialize the desktop UI this.initializeUI(); - + // Exit initialization here if we're using an embedded code path. if(this.keyman.isEmbedded) { if(!this.keyman.keyboardManager.setDefaultKeyboard()) { @@ -1666,7 +1664,7 @@ namespace com.keyman.dom { // Initialize touch-screen device interface I3363 (Build 301) if(device.touchable) { this.keyman.handleRotationEvents(); - } + } // Initialize browser interface if(this.keyman.options['attachType'] != 'manual') { @@ -1675,16 +1673,16 @@ namespace com.keyman.dom { // Create an ordered list of all input and textarea fields this.listInputs(); - + // Initialize the OSK and set default OSK styles // Note that this should *never* be called before the OSK has been initialized. - // However, it possibly may be called before the OSK has been fully defined with the current keyboard, need to check. - //osk._Load(); - - //document.body.appendChild(osk._Box); + // However, it possibly may be called before the OSK has been fully defined with the current keyboard, need to check. + //osk._Load(); + + //document.body.appendChild(osk._Box); //osk._Load(false); - + // I3363 (Build 301) if(device.touchable) { const osk = keyman.osk as osk.AnchoredOSKView; @@ -1698,23 +1696,23 @@ namespace com.keyman.dom { ds=dTrailer.style; ds.width='100%'; ds.height=(screen.width/2)+'px'; - document.body.appendChild(dTrailer); - + document.body.appendChild(dTrailer); + // Sets up page-default touch-based handling for activation-state management. - // These always trigger for the page, wherever a touch may occur. Does not + // These always trigger for the page, wherever a touch may occur. Does not // prevent element-specific or OSK-key-specific handling from triggering. const _this = this; this.touchStartActivationHandler=function(e) { _this.deactivateOnRelease=true; _this.touchY=e.touches[0].screenY; - // On Chrome, scrolling up or down causes the URL bar to be shown or hidden + // On Chrome, scrolling up or down causes the URL bar to be shown or hidden // according to whether or not the document is at the top of the screen. // But when doing that, each OSK row top and height gets modified by Chrome - // looking very ugly. It would be best to hide the OSK then show it again + // looking very ugly. It would be best to hide the OSK then show it again // when the user scroll finishes, but Chrome has no way to reliably report // the touch end event after a move. c.f. http://code.google.com/p/chromium/issues/detail?id=152913 - // The best compromise behaviour is simply to hide the OSK whenever any + // The best compromise behaviour is simply to hide the OSK whenever any // non-input and non-OSK element is touched. _this.deactivateOnScroll=false; if(device.OS == 'Android' && navigator.userAgent.indexOf('Chrome') > 0) { @@ -1728,7 +1726,7 @@ namespace com.keyman.dom { if(typeof(p) != 'undefined' && p != null) { if(p.className.indexOf('keymanweb-input') >= 0) return false; if(p.className.indexOf('kmw-key-') >= 0) return false; - if(typeof(p.parentElement) != 'undefined') { + if(typeof(p.parentElement) != 'undefined' && p.parentElement != null) { p=p.parentElement; if(p.className.indexOf('keymanweb-input') >= 0) return false; if(p.className.indexOf('kmw-key-') >= 0) return false; @@ -1768,17 +1766,17 @@ namespace com.keyman.dom { } //document.body.appendChild(keymanweb._StyleBlock); - + // Restore and reload the currently selected keyboard, selecting a default keyboard if necessary. - this.keyman.keyboardManager.restoreCurrentKeyboard(); + this.keyman.keyboardManager.restoreCurrentKeyboard(); /* Setup of handlers for dynamically-added and (eventually) dynamically-removed elements. * Reference: https://developer.mozilla.org/en/docs/Web/API/MutationObserver - * + * * We place it here so that it loads after most of the other UI loads, reducing the MutationObserver's overhead. * Of course, we only want to dynamically add elements if the user hasn't enabled the manual attachment option. */ - + if(typeof MutationObserver == 'function') { var observationTarget = document.querySelector('body'), observationConfig: MutationObserverInit; if(this.keyman.options['attachType'] != 'manual') { //I1961 @@ -1794,7 +1792,7 @@ namespace com.keyman.dom { this.enablementObserver = new MutationObserver(this._EnablementMutationObserverCore); this.enablementObserver.observe(observationTarget, observationConfig); } else { - console.warn("Your browser is outdated and does not support MutationObservers, a web feature " + + console.warn("Your browser is outdated and does not support MutationObservers, a web feature " + "needed by KeymanWeb to support dynamically-added elements."); } @@ -1814,12 +1812,12 @@ namespace com.keyman.dom { /** * Initialize the desktop user interface as soon as it is ready - **/ + **/ initializeUI() { if(this.keyman.ui && this.keyman.ui['initialize'] instanceof Function) { this.keyman.ui['initialize'](); // Display the OSK (again) if enabled, in order to set its position correctly after - // adding the UI to the page + // adding the UI to the page this.keyman.osk.present(); } else if(this.keyman.isEmbedded) { // UI modules aren't utilized in embedded mode. There's nothing to init, so we simply diff --git a/web/source/keyboards/kmwkeyboards.ts b/web/source/keyboards/kmwkeyboards.ts index 5ac0622d51..54c118baf1 100644 --- a/web/source/keyboards/kmwkeyboards.ts +++ b/web/source/keyboards/kmwkeyboards.ts @@ -73,7 +73,7 @@ namespace com.keyman.keyboards { /** * Utility to convert stubs to KeyboardStub[] - * @param arg + * @param arg * @returns (KeyboardStub|ErrorStub)[] */ public static toStubs(arg: any): (KeyboardStub|ErrorStub)[] { @@ -187,8 +187,6 @@ namespace com.keyman.keyboards { activeStub: KeyboardStub = null; keyboardStubs: KeyboardStub[] = []; - firstCall: boolean = true; // First time to call keymanCloudRequest() - // For deferment of adding keyboards until keymanweb initializes deferment: Promise = null; endDeferment:() => void; @@ -962,8 +960,8 @@ namespace com.keyman.keyboards { /** * Returns a Promise of the merged keyboard stubs and error stubs. - * - * If the keyboard stub array is empty, will return a rejected Promise, + * + * If the keyboard stub array is empty, will return a rejected Promise, * otherwise returns a resolved Promise. * * @param keyboardStubs array of keyboard stubs to merge. @@ -1220,14 +1218,16 @@ namespace com.keyman.keyboards { const promiseFuncs = this.registrationResolvers[promiseid]; window.clearTimeout(promiseid); - try { - if(result instanceof Error) { - promiseFuncs.reject(result as Error); - } else { - promiseFuncs.resolve(result as KeyboardStub[]); + if(promiseFuncs) { + try { + if(result instanceof Error) { + promiseFuncs.reject(result as Error); + } else { + promiseFuncs.resolve(result as KeyboardStub[]); + } + } finally { + delete this.registrationResolvers[promiseid]; } - } finally { - delete this.registrationResolvers[promiseid]; } } } @@ -1283,106 +1283,87 @@ namespace com.keyman.keyboards { * @param {string[]} languages Array of language names * @returns {Promise<(KeyboardStub|ErrorStub)[]>} Promise of added keyboard stubs **/ - async addLanguageKeyboards(languages: string[]): Promise<(KeyboardStub|ErrorStub)[]> { - var i, j, lgName, cmd, addAll, retPromise; - + addLanguageKeyboards(languages: string[]): Promise<(KeyboardStub|ErrorStub)[]> { let errorStubs: ErrorStub[] = []; // Defer registering keyboards by language until the language list has been loaded if (this.languageList == null) { - if (this.firstCall) { - this.firstCall = false; - let promise = this.keymanCloudRequest('',true); - // If promise is not error, then... (needs an error guard) - promise.catch(function(error) { - let msg = "Unable to retrieve the master language list."; - console.error(msg, error) - let stub: ErrorStub = {error: new Error(msg)}; - errorStubs.push(stub); - return Promise.reject(errorStubs); + if (this.languageListPromise == null) { + this.languageListPromise = this.keymanCloudRequest('',true).catch((error) => { + // If promise is not error, then... (needs an error guard) + console.error(error); + errorStubs.push({error: error}); + return errorStubs; }); - this.languageListPromise = promise; } let _this = this; - retPromise = new Promise(function(resolve, reject) { + return new Promise<(KeyboardStub|ErrorStub)[]>(function(resolve, reject) { if (_this.languageListPromise) { // 1: wait for the language list to be loaded properly - _this.languageListPromise.then(function() { + return _this.languageListPromise.then(function() { // 2: perform the actual query, now that we can find the language code resolve(_this.addLanguageKeyboards(languages)); }).catch(function(error) { - return Promise.reject(error); + reject(error); }); } else { - return _this.addLanguageKeyboards(languages); + resolve(_this.addLanguageKeyboards(languages)); } }).then(function(result) { if (result instanceof Error) { - return Promise.reject(result); + throw result; } - return Promise.resolve(result); - }).catch(function(error) { - return Promise.reject(error); + return result; }); - - } else { // Identify and register each keyboard by language name - cmd = ''; - for(i=0; i} Promise of added keyboard/error stubs * */ - async ['addKeyboards'](...args: any[]) : + ['addKeyboards'](...args: any[]) : Promise<(com.keyman.keyboards.KeyboardStub|com.keyman.keyboards.ErrorStub)[]> { if (!args || !args[0] || args[0].length == 0) { // Get the cloud keyboard catalog - let stubs: (com.keyman.keyboards.KeyboardStub|com.keyman.keyboards.ErrorStub)[] = []; - try { - await this.keyboardManager.keymanCloudRequest('',false); - return Promise.resolve(stubs); - } catch(error) { + return this.keyboardManager.keymanCloudRequest('',false).catch(error => { console.error(error); - let stub: com.keyman.keyboards.ErrorStub = {error: error}; - stubs.push(stub); - return Promise.reject(stubs); - }; + return Promise.reject([{error: error}]); + }); } else { let x: (string|com.keyman.keyboards.KeyboardStub)[] = []; if (Array.isArray(args[0])) { diff --git a/web/source/osk/browser/keytip.ts b/web/source/osk/browser/keytip.ts index 115f20bfa1..38f859e4e2 100644 --- a/web/source/osk/browser/keytip.ts +++ b/web/source/osk/browser/keytip.ts @@ -4,10 +4,19 @@ namespace com.keyman.osk.browser { public key: KeyElement; public state: boolean = false; - private canvas: HTMLCanvasElement; - private label: HTMLSpanElement; + // ----- + // | | <-- tip + // | x | <-- label + // |_ _| + // | | + // | | <-- cap + // |___| - private constrain: boolean; + private readonly cap: HTMLDivElement; + private readonly tip: HTMLDivElement; + private readonly label: HTMLSpanElement; + + private readonly constrain: boolean; // constrain: keep the keytip within the bounds of the overall OSK. // Will probably be handled via function in a later pass. @@ -18,10 +27,15 @@ namespace com.keyman.osk.browser { // The following style is critical, so do not rely on external CSS tipElement.style.pointerEvents='none'; + tipElement.style.display='none'; - // Add CANVAS element for outline and SPAN for key label - tipElement.appendChild(this.canvas = document.createElement('canvas')); - tipElement.appendChild(this.label = document.createElement('span')); + tipElement.appendChild(this.tip = document.createElement('div')); + tipElement.appendChild(this.cap = document.createElement('div')); + this.tip.appendChild(this.label = document.createElement('span')); + + this.tip.className = 'kmw-keytip-tip'; + this.cap.className = 'kmw-keytip-cap'; + this.label.className = 'kmw-keytip-label'; this.constrain = constrain; } @@ -37,29 +51,31 @@ namespace com.keyman.osk.browser { // The key element is positioned relative to its key-square, which is, // in turn, relative to its row. Rows take 100% width, so this is sufficient. // + let rowElement = (key.key as OSKBaseKey).row.element; + // May need adjustment for borders if ever enabled for the desktop form-factor target. - var xLeft = (key.offsetParent as HTMLElement).offsetLeft, - xWidth = key.offsetWidth, - xHeight = key.offsetHeight, + let rkey = key.getClientRects()[0], rrow = rowElement.getClientRects()[0]; + let xLeft = rkey.left - rrow.left, + xWidth = rkey.width, + xHeight = rkey.height, kc = key.key.label, - edge = 0, previewFontScale = 1.8; // Canvas dimensions must be set explicitly to prevent clipping - this.canvas.width = 1.6 * xWidth; - this.canvas.height = 2.3 * xHeight; + let canvasWidth = 1.6 * xWidth; + let canvasHeight = 2.3 * xHeight; let kts = this.element.style; kts.top = 'auto'; + // Matches how the subkey positioning is set. - let rowElement = (key.key as OSKBaseKey).row.element; const _Box = vkbd.element.parentNode as HTMLDivElement; kts.bottom = (_Box.offsetHeight - rowElement.offsetHeight - rowElement.offsetTop + key.offsetTop) + 'px'; kts.textAlign = 'center'; kts.overflow = 'visible'; kts.fontFamily = util.getStyleValue(kc,'font-family'); - kts.width = this.canvas.width+'px'; - kts.height = this.canvas.height+'px'; + kts.width = canvasWidth+'px'; + kts.height = canvasHeight+'px'; var px=util.getStyleInt(kc, 'font-size'); if(px != 0) { @@ -75,39 +91,37 @@ namespace com.keyman.osk.browser { this.label.textContent = kc.textContent; - let ktls = this.label.style; - ktls.display = 'block'; - ktls.position = 'absolute'; - ktls.textAlign = 'center'; - ktls.width='100%'; - ktls.top = '2%'; - ktls.bottom = 'auto'; - - // Adjust canvas shape if at edges - var xOverflow = (this.canvas.width - xWidth) / 2; + // Adjust shape if at edges + var xOverflow = (canvasWidth - xWidth) / 2; if(xLeft < xOverflow) { - edge = -1; + this.cap.style.left = '0px'; xLeft += xOverflow; } else if(xLeft > window.innerWidth - xWidth - xOverflow) { - edge = 1; + this.cap.style.left = (canvasWidth - xWidth) + 'px'; xLeft -= xOverflow; + } else { + this.cap.style.left = xOverflow + 'px'; } + kts.left=(xLeft - xOverflow) + 'px'; + let cs = getComputedStyle(this.element); let oskHeight = keyman.osk.computedHeight; let bottomY = parseInt(cs.bottom, 10); let tipHeight = parseInt(cs.height, 10); - let delta = 0; + this.cap.style.width = xWidth + 'px'; + this.tip.style.height = (canvasHeight / 2) + 'px'; + this.cap.style.top = (canvasHeight / 2) + 'px'; + this.cap.style.height = (canvasHeight / 2 - 1) + 'px'; + if(this.constrain && tipHeight + bottomY > oskHeight) { - delta = tipHeight + bottomY - oskHeight; - this.canvas.height = this.canvas.height - delta; - kts.height = this.canvas.height + 'px'; + const delta = tipHeight + bottomY - oskHeight; + kts.height = (canvasHeight-delta) + 'px'; + const hx = Math.max(0, (canvasHeight-delta)-(canvasHeight/2)); + this.cap.style.height = hx + 'px'; } - this.drawPreview(this.canvas, vkbd.device, xWidth, xHeight, edge, delta); - - kts.left=(xLeft - xOverflow) + 'px'; kts.display = 'block'; } else { // Hide the key preview this.element.style.display = 'none'; @@ -117,80 +131,5 @@ namespace com.keyman.osk.browser { this.key = key; this.state = on; } - - /** - * Draw key preview in element using CANVAS - * @param {Object} canvas CANVAS element - * @param {number} w width of touched key, px - * @param {number} h height of touched key, px - * @param {number} edge -1 left edge, 1 right edge, else 0 - */ - drawPreview(canvas: HTMLCanvasElement, device: com.keyman.utils.DeviceSpec, w: number, h: number, edge: number, delta?: number) { - delta = delta || 0; - - var ctx = canvas.getContext('2d'), dx = (canvas.width - w)/2, hMax = canvas.height + delta, - w0 = 0, w1 = dx, w2 = w + dx, w3 = w + 2 * dx, - h1 = 0.5 * hMax, h2 = 0.6 * hMax, h3 = hMax, r = 8; - - let hBoundedMax = canvas.height; - - h2 = h2 > hBoundedMax ? hBoundedMax : h2; - h3 = hMax > hBoundedMax ? hBoundedMax : h3; - - if(device.OS == utils.OperatingSystem.Android) { - r = 3; - } - - // Adjust the preview shape at the edge of the keyboard - switch(edge) { - case -1: - w1 -= dx; - w2 -= dx; - break; - case 1: - w1 += dx; - w2 += dx; - break; - } - - // Clear the canvas - ctx.clearRect(0,0,canvas.width,canvas.height); - - // Define appearance of preview (cannot be done directly in CSS) - if(device.OS == utils.OperatingSystem.Android) { - var wx=(w1+w2)/2; - w1 = w2 = wx; - } - - let styleConsts = new utils.StyleConstants(device); - ctx.fillStyle = styleConsts.popupCanvasBackgroundColor; - ctx.lineWidth = 1; - ctx.strokeStyle = '#cccccc'; - - // Draw outline - ctx.save(); - ctx.beginPath(); - ctx.moveTo(w0+r,0); - ctx.arcTo(w3,0,w3,r,r); - if(device.OS == utils.OperatingSystem.Android) { - ctx.arcTo(w3,h1,w2,h2,r); - ctx.arcTo(w2,h2,w1,h2,r); - } else { - let lowerR = 0; - if(h3 > h2) { - lowerR = h3-h2 > r ? r : h3-h2; - } - ctx.arcTo(w3,h1,w2,h2,r); - ctx.arcTo(w2,h2,w2-lowerR,h3,lowerR); - ctx.arcTo(w2,h3,w1,h3,lowerR); - ctx.arcTo(w1,h3,w1,h2-lowerR,lowerR); - } - ctx.arcTo(w1,h2,w0,h1-r,r); - ctx.arcTo(w0,h1,w0,r,r); - ctx.arcTo(w0,0,w0+r,0,r); - ctx.fill(); - ctx.stroke(); - ctx.restore(); - }; } } \ No newline at end of file diff --git a/web/source/osk/browser/pendingLongpress.ts b/web/source/osk/browser/pendingLongpress.ts index 84b2364dc0..08f227fc39 100644 --- a/web/source/osk/browser/pendingLongpress.ts +++ b/web/source/osk/browser/pendingLongpress.ts @@ -7,12 +7,12 @@ namespace com.keyman.osk.browser { * whether or not a series of touch events corresponds to a longpress * touch input. The `resolve` method may be used to trigger the * subkey menu early, as with the upward quick-display shortcut. - * + * * This is the default implementation of longpress behavior for KMW. * Alterate implementations are modeled through the `embedded` * namespace's equivalent, which is designed to facilitate custom * modeling for such gestures. - * + * * Once the conditions to recognize a longpress gesture have been * fulfilled, this class's `promise` will resolve with a `SubkeyPopup` * matching the gesture's 'base' key, which itself provides a @@ -50,16 +50,24 @@ namespace com.keyman.osk.browser { this.timerId = null; } - if(this.resolver) { + if(this.resolver) { this.resolver(null); this.resolver = null; } } public resolve() { + // User has flicked up to get to the longpress, before + // the timeout has expired. We need to cancel the timeout. + // See #5950 + if(this.timerId) { + window.clearTimeout(this.timerId); + this.timerId = null; + } + if(this.resolver) { this.resolver(new SubkeyPopup(this.vkbd, this.baseKey)); } - } + } } } diff --git a/web/source/osk/oskKey.ts b/web/source/osk/oskKey.ts index c05f278f28..d156c07b27 100644 --- a/web/source/osk/oskKey.ts +++ b/web/source/osk/oskKey.ts @@ -175,7 +175,7 @@ namespace com.keyman.osk { * may be used to toggle which state the key's button class is in. * - shift <=> shift-on * - special <=> special-on - * @param {boolean=} flag The new toggle state + * @param {boolean=} flag The new toggle state */ public setToggleState(flag?: boolean) { let btnClassId: number; @@ -186,7 +186,7 @@ namespace com.keyman.osk { } else { btnClassId = this.spec['sp']; } - + // 1 + 2: shift + shift-on // 3 + 4: special + special-on switch(OSKKey.BUTTON_CLASSES[btnClassId]) { @@ -291,7 +291,7 @@ namespace com.keyman.osk { } // re-use canvas object for better performance - var canvas: HTMLCanvasElement = OSKKey.getTextMetrics['canvas'] || + var canvas: HTMLCanvasElement = OSKKey.getTextMetrics['canvas'] || (OSKKey.getTextMetrics['canvas'] = document.createElement("canvas")); var context = canvas.getContext("2d"); context.font = fontSize + " " + fontFamily; @@ -304,7 +304,7 @@ namespace com.keyman.osk { let buttonStyle = getComputedStyle(this.btn); let keyWidth = parseFloat(buttonStyle.width); let emScale = 1; - + const originalSize = getFontSizeStyle(style.fontSize || '1em'); // Not yet available; it'll be handled in a later layout pass. @@ -404,6 +404,29 @@ namespace com.keyman.osk { oldText; } + + private unicodeKeyIdToString(id: string): string { + // This is similar to defaultOutput.ts:forUnicodeKeynames and could potentially + // be refactored in the future. + if(!id || id.substr(0,2) != 'U_') { + return null; + } + + let result = ''; + const codePoints = id.substr(2).split('_'); + for(let codePoint of codePoints) { + const codePointValue = parseInt(codePoint, 16); + if (((0x0 <= codePointValue) && (codePointValue <= 0x1F)) || ((0x80 <= codePointValue) && (codePointValue <= 0x9F))) { + continue; + } else { + // String.fromCharCode() is inadequate to handle the entire range of Unicode + // Someday after upgrading to ES2015, can use String.fromCodePoint() + result += String.kmwFromCharCode(codePointValue); + } + } + return result ? result : null; + } + // Produces a HTMLSpanElement with the key's actual text. protected generateKeyText(vkbd: VisualKeyboard): HTMLSpanElement { let spec = this.spec; @@ -415,9 +438,7 @@ namespace com.keyman.osk { keyText='\xa0'; // default: nbsp. if(typeof spec['id'] == 'string') { // If the ID's Unicode-based, just use that code. - if(/^U_[0-9A-F]{4}$/i.test(spec['id'])) { - keyText=String.fromCharCode(parseInt(spec['id'].substr(2),16)); - } + keyText = this.unicodeKeyIdToString(spec['id']) || keyText; } } else { keyText=spec['text']; @@ -489,7 +510,7 @@ namespace com.keyman.osk { let btn = this.btn; // These functions do not account for 'fixed' positioning. - let x0 = dom.Utils.getAbsoluteX(btn); + let x0 = dom.Utils.getAbsoluteX(btn); let y0 = dom.Utils.getAbsoluteY(btn); let isFixed = false; @@ -502,12 +523,12 @@ namespace com.keyman.osk { node = node.offsetParent as HTMLElement; } } - + if(isFixed) { x0 += window.pageXOffset; y0 += window.pageYOffset; } - + let x1 = x0 + btn.offsetWidth; let y1 = y0 + btn.offsetHeight; diff --git a/web/source/osk/oskView.ts b/web/source/osk/oskView.ts index 2a8756854f..d4bfd23f44 100644 --- a/web/source/osk/oskView.ts +++ b/web/source/osk/oskView.ts @@ -22,7 +22,7 @@ namespace com.keyman.osk { manual = "manual", automatic = "automatic" } - + export abstract class OSKView { _Box: HTMLDivElement; @@ -34,23 +34,23 @@ namespace com.keyman.osk { protected device: com.keyman.utils.DeviceSpec; protected readonly hostDevice: com.keyman.utils.DeviceSpec; - private _boxBaseMouseDown: (e: MouseEvent) => boolean; + private _boxBaseMouseDown: (e: MouseEvent) => boolean; private _boxBaseTouchStart: (e: TouchEvent) => boolean; private _boxBaseTouchEventCancel: (e: TouchEvent) => boolean; private keyboard: keyboards.Keyboard; private _target: text.OutputTarget; - + /** * The configured width for this OSKManager. May be `undefined` or `null` - * to allow automatic width scaling. + * to allow automatic width scaling. */ private _width: ParsedLengthStyle; /** * The configured height for this OSKManager. May be `undefined` or `null` - * to allow automatic height scaling. + * to allow automatic height scaling. */ private _height: ParsedLengthStyle; @@ -79,7 +79,6 @@ namespace com.keyman.osk { private _displayIfActive: boolean = true; private _animatedHideTimeout: number; - private _animatedHideResolver: () => void; constructor(deviceSpec: com.keyman.utils.DeviceSpec, hostDevice?: com.keyman.utils.DeviceSpec) { this.device = deviceSpec; @@ -111,7 +110,7 @@ namespace com.keyman.osk { // Register a listener for model change events so that we can hot-swap the banner as needed. // Handled here b/c banner changes may trigger a need to re-layout the OSK. const _this = this; - keymanweb.core.languageProcessor.on('statechange', + keymanweb.core.languageProcessor.on('statechange', function(state: text.prediction.StateChangeEnum) { let currentType = _this.bannerView.activeType; _this.bannerView.selectBanner(state); @@ -182,7 +181,7 @@ namespace com.keyman.osk { /** * Gets and sets the IME-like interface (`OutputTarget`) to be affected by events from * the OSK. - * + * * If `activationMode` is `'conditional'`, this property's state controls the visibility * of the OSKView. */ @@ -259,11 +258,11 @@ namespace com.keyman.osk { /** * A property denoting whether or not the OSK should be presented if it meets its - * activation conditions. - * + * activation conditions. + * * When `activationMode == 'manual'`, `displayIfActive == true` is the lone * activation condition. - * + * * Note: cannot be set to `false` if `activationMode == 'static'`. */ get displayIfActive(): boolean { @@ -318,7 +317,7 @@ namespace com.keyman.osk { /** * The configured width for this VisualKeyboard. May be `undefined` or `null` - * to allow automatic width scaling. + * to allow automatic width scaling. */ get width(): ParsedLengthStyle { return this._width; @@ -326,7 +325,7 @@ namespace com.keyman.osk { /** * The configured height for this VisualKeyboard. May be `undefined` or `null` - * to allow automatic height scaling. + * to allow automatic height scaling. */ get height(): ParsedLengthStyle { return this._height; @@ -357,7 +356,7 @@ namespace com.keyman.osk { } /** - * The top-level style string for the font size used by the predictive banner + * The top-level style string for the font size used by the predictive banner * and the primary keyboard visualization elements. */ get baseFontSize(): string { @@ -397,7 +396,7 @@ namespace com.keyman.osk { return ParsedLengthStyle.special(fontScale, 'em'); } else { return this.computedHeight ? ParsedLengthStyle.inPixels(this.computedHeight / 8) : undefined; - } + } } public get activeKeyboard(): keyboards.Keyboard { @@ -509,9 +508,6 @@ namespace com.keyman.osk { availableHeight -= this.bannerView.height + 5; } this.vkbd.setSize(this.computedWidth, availableHeight, pending); - if(!pending) { - this.vkbd.refreshLayout(); - } const bs = this._Box.style; // OSK size settings can only be reliably applied to standard VisualKeyboard @@ -614,7 +610,7 @@ namespace com.keyman.osk { private layerChangeHandler: text.SystemStoreMutationHandler = function(this: OSKView, source: text.MutableSystemStore, newValue: string) { - // This handler is also triggered on state-key state changes (K_CAPS) that + // This handler is also triggered on state-key state changes (K_CAPS) that // may not actually change the layer. if(this.vkbd) { this.vkbd._UpdateVKShiftStyle(); @@ -691,7 +687,7 @@ namespace com.keyman.osk { /** * The main function for presenting the OSKView. - * + * * This includes: * - refreshing its layout * - displaying it @@ -719,7 +715,7 @@ namespace com.keyman.osk { /* In case it's still '0' from a hide() operation. * - * (Opacity is only modified when device.touchable = true, + * (Opacity is only modified when device.touchable = true, * though a couple of extra conditions may apply.) */ this._Box.style.opacity = '1'; @@ -808,7 +804,7 @@ namespace com.keyman.osk { } /** - * + * * @returns `false` if the OSK is in an invalid state for being presented to the user. */ protected mayShow(): boolean { @@ -829,8 +825,8 @@ namespace com.keyman.osk { } /** - * - * @param hiddenByUser + * + * @param hiddenByUser * @returns `false` if the OSK is in an invalid state for being hidden from the user. */ protected mayHide(hiddenByUser: boolean): boolean { @@ -851,22 +847,56 @@ namespace com.keyman.osk { /** * Applies CSS styling and handling needed to perform a fade animation when * hiding the OSK. - * + * * Note: currently reflects an effectively-dead code path, though this is * likely not intentional. Other parts of the KMW engine seem to call hideNow() * synchronously after each and every part of the engine that calls this function, * cancelling the Promise. - * + * * @returns A Promise denoting either cancellation of the hide (`false`) or * completion of the hide & its animation (`true`) */ + protected useHideAnimation(): Promise { const os = this._Box.style; const _this = this; - const promise = new Promise(function(resolve) { - os.transition='opacity 0.5s linear 0'; - _this._animatedHideResolver = resolve; + return new Promise(function(resolve) { + const cleanup = function() { + // TODO(lowpri): attach event listeners on create and leave them there + _this._Box.removeEventListener('transitionend', cleanup, false); + _this._Box.removeEventListener('webkitTransitionEnd', cleanup, false); + _this._Box.removeEventListener('transitioncancel', cleanup, false); + _this._Box.removeEventListener('webkitTransitionCancel', cleanup, false); + if(_this._animatedHideTimeout != 0) { + window.clearTimeout(_this._animatedHideTimeout); + } + _this._animatedHideTimeout = 0; + + if(_this._Visible && _this.activationConditionsMet) { + // Leave opacity alone and clear transition if another element activated + os.transition=''; + os.opacity='1'; + resolve(false); + return false; + } else { + resolve(true); + return true; + } + }, startup = function() { + _this._Box.removeEventListener('transitionrun', startup, false); + _this._Box.removeEventListener('webkitTransitionRun', startup, false); + _this._Box.addEventListener('transitionend', cleanup, false); + _this._Box.addEventListener('webkitTransitionEnd', cleanup, false); + _this._Box.addEventListener('transitioncancel', cleanup, false); + _this._Box.addEventListener('webkitTransitionCancel', cleanup, false); + }; + + _this._Box.addEventListener('transitionrun', startup, false); + _this._Box.addEventListener('webkitTransitionRun', startup, false); + + os.transition='opacity 0.5s linear 0'; + os.opacity='0'; // Cannot hide the OSK smoothly using a transitioned drop, since for // position:fixed elements transitioning is incompatible with translate3d(), @@ -874,45 +904,8 @@ namespace com.keyman.osk { // Opacity can be transitioned and is probably the simplest alternative. // We must condition on osk._Visible in case focus has since been moved to another // input (in which case osk._Visible will be non-zero) - _this._animatedHideTimeout = window.setTimeout(function(this: AnchoredOSKView) { - if(_this._animatedHideResolver) { - _this._animatedHideResolver(); - } - - _this._animatedHideTimeout = 0; - _this._animatedHideResolver = null; - }.bind(_this), 200); // Wait a bit before starting, to allow for moving to another element - }); - - return promise.then(function() { - // Repro for passing this condition: - // 1. Touch an input element of the page - // 2. Within a second (before the focusing timer expires), touch the base page. - // See domEventHandlers.ts, `focusTimer` / `setFocusTimer`. - // 3. After the timer expires, touch the base page again. - if(_this._Visible && _this.activationConditionsMet) { - // Leave opacity alone and clear transition if another element activated - os.transition=''; - return false; - } else { - // Set opacity to zero, should decrease smoothly. Starts the actual animation. - os.opacity='0'; - - // Listen for the animation's end. - return new Promise(function(resolve) { - _this._animatedHideResolver = resolve; - // Actually hide the OSK at the end of the transition - _this._Box.addEventListener('transitionend', _this._animatedHideResolver, false); - _this._Box.addEventListener('webkitTransitionEnd', _this._animatedHideResolver, false); - }).then(function() { - // Remove the promise's resolver method from future handling. - _this._Box.removeEventListener('transitionend', _this._animatedHideResolver, false); - _this._Box.removeEventListener('webkitTransitionEnd', _this._animatedHideResolver, false); - - // The hide animation is considered complete now. - return true; - }); - } + _this._animatedHideTimeout = window.setTimeout(cleanup, + 200); // Wait a bit before starting, to allow for moving to another element }); } @@ -933,21 +926,14 @@ namespace com.keyman.osk { // Was an animated hide waiting to start? Just cancel it. if(this._animatedHideTimeout) { window.clearTimeout(this._animatedHideTimeout); - this._animatedHideResolver = null; this._animatedHideTimeout = 0; } // Was an animated hide already in progress? If so, just trigger it early. - if(this._animatedHideResolver) { - this._animatedHideResolver(); // also triggers finalizeHide(). - this._animatedHideResolver = null; - } else { - const os = this._Box.style; - os.transition=''; - os.opacity='0'; - - this.finalizeHide(); - } + const os = this._Box.style; + os.transition=''; + os.opacity='0'; + this.finalizeHide(); } ['shutdown']() { @@ -993,10 +979,10 @@ namespace com.keyman.osk { /** * Display build number - * + * * In the future, this should raise an event that the consuming KeymanWeb * engine may listen for & respond to, rather than having it integrated - * as part of the OSK itself. + * as part of the OSK itself. */ showBuild() { let keymanweb = com.keyman.singleton; @@ -1006,11 +992,11 @@ namespace com.keyman.osk { /** * Display list of installed keyboards in pop-up menu - * + * * In the future, this language menu should be defined as a UI module like the standard * desktop UI modules. The globe key should then trigger an event to _request_ that the * consuming engine display the active UI module's menu. - * + * **/ showLanguageMenu() { if(this.hostDevice.touchable) { diff --git a/web/source/osk/uiTouchHandlerBase.ts b/web/source/osk/uiTouchHandlerBase.ts index cd9c5fb861..1602c785c1 100644 --- a/web/source/osk/uiTouchHandlerBase.ts +++ b/web/source/osk/uiTouchHandlerBase.ts @@ -2,8 +2,8 @@ namespace com.keyman.osk { /** * This class was added to facilitate scroll handling for overflow-x elements, though it could * be extended in the future to accept overflow-y if needed. - * - * This is necessary because of the OSK's need to use `.preventDefault()` for stability; that + * + * This is necessary because of the OSK's need to use `.preventDefault()` for stability; that * same method blocks native handling of overflow scrolling for touch browsers. */ class ScrollState { @@ -51,7 +51,6 @@ namespace com.keyman.osk { private scrollTouchState: ScrollState; private pendingTarget: Target; - private popupBaseTarget: Target; constructor(baseElement: HTMLElement, rowClassMatch: string, selectedTargetMatch: string) { this.baseElement = baseElement; @@ -87,7 +86,7 @@ namespace com.keyman.osk { protected abstract hasSubmenu(t: Target): boolean; /** - * Indicates that the user is maintaining a `Touch` on the specified `Target`. + * Indicates that the user is maintaining a `Touch` on the specified `Target`. * Popups and-or longpress menus may be appropriate. * @param t The `Target` being held. */ @@ -151,7 +150,7 @@ namespace com.keyman.osk { if(this.isInvalidTarget(this.findTargetFrom(childNode))) { continue; } - + x1 = childNode.offsetLeft; x2 = x1 + childNode.offsetWidth; @@ -231,8 +230,8 @@ namespace com.keyman.osk { this.touchX = coord.x; this.touchY = coord.y; - // If popup stuff, immediately return. - + // If popup stuff, immediately return. + this.touchCount = coord.activeInputCount; if(!this.currentTarget) { @@ -275,7 +274,7 @@ namespace com.keyman.osk { } // Cancel (but do not execute) pending key if neither a popup key or the base key - if((t == null) || ((t.id.indexOf('popup') < 0) && (t.id != this.popupBaseTarget.id))) { + if(t == null || t.id.indexOf('popup') < 0) { this.highlight(this.pendingTarget,false); this.clearHolds(); this.pendingTarget = null; @@ -351,24 +350,10 @@ namespace com.keyman.osk { // Do not move over keys if device popup visible if(this.hasModalPopup()) { - if(key1 == null) { - if(key0) { - this.highlight(key0,false); - } - this.pendingTarget=null; - } else { - if(key1 == this.popupBaseTarget) { - if(!util.hasClass(key1, this.selectedTargetMatch)) { - this.highlight(key1,true); - } - this.pendingTarget = key1; - } else { - if(key0) { - this.highlight(key0,false); - } - this.pendingTarget = null; - } + if(key0) { + this.highlight(key0,false); } + this.pendingTarget=null; return; } @@ -398,12 +383,12 @@ namespace com.keyman.osk { // Once a subkey array is displayed, do not allow changing the base key. // Keep that array visible and accept no other options until the touch ends. - if(key1 && key1.id.indexOf('popup') < 0 && key1 != this.popupBaseTarget) { // TODO: reliant on 'popup' in .id + if(key1 && key1.id.indexOf('popup') < 0) { // TODO: reliant on 'popup' in .id return; } // Highlight the base key on devices that do not append it to the subkey array. - if(key1 && key1 == this.popupBaseTarget && key1.className.indexOf(this.selectedTargetMatch) < 0) { + if(key1 && key1.className.indexOf(this.selectedTargetMatch) < 0) { this.highlight(key1,true); } // Cancel touch if moved up and off keyboard, unless popup keys visible diff --git a/web/source/osk/visualKeyboard.ts b/web/source/osk/visualKeyboard.ts index 2e25ebba27..85f3abd3a9 100644 --- a/web/source/osk/visualKeyboard.ts +++ b/web/source/osk/visualKeyboard.ts @@ -819,7 +819,7 @@ namespace com.keyman.osk { } if (this.keyPending) { - if (key0 != key1 || key1.className.indexOf('kmw-key-touched') < 0) { + if (key0 != key1 || key1.className.indexOf(OSKKey.HIGHLIGHT_CLASS) < 0) { this.highlightKey(key1, true); } } diff --git a/web/source/resources/osk/kmwosk.css b/web/source/resources/osk/kmwosk.css index d8572f50c6..0c412fe121 100644 --- a/web/source/resources/osk/kmwosk.css +++ b/web/source/resources/osk/kmwosk.css @@ -323,8 +323,58 @@ body div.kmw-key-shift-on span.kmw-key-text {font-family:SpecialOSK !important;f } /* Key preview styles */ + div.ios div.kmw-keytip { - position:absolute; left:0; top:0; width:3em; height:3em; background-color:rgb(0,0,0,0);overflow:visible; + position: absolute; + left: 0; + top: 0; + width: 3em; + height: 3em; + background-color: rgb(0, 0, 0, 0); + overflow: visible; +} + +div.ios div.kmw-keytip-tip { + position: absolute; + top: 0; + width: 100%; + border-radius: 6px 6px 4px 4px; + border: solid 1px #cccccc; + border-bottom: solid 1px #8a8d90; +} + +div.ios div.kmw-keytip-cap { + position: absolute; + border-radius: 0 0 5px 5px; + border-top: solid 1px white; + border-bottom: solid 1px #8a8d90; +} + +span.kmw-keytip-label { + display: block; + transform: translateY(-50%); + top: 50%; + position: relative; +} + +div.ios div.kmw-keytip-cap, +div.ios div.kmw-keytip-tip { + background:white; +} + +div.android div.kmw-keytip { + position:absolute; + left: 0; + top: 0; + width: 3em; + height: 3em; + color: white; + overflow: visible; +} + +div.android div.kmw-keytip-tip { + border-radius: 6px; + background: #999; } /* Dark mode - ensure text is colored appropriately for key tips. */ @@ -333,21 +383,21 @@ div.ios div.kmw-keytip { color:#fff; } + div.ios div.kmw-keytip-cap, + div.ios div.kmw-keytip-tip { + background: #0f1319; + } + + div.ios div.kmw-keytip-cap { + border-top: solid 1px #0f1319; + } + /* Style for callout used on phones */ #kmw-popup-callout { background-color: #0f1319; } } -div.android div.kmw-keytip { - position:absolute; left:0; top:0; width:3em; height:3em; - color:#fff; - background-color: rgb(0,0,0,0); - overflow:visible; -} - -div.android #keytip {background-color:#f00;} - /* Box styles for keyboard-specific OSK (e.g. EuroLatin) and if no keyboard active (desktop only) */ .kmw-osk-static, .kmw-osk-none{text-align:left;font:12px sans-serif;border:solid 1px #ad4a28;color:blue;background-color:white;} .kmw-osk-none{padding:4px 6px 6px} diff --git a/web/source/text/prediction/modelManager.ts b/web/source/text/prediction/modelManager.ts index cce2aeccee..607f291ad6 100644 --- a/web/source/text/prediction/modelManager.ts +++ b/web/source/text/prediction/modelManager.ts @@ -8,10 +8,10 @@ namespace com.keyman.text.prediction { // Allows for easy model lookup by language code; useful when switching keyboards. languageModelMap: {[language:string]: ModelSpec} = {}; - + init() { let keyman = com.keyman.singleton; - + // Registers this module for keyboard (language) and model change events. keyman['addEventListener']('keyboardchange', this.onKeyboardChange.bind(this)); } @@ -46,6 +46,11 @@ namespace com.keyman.text.prediction { let keyman = com.keyman.singleton; let activeLanguage = keyman.keyboardManager.getActiveLanguage(); + if(JSON.stringify(model) == JSON.stringify(this.registeredModels[model.id])) { + // We are already registered, let's not go through and re-register + // because we'll already have the correct model active + return; + } this.registeredModels[model.id] = model; // Register the model for each targeted language code variant. diff --git a/web/testing/commonHeader.js b/web/testing/commonHeader.js index d1f0ed9594..db32e4dea3 100644 --- a/web/testing/commonHeader.js +++ b/web/testing/commonHeader.js @@ -40,6 +40,36 @@ and is called when the page loads. */ + function errToString(err) { + if(Array.isArray(err)) { + let result = ''; + for(let e of err) { + if(e.error instanceof Error) { + result += e.error.message + '\n'; + } else { + result += JSON.stringify(e) + '\n'; + } + } + return result; + } + if(err instanceof Error) { + return err.message; + } + return JSON.stringify(err); + } + + function doAddKeyboards(data) { + return keyman.addKeyboards(data).catch(function(err) { + console.error('keyman.addKeyboards failed with '+errToString(err)+' for '+JSON.stringify(data)); + }); + } + + function doAddKeyboardsForLanguage(data) { + return keyman.addKeyboardsForLanguage(data).catch(function(err) { + console.error('keyman.addKeyboardsForLanguage failed with '+errToString(err)+' for '+JSON.stringify(data)); + }); + } + function loadKeyboards(nestLevel) { var kmw=keyman; @@ -57,22 +87,22 @@ // 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'}, + doAddKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, filename:(prefix + '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. // 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. + doAddKeyboards('french','@he'); + doAddKeyboards({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'); + doAddKeyboardsForLanguage('Dzongkha'); // Add a fully-specified, locally-sourced, keyboard with custom font - kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', + doAddKeyboards({id:'lao_2008_basic',name:'Lao Basic', languages: { id:'lo',name:'Lao',region:'Asia', }, @@ -98,16 +128,16 @@ { case 1: sKbd=document.getElementById('kbd_id1').value; - kmw.addKeyboards(sKbd); + doAddKeyboards(sKbd); break; case 2: sKbd=document.getElementById('kbd_id2').value.toLowerCase(); - kmw.addKeyboards('@'+sKbd); + doAddKeyboards('@'+sKbd); break; case 3: // Add keyboard for comma-separated language name(s) sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd); + doAddKeyboardsForLanguage(sKbd); break; } } diff --git a/web/testing/index.html b/web/testing/index.html index c8821e4ade..745abae626 100644 --- a/web/testing/index.html +++ b/web/testing/index.html @@ -60,6 +60,7 @@

Test SpacebarText APIs (#949)

Test inline OSK (#5665)

Test variable stores and predictive text (#2924)

+

Test scrolling of touch elements (#5313)

Return to main index. diff --git a/web/testing/scrolling/index.html b/web/testing/scrolling/index.html new file mode 100644 index 0000000000..6520a448c5 --- /dev/null +++ b/web/testing/scrolling/index.html @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + KeymanWeb Testing Page - Predictive Text: robust testing + + + + + + + + + + + + + + + + +

KeymanWeb Sample Page - Scrolling Touch Elements

+
+ +
+ +

Type in your language in this text area:

+ + +

Return to testing home page

+
+ + + + + + + diff --git a/windows/src/desktop/help/desktop_images/km_install_anywhere_1.png b/windows/src/desktop/help/desktop_images/km_install_anywhere_1.png new file mode 100644 index 0000000000..dc3b4ad3ff Binary files /dev/null and b/windows/src/desktop/help/desktop_images/km_install_anywhere_1.png differ diff --git a/windows/src/desktop/help/desktop_images/km_install_anywhere_2.png b/windows/src/desktop/help/desktop_images/km_install_anywhere_2.png new file mode 100644 index 0000000000..2cd4f49871 Binary files /dev/null and b/windows/src/desktop/help/desktop_images/km_install_anywhere_2.png differ diff --git a/windows/src/desktop/help/desktop_images/km_install_anywhere_3.png b/windows/src/desktop/help/desktop_images/km_install_anywhere_3.png new file mode 100644 index 0000000000..e668180823 Binary files /dev/null and b/windows/src/desktop/help/desktop_images/km_install_anywhere_3.png differ diff --git a/windows/src/desktop/help/desktop_images/km_non_app_store_1.png b/windows/src/desktop/help/desktop_images/km_non_app_store_1.png new file mode 100644 index 0000000000..d3772950a7 Binary files /dev/null and b/windows/src/desktop/help/desktop_images/km_non_app_store_1.png differ diff --git a/windows/src/desktop/help/desktop_images/km_non_app_store_2.png b/windows/src/desktop/help/desktop_images/km_non_app_store_2.png new file mode 100644 index 0000000000..219b5a413b Binary files /dev/null and b/windows/src/desktop/help/desktop_images/km_non_app_store_2.png differ diff --git a/windows/src/desktop/help/start/download-and-install-keyman.md b/windows/src/desktop/help/start/download-and-install-keyman.md index 762c2061e0..27902001c7 100644 --- a/windows/src/desktop/help/start/download-and-install-keyman.md +++ b/windows/src/desktop/help/start/download-and-install-keyman.md @@ -17,16 +17,19 @@ After you download Keyman, to install the program: 2. Double-click the file to begin installation. -3. You will be asked to allow the installer to access your system. +3. You may be warned to use a "Microsoft-verfied" App from the Microsoft Store. This is because Keyman is not available in the Microsoft Store. If "**Install Anyway**" is an option, select that. Otherwise follow the instructions found here [How To - Allow Windows 10 to Install Apps From Anywhere](../troubleshooting/install-app-from-anywhere) +![](../desktop_images/km_non_app_store_2.png) + +4. You will be asked to allow the installer to access your system. Click Yes to continue. ![](../desktop_images/setup-click1.png) -4. The installer may need to download additional components from keyman.com +5. The installer may need to download additional components from keyman.com in order to complete the installation. If prompted by your firewall software, ensure you Allow the installer to access the web. -5. Click Install Keyman to finish installing. Keyman may download additional +6. Click Install Keyman to finish installing. Keyman may download additional files during the installation process. ![](../desktop_images/setup-click2.png) @@ -37,7 +40,7 @@ After you download Keyman, to install the program: **Note:** You can select advanced installation options from the Install Options link on the bottom left. -6. At the end of the installation, click Start Keyman to begin using +7. At the end of the installation, click Start Keyman to begin using Keyman or click Configuration to change Keyman settings first. ![](../desktop_images/setup-click3.png) diff --git a/windows/src/desktop/help/troubleshooting/index.md b/windows/src/desktop/help/troubleshooting/index.md index 29c9fc19ca..03e9745fec 100644 --- a/windows/src/desktop/help/troubleshooting/index.md +++ b/windows/src/desktop/help/troubleshooting/index.md @@ -11,3 +11,5 @@ title: Troubleshooting * [How To - Use the Keyman Setup Bootstrapper](bootstrapper) * [How To - Fix A Problem with an Active Keyman Keyboard Not Typing](hidden) + +* [How To - Allow Windows 10 to Install Apps From Anywhere](install-app-from-anywhere) diff --git a/windows/src/desktop/help/troubleshooting/install-app-from-anywhere.md b/windows/src/desktop/help/troubleshooting/install-app-from-anywhere.md new file mode 100644 index 0000000000..160d7cf023 --- /dev/null +++ b/windows/src/desktop/help/troubleshooting/install-app-from-anywhere.md @@ -0,0 +1,26 @@ +--- +title: How To - Allow Windows 10 to Install Apps From Anywhere +--- + +## Allow Windows 10 to Install Apps From Anywhere + +1. You may be warned to use a "Microsoft-verfied" App from the Microsoft Store. This is because Keyman is not available in the Microsoft Store. +If "**Change my app recommendation settings**" is shown, select that and go to step 4. +![](../desktop_images/km_non_app_store_1.png) + + +2. If that is not available Open the Windows 10 start menu and select **Settings** +![](../desktop_images/km_install_anywhere_1.png) + +3. Click on the **Apps** in the **Windows Settings** menu +![](../desktop_images/km_install_anywhere_2.png) + +4. In the **Apps & Features** Section go to **choose where to get apps**, click on the dropdown menu below and select "Anywhere, but warn me before installing an app that's not from the Microsoft Store" or one of the other options that allows you to install from *Anywhere*. +![](../desktop_images/km_install_anywhere_3.png) + +5. Now continue installing by double-clicking on the Keyman file identified in Step 1 of [How To - Download and Install a Keyman](../start/download-and-install-keyman). + +## Related Topics + +- [How To - Download and Install a Keyman](../start/download-and-install-keyman) +- [Software Task - Uninstall Keyman](../basic/uninstall) diff --git a/windows/src/desktop/kmshell/locale/bwr-NT/strings.xml b/windows/src/desktop/kmshell/locale/bwr-NT/strings.xml new file mode 100644 index 0000000000..21a8701681 --- /dev/null +++ b/windows/src/desktop/kmshell/locale/bwr-NT/strings.xml @@ -0,0 +1,912 @@ + + + + + + + Keyman + + + + Segoe UI + + + + 9 + + + + &Yes + + + + &No + + + + OK + + + + Awo + + + + Harta + + + + OK + + + + Awo + + + + Ntsari fwato ngǝni kamnyar car keyboard laga + + + + Keyman akwa kǝthlǝr har ba kulini. Ntsari fwato ngǝni kamnyar kǝthlǝr ka keyboard anr hulfur mnya nga kǝlara saka + + + + Keyman ku kurta ka baɗitǝ kǝthlǝr. Ntsari fwato na ar Keyman ni akwa vǝi tǝ labar ana wutǝdzi kamnyar kǝthlǝr ka keyboard ar hulfur mnya nga + + + + Keyman Configuration + + + + Dlara + + + + Layoutyeri na ar Keyboard + + + + K + + + + Suayeri ar cara + + + + O + + + + Hotkiyeri + + + + H + + + + Support + + + + S + + + + Vi kǝɓa na ar pazhikur ar zaman + + + + T + + + + Thlǝma fayil: + + + + Jili Package ni: + + + + Jili Keyboard ni: + + + + Mdǝna baɗitǝ namari: + + + + Websaitu: + + + + Package: + + + + Fontyeri: + + + + Layoutyeri na ar Keyboard ni: + + + + Keyboard Layout: + + + + Keyboard Language: + + + + Encodings: + + + + Jili Layout ni: + + + + Fixed (Positional) + + + + Mdǝ namtǝ ni katsa kǝɓa ka Windows layout (Mnemonic) + + + + Hulfur mnyayeri: + + + + + + + + Jaktǝ hulfur mnya + + + + On Screen Keyboard: + + + + Pali + + + + Mdǝku hara installari + + + + Mda aɗi hara install ni wa + + + + Ɗǝmhǝ rubutuyeri: + + + + Mdǝku hara installari + + + + Mda aɗi hara install ni wa + + + + Psymnya: + + + + Kopirait: + + + + Ana kǝtǝ su natǝ mdǝ pali kasha-kasha + + + + Install keyboard... + + + + Dawunlod keyboard... + + + + Suayeri natǝ mda ata cara ata keyboard + + + + Ga aɗika keyboard laga natǝ gǝ hara insatallari wa. Ntsari Dawunlod Keyboard kamnyar kara hara install keyboard layout ni akwa websait na ar Keyman ni. + + + + + Ga ata tsoktǝ hara only uninstall ar keyboard ni amma \'%1$s\' sai ma ga an Administrator ni + + + + Tǝr keyboard + + + + Kǝtǝ fwator kod ngǝni kamnyar hara lodi ar keyboard ata na\'ura laga wala + + + + tǝra akwa onlayin + + + + + + + + Shanga + + + + Baɗita + + + + On Screen Keyboard + + + + Lǝkǝma + + + + Hotkeyeri na ar keyboard ana pali kǝthlǝr ar keyboard + + + + Pali AltGr ka Ctrl+Alt + + + + Kǝthlǝr ka deadkeyeri na ar hardware apa plai keyeri + + + + Ncangtǝ sauri atar psǝmnyayeri + + + + Ana mwantǝ su na aɗi kalkal wa tǝkǝ ma su ni hara ala keyman.com + + + + Ngkǝrɓǝla labar na ar hangkur kǝthlǝr tǝ ga ana hara kula ngkǝr thlǝma ga aka keyman.com + + + + Baɗita sakatǝ Windows baɗita + + + + Ncangtǝ fwato laga ata screen ni + + + + Cangtǝ lale ata screen ni + + + + Pakǝ ka kǝrni akwa keyman.com kǝlara sati kamnyar wutǝ labar na bǝlin + + + + Ngkǝrha kamnyar sǝndǝr applicationyeri aɗi kǝɓa wa sakatǝ Keyman baɗita + + + + Psi Shift/Ctrl/Alt ata On Screen Keyboard saka tǝgǝ kurtǝ ka ntsari key laga + + + + Kǝlara saka ncangtǝ On Screen Keyboard Window saka tǝ mdǝ car keyboard ar Keyman + + + + Pali On Screen Keyboard/Help na kalkal ka kǝrni sakatǝ mdǝ car keyboard laga + + + + Pali suna ncang Unicode supplementary character (ata bara ka mdǝ jaktǝ kǝlani) + + + + Kǝla hulfur mnya na ar \'Unknown\' + + + + Debugging + + + + Kǝla hulfur mnya ar Windows sakatǝ mdǝ car keyboard ar Keyman + + + + Car keyboard layout aka shanga applicationyeri + + + + Kǝlara saka kara jakǝngkǝr dunar mdǝ na ana kǝthlǝr ka su ni saka tǝ Windows Vista akwa hara kǝthlǝr + + + + Base Keyboard... + + + + + + + + + + + + + (aɗiwa akwa) + + + + Hartǝ Keyman + + + + Kǝla menu na ar Keyboard + + + + Ncantǝ ghǝli ar on screen keyboard + + + + Kǝla Configuration + + + + Ncantǝ ghǝli na ar Font Helper + + + + Ncangtǝ ghǝli na ar Character Mapu + + + + Kǝla Text Editor + + + + Kǝla Language Switcher + + + + Hotkeyeri na ar Shanga da + + + + Keyboard Layouts + + + + Ma gǝka wujir laga na tsukwa kǝthlǝr ka Keyman, tǝng kara yuwa akwa vi ɓukci na ar Keyman Community Forum. + + + + Kǝla Keyman Community Forum + + + + SIL International an namta + + + + Copyright © SIL International. All Rights Reserved. + + + + Jili + + + + Linyeri naka ampani + + + + Dlara na ar Online + + + + Pakǝ su na bǝlin + + + + Proxy Settings... + + + + Keyman System Settings... + + + + Diagnostics + + + + Kara dawunlod keyboard akwa keyman.com + + + + Ga aɗe hara instal wa, hara dawunlodi daci + + + + + Aɗi wuta ka hara dawunlodi ar keyboard wa, dametǝdzi %2$d: %1$s + + + + < Mǝngǝl + + + + Hara instal ar keyboard/package + + + + Suayeri atakǝr + + + + Karatǝra + + + + Hara instal + + + + Proxy Server Configuration + + + + Sava: + + + + Port: + + + + Username: + + + + Password: + + + + Set base keyboard + + + + Car base Latin script ni +Keyboard natǝ ga ana hara kǝthlǝr kari akwa Windows. Keyboardyeri ar Keyman ata sǝnda ka kǝrni layout natǝ gǝ hira. + + + + Pali hotkey + + + + + Car hotkey na ar standard wala char ʻCustomʻ ka kǝdzi Ctrl,Shift ka/wala Altka taip ar hotkey natǝ gǝ hira na ar hulfur mnya %1$s: + + + + Carɓǝla hotkey na kalkal wala kara car ʻCustomʻ ambǝla kara kǝdzi Ctrl,Shift ka/wala Alt ka kara taip ar hotkey natǝ gǝ hira: + + + + + Hotkey %1$s ni ata ngkatǝ kethlǝr ka keyboard na aɗi natǝ mda ana hara kǝrthlǝr kari. Kara tamata gǝ kǝthlǝr ka Ctrl wala Alt. Ga ata bara kara pali ngǝni kulini ya\? + + + + + Hotkey %1$s ni ata ngkatǝ hotkey natǝ mdǝ cara na ar keyboard %2$s. Ma ǝ lǝkǝma, hotkey na ar keyboard %2$s ata cijimtadzi. Lǝkǝma\? + + + + + Hotkey %1$s ni ata ngkatǝ hotkey na damwa. Magǝ lǝkǝma, hotkey na palani ata cijimtadzi. Lǝkǝma\? + + + + Kariyeri na ar Keyman na blǝlin nari Aɗi + + + + Kariyeri na ar Keyman aɗi kulini + + + + Usa kara carɓǝla suayeri natǝ ga ata jaktǝ bǝlinkur ɗa natǝ ga ata bara hara instal ɗa: + + + + Ga ata tsoktǝ hara dawunlodi ar jili na bǝlin ni akwa: + + + + Hara instal kulini + + + + Awo + + + + Jili na waci + + + + Kari natǝ mdǝ jaktǝ bǝlinkur ni + + + + Olkur + + + + + Keyman %1$s + + + + + Keyboard %1$s %2$s + + + + Aɗi wuta ka kǝɓa ka keyma.com wa - usa kara tamata gǝka Intanet natǝ kǝ paktǝdzi ɓɗakǝlaka ambǝla gǝ jaktǝ ngkǝrta ka damwa. + + + + + Aɗi wuta ka hara gari ka keyman.com wa - usa kara tamata gǝka Intanet natǝ kǝ paktǝdzi ɓɗakǝlaka ambǝla gǝ jaktǝ ngkǝrta ka damwa. Dametǝdzi natǝ kǝsi an: %1$s + + + + Suayeri natǝ mda ata jaktǝ bǝlinkur ɗa na ar Keyman aɗi + + + + Ntsari fwato ngǝni kamnyar kara dawunlodi ka hara instal na ar suayeri na bǝlin ni + + + + Wulha ka &hara instal ar suayeri na bǝlin na ar Keyman + + + + E&xit wulɓǝla bǝlinkur su atar onlayin + + + + Keyman + + + + Kǝlar Keyman + + + + Lǝɓǝla + + + + Configuration + + + + Ncangtǝ skirin ngǝni sakatǝ kǝ kǝlar dzi + + + + Ncangta akwa + + + + Kara bara bwaduka ncangtǝ hulfur mnya atar onlayin... + + + + Dlara kara shinauɓǝla lakutǝ mda ata wuta ka hara kǝthlǝr kari bu... + + + + Bura-Pabir + + + + Bura-Pabir + + + + en + + + + en + + + + Keyman + + + + Jaktǝ hara seti ar Hint + + + + Shanga psǝmnyayeri na ar sauri mdǝku kurtǝ ka jaktǝ hara settin ar ni antǝ ata jaktǝ ncangtǝdzi. + + + + + Lǝɓǝla akwa Keyman\? + + + + + Gǝ tamata abbǝr ga ata lǝɓǝla akwa Keyman\? Keyboardyeri ar Keyman arna tsu mda ata vu ni akwa hulfur mnyayeri na ar Windows, amma aɗeta hara kǝthlǝr wa sai magu kǝlarKeyman. + + + + + Mdǝku hartǝ on screen keyboard + + + + + Gǝ hartǝ on screen keyboard ni. Harba kulini Keyman akwa hara kǝthlǝr. Ga ata tsoktǝ kǝlar on screen keyboard ni kǝlara sala atar ntsari fwato na ar Keyman antǝ kara car \"on screen keyboard\" + + + + Aɗu ncang sauri ngǝni kǝra wa + + + + Dlara ar Keyman + + + + Dlara ar suayeri na akwa + + + + Kǝlar dlara \" + + + + \" keyboard + + + + Wulhǝ on screen keyboard + + + + Wul Dlara na ar Rubutuyeri + + + + Wul Mapu ar character + + + + Kǝlar configuration ar Keyman + + + + Kǝlar Dlara + + + + Hartǝ on screen keyboard + + + + Kǝlar Keyman &Harta + + + + On Screen &Keyboard + + + + &Dlara na ar Rubutu + + + + Character &Mapu + + + + &Text Editor... + + + + &Configuration... + + + + &Dlara... + + + + E&xit + + + + Katsa baɗitǝ kǝmtadzi ma mda aɗekwa kǝthlǝr kari wa + + + + Ncangtǝ Toolbar + + + + Ɗuwari apa Web Page... + + + + Print... + + + + Keyman + + + + + Jili: %1$s + + + + &Print... + + + + + Mdǝku kurtǝ ka hara instal ar package naka thlǝmari %1$s ni. Ga ata bara kara kǝlɓǝlani ni tǝng ga ata hara instal na bǝlin na ni ya\? + + + + + Mdǝ ku kurtǝ ka hara instal ar keyboard naka thlǝmari %1$s ni. Magǝ lǝkǝma, ata kǝlɓǝla ni kwapu na bǝlin ni wutǝ ka mdǝ hara instal ni. Lǝkǝma\? + + + + + Keyboard \'%1$s\' akwa kuta package \'%2$s\'. Doli sai magǝ kǝlɓǝla shanga package ni. Lǝkǝma\? + + + + + Aɗi wuta ka hara instal ar hulfur mnya na ar keyboard wa; Windows ka tǝng hulfur mnyayeri da 4 natǝ mda atsoktǝ hara alada custom \'transient\', antǝ nughum gǝ kurtǝ ka mzhitǝ hangkur ngǝni. + + + + + Package wala keyboard \'%1$s\' aɗika dlara laga na ar baɗita wa. + + + + Operating system ngǝni aɗena kǝɓzdzi wa. + + + + KeymanDesktop.chm + + + + Mda aɗi tsoktǝ wutǝ fayil ar dlara wa. + + + + Codepage + + + + Unicode + + + + Akwa hara dawunlodi ar fayil + + + + + Gǝ tamata abbǝr ga ata bara kara kǝlɓula on screen keyboard ngǝni natǝ mdǝ hara instal aka %1$s\ ya? + + + + + Gǝ tamata abbǝr ga ata bara kara kǝlɓǝla keyboard ngǝni ya%1$s\? + + + + + Gǝ tamata abbǝr ga ata bara kara kǝlɓǝla package ngǝni ya %1$s\?\n\n%2$s + + + + + Mda ata kǝlɓǝla jili rubutuyeri ngǝni tsuwa: %1$s. + + + + Mapu na ar character ni tsaka database na ar characteryeri natǝ ata bara ka mdǝ hara build ni kwapu ka mdǝ wutǝ hara kǝthlǝr katsa. Hara build ni kulini ya\? + + + + + Mda aɗa wuta ka mdǝ cijimta character na ar Unicode na ar database kamnyar hara rebuild ni wa. Suayeri naka dametǝdzi ni da an: %1$s + + + + + Mda aɗa wuta ka mdǝ hǝnɓǝla character na ar Unicode database wa antǝ mdǝ ngkatǝni hara kǝthlǝr. Suayeri naka dametǝdzi ni da an: %1$s + + + + + Character na Unicode database aɗi wutǝ ka hara lodi kalkal wa (%1$s). Hara rebuild ni kulini ya\? + + + + + Keyman ku ngǝlari ta kǝlardzi. Usa kara wulhǝ setiyeri ar nga na ar sǝkuriti kamnyar kara tamata progǝram na ar Keyman.exe ku wutǝ kǝlardzi kwapu lǝkǝma. Dametǝdzi natǝ ku kalaktǝdzi an:\n\n\"%1$s\"\n\nGa ata bara kara jaktǝ ngkǝrtǝ kǝlar Keyman ka damwa kulini ya\? + + + + Mda ata ɗuwa labar na ar debug na ar Keyman akwa log fauil natǝ mda ana kǝgani %LOCALAPPDATA%\Keyman\Diag\system#.etl (natǝ # lamba laga). Kara lǝɓǝla akwa Keyman kwapu gǝ ngkǝrtǝ cijimta fayil ngǝni.\n\nNDƏKƏR THLƏM: Fayil ngǝni ata mbiku ka nzi ola akwa kuta saka rakka. Nam debugging ka tsa haraata tsoktǝ vu ka system ni aɗi kǝthlǝr ka huva wa antǝ ka mdǝ hara ngǝni tǝng sai ka dlara na ar mallǝm na sǝndǝ suni ɓɗaku.\n\nNƏKƏR THLƏM ATAR SU NA ASHIR: Kara sǝnda abbǝr wa fayil na ar debug ata sǝnda ka kǝtǝ shanga suayeri natǝgǝ ntsari saka tǝ ga akwa hara taipin. Kara kǝlar dubu tǝng sai saka tǝ ga akwa hara debugging wala saka tǝ ga akwa namɓǝla suayeri. + + + + Usa kara sǝkta sakatǝ akwa pakɓǝla jili rubutuyeri na kǝɓa kari keyboard %1$s + + + + Keyboard %1$s aɗi keyboard ar Unicode wa. Jili rubutuyeri aɗa tsoktǝ wutǝdzi wa ka kǝrni. Usa wulhǝ kakaɗuyeri na ar keyboard na ar labar ar jili rubutuyeri. + + + + Aɗiwa keyboard layoutyeri natǝ mdǝ hara instal ɗa wala lodi ɗa kulini wa. + + + + Usa kara carɓǝla keyboard na ar Keyman kamnyar wutǝ jili rubutuyeri na kǝɓa kari. + + + + Vi ar Hara Rubutuyeri - Keyman + diff --git a/windows/src/desktop/kmshell/locale/ff-NG/strings.xml b/windows/src/desktop/kmshell/locale/ff-NG/strings.xml new file mode 100644 index 0000000000..657a82bb58 --- /dev/null +++ b/windows/src/desktop/kmshell/locale/ff-NG/strings.xml @@ -0,0 +1,912 @@ + + + + + + + Keyman + + + + Sego UI + + + + 9 + + + + &Eh + + + + &A`a + + + + TOH + + + + Wurtu + + + + Maɓɓu + + + + TOH + + + + Wurtu + + + + A` ɓidda dow alaama ɗo ngam a` suɓa kibod + + + + Keyman ɗo waɗa. A` ɓidda dow alaama ɗo ngam a` naftira bee ɗemngal ma kala wakkati fuu + + + + Keyman ɗo waɗa. A` ɓidda alaama Keyman man nder andinoore na`ura ngam a` naftira bee kibod ɗemngal ma + + + + Nastugo nder Keyman + + + + Mballa + + + + No kibod woni + + + + K + + + + Cuɓol + + + + O + + + + Tappirɗe mawɗe + + + + H + + + + Mballa + + + + S + + + + A` yaha warugo + + + + T + + + + Inde fayel: + + + + Nonde pakej: + + + + Nonde kibod: + + + + Mo waɗi ɗum: + + + + Lenguru: + + + + Pakej: + + + + Mawnugo bindi: + + + + No kibod woni: + + + + Yanayu kibod: + + + + Ɗemngal kibod: + + + + Enkodi: + + + + Yanayu nonde: + + + + Ɗo haaɗi (Dow no woni) + + + + Waɗi no dow windo man woni (Mnemonic) + + + + Ɗemle: + + + + X + + + + Waatu ɗemngal + + + + Kibod dow yeeso na`ura: + + + + Kostom + + + + Wa`i + + + + Wa`ay + + + + Famminorɗe: + + + + Wa`i + + + + Wa`ay + + + + Lellal: + + + + Hakke marol: + + + + Waylol man ɗo wayla to waɗaama + + + + Wa`unu kibod + + + + Jippunu kibod... + + + + Laabi kibod + + + + A` walaa kibod ko a` wa`inta. A` ɓidda tappirɗe Jippungo Kibod ngam a` wa`inna kibod man diga lenguru Keyman. + + + + + A` itta kibod man on \'%1$s\' to a` laati nder marɓe hakke marol + + + + A` senda kibod + + + + Waɗu sikanin lamba ɗo ngam a` wa`ina kibod ɗo dow na`ura feere ko + + + + A` senda haa dow online + + + + + + + + Ko woni nder fuu + + + + Kunnu + + + + Kibod dow yeeso na`ura + + + + Ko woni yeeso + + + + Tappirɗe kibod mawɗi meema ngam wadda kibod + + + + A` hawta AltGr bee Ctrl+Alt + + + + Joggu tappirɗe mayɗe dow tappirɗe pilen + + + + Holla lellal ko lelldi + + + + Wulla masala haa keyman.com + + + + Senda lincitol naftirol nder sirru bee keyman.com + + + + Fuɗɗa to na`ura man kunni + + + + Holla yanayu yeeso na`ura + + + + Holla yeeso jaɓɓol + + + + Yaha ha keyman.com kala ɓaawo asaweere fuu ngam kesitiggo + + + + Foondu manhajaji ko mari miijo to Keyman fuɗɗi + + + + Yoofu Shift/Ctrl/Alt dow yeeso kibod yeeso na`ura to a` ɓiɗɗi tappirɗe + + + + Koonday holla kibod man dow yeeso na`ura to kibod keyman suptaama + + + + Yaha haa dow kibod yeeso na`ura/mballa to kibod suptaama + + + + Kunnu alaama Unikod holla (ɗo mari haaje lora kunna) + + + + Kunnu dow ʻƊemngalʻ ngam andaaka + + + + Debugin + + + + A` kunna ɗemngal komputa to kibod keyman man suptaama + + + + A` supta mawnugo kibod dow manhajaji fuu + + + + Koonday teddina daama naftirol to ɗo naftira bee man dow Windo Vista + + + + Kibod nder komputa... + + + + + + + + + + + + + (walaa ko gootel) + + + + A` nyifa Keyman + + + + Maɓɓutu Menu kibod + + + + A` holla ɓindirgo kibod dow yeeso na`ura + + + + Maɓɓutu Konfigureshon + + + + A` holla bindrgol Mballa mawnol + + + + Hollu alluure haatumeere alkule + + + + Maɓɓutu Bindol bindi + + + + Maɓɓutu Suɓol Ɗemngal + + + + Ko wontiri fuu nder Hotkiji + + + + No kibod woni + + + + To a` ɗo mari masala feere dow naftirol Keyman, a` ƴama ƴamɗe haa babal moptirde keyman. + + + + Maɓɓutu babal maptirde Keyman + + + + SIL Intanashonal on ngaɗi ɗum + + + + Hakke marol © SIL Intanashonal. Mari hakke man fuu. + + + + Nonde + + + + Lengiji marɗum bote + + + + Mballa dow online + + + + Ƴeewto kesɗitine + + + + Setin proksi... + + + + Setin na`ura Keyman... + + + + Lincitol + + + + Jippina kibod diga keyman.com + + + + Taa wa`inna, jippina tan + + + + + Sali jippingo kibod, masala %2$d: %1$s + + + + Ɓaawo < + + + + Wa`unu Kibod/Pakej + + + + Ko woni nder gootel gootel + + + + Jannga mi + + + + Wa`unu + + + + Proksi sava konfigureshon + + + + Sava: + + + + Tufnde: + + + + Inde naftirol: + + + + Paswot: + + + + Waɗa setin kuungel kibod + + + + Suɓu alkule tappirɗe Latin +Kibod ko a` naftirta dow na`ura. Kibod Keyman koosan bee hoore mum yanayu mawnugo man. + + + + Waylu hotki + + + + + A` supta hotki no jooɗata ko bo a` suɓa ʻKostomʻ nden a` jogga Ctrl,Shift bee nanta/ko Alt nden a` winda hotki ko a` yiɗi dow ɗemngal %1$s: + + + + A` supta hotki no jooɗata ko bo a` suɓa ʻKostomʻ nden a` jogga Ctrl,Shift bee nanta/ko Alt nden a` winda hotki ko a` yiɗi: + + + + + Hotki man narrata bee kibod ko woowi naftirgo. A` naftira Ctrl ko Alt. A` ɗo yiɗi waylugo ɗo jotta\? + + + + + Hotki man %1$s man narray bee hotki kibod ko supti %2$s. To a` jokki, hotki jee kibod man %2$s ittan. Jokka\? + + + + + Hotki man %1$s narray bee hotki feere. To a` jokki, hotki feere man itta. Jokka\? + + + + Kesɗitin Keyman ɗo feesi + + + + Kestiɗin Keyman ɗo feesi + + + + Usseni suɓu kesɗitin a` yiɗi wa`ungo: + + + + A` jippinay nonde kesum man haa: + + + + Wa`unu jotta + + + + Wurtu + + + + Nonde hiɗinga + + + + Terɗe Kesɗitinaaɗe + + + + Mawnugo + + + + + Keyman %1$s + + + + + Kibod %1$s %2$s + + + + Sali yottugo keyman.com - usseni a` tabbitina dow a` ɗo mari intanet ko ɗo huuwa nden a` foondita. + + + + + Sali yottugo keyman.com - usseni a` tabbitina dow a` ɗo mari intanet ko ɗo huuwa nden a` foondita. masala ko keɓi man kanjum woni no: %1$s + + + + Kesɗitin keyman ɗo feesi + + + + A` ɓiɗɗa alaama ɗo ngam a` jippina nden a` wa`ina kesɗitin + + + + A` laara nden a` &wa`ina kesɗitin Keyman + + + + W&urtu tefol kesɗitin dow online + + + + Keyman + + + + Ummin Keyman + + + + Wurta + + + + Konfigureshon + + + + A` holla yeeso na`ura ɗo dow ummingo + + + + Hollu nder + + + + Tefu ɗemngal hollol feere haa online... + + + + Mballu fassuru no naftirte... + + + + Fulfulde Nigeria + + + + Fulfulde Nigeria + + + + en + + + + en + + + + Keyman + + + + Dabare firlitol + + + + Lelle dabareji man fuu firlitaama nden ɗum hollan fahin. + + + + + Wurtu Keyman\? + + + + + A` tabbitini a` wurtan Keyman\? Kibod keyman maɗa heɗitan ɗum nder ɗemle komputa, amma bo ɗum huuwataako sayto a` ummini Keyman. + + + + + Kibod yeeso na`ura maɓɓaama + + + + + A` maɓɓi kibod dow yeeso na`ura. Keyman man ɗo huuwa. A` maɓɓitan kibod yeeso na`ura man kala wakkati fuu hedi ŋo`ugo alaama Keyman nden bo supta \"Kibod yeeso na`ura\" + + + + Taa hollu dabare ɗo fahin + + + + Mballa Keyman + + + + Ko woni nder mballa man + + + + Mballa dow \" + + + + \" kibod + + + + Laaru Kibod yeeso na`ura + + + + A` laara Mballa Mawnugo + + + + A` laara Yanayu map + + + + Maɓɓutu konfigureshon Keyman + + + + Maɓɓutu Mballa + + + + Maɓɓu Kibod yeeso na`ura + + + + Ŋo`u Keyman &Nyifu + + + + Kibod yeeso &na`ura + + + + &Mballa Mawnugo + + + + Yanayu &Map + + + + & Wo`innol Binndi... + + + + &Konfigureshon... + + + + &Mballa... + + + + W&urol + + + + Ɗo majja to walaa huuwa + + + + Hollu Bindirgol + + + + Resu ɗum darnde lenguru... + + + + Wurtin... + + + + Keyman + + + + + Nonde %1$s + + + + &Wurtin... + + + + + Pakej bee inde %1$s man arti wa`inni. A` yiɗi a` itta ɗum nden a` wa`inna kesum man\? + + + + + Kibod bee inde man \'%1$s\' arti wa`inni. To a` jokki, ɗum ittan bako a` wa`inna kesum. A` jokkan\? + + + + + Kibod \'%1$s\' man ɗo nder pakej \'%2$s\'. Doole sayto a` itti pakej man fuu. A` jokkan\? + + + + + Sali wa`inngo ɗemngal kibod; Komputa man ɗo mari haaɗande dow ɗemle 4, a` yotti ragare maaje. + + + + + Pakej ko kibod man \'%1$s\' wonday bee andinoore mballa. + + + + Ko huudata man jaɓaaka. + + + + KeymanDesktop.chm + + + + Fayel mballa man tawtataake. + + + + Codepage + + + + Unicode + + + + Jpuunu fayel + + + + + A` tabbitini a` yiɗi ittugo kibod yeeso na`ura ko wa`inni dow %1$s\? + + + + + A` tabbiti a` yiɗi ittugo kibod %1$s\? + + + + + A` tabbiti a` yiɗi ittugo pakej %1$s\?\n\n%2$s + + + + + Mawnugo ɗo ma ittan: %1$s. + + + + Map yanayu ɗo ɗo mari yanayuji ko mari haaje nyiɓugo bako naftira ɗum. A` nyiɓay ɗun jotta\? + + + + + Yanayu databes unikod man ittataako ngam nyiɓa. bayanu masala ko keɓi man kinji on: %1$s + + + + + Yanayu databes unikod man waɗataake nden bo ɗum wonnaama. Bayanu masala ko keɓi man kanjum on: %1$s + + + + + Yanayu databes unikod man wurtay no haandi (%1$s). Nyiɓitay ɗum jotta\? + + + + + Keyman sali ummugo. Usseni a` larta setin sekuriti ma ngam tabbita dow keyman.exe man accaama umma bako fuɗɗita. masala ko lorti man kanjum woni::\n\n\"%1$s\"\n\na` ɗo yiɗi foondugo nden a` fuɗɗita Keyman man fahin jotta\? + + + + Bayanu debug jee Keyman sigan haa log fayel feere ko ɗo ƴowna %LOCALAPPDATA% \Keyman\Diag\system#.etl (haa # ɗum lamba on). A` wurta keyman bako a` habda woggugo fayel.\n\n\WARNING: Fayel ɗo mawnay bee jawɗum. Accugo debugin saway na`ura man nden ɗum waɗay on tan to mballa andaaɓe sawri.\n\nPRIVACY WARNING: Usseni a` anda dow logfayel debugin ɗo koosa kala kistrokji ko ɓiɗɗi fuu. A` maɓɓitay log debugin man on tan wakkati debugin man ko bo wakkati lincitol. + + + + Usseni a` reena nde ɗo tefa yanayu kebod ko nandi %1$s + + + + Kibod %1$s man naa ɗum Unikod kibod. Yanayu mawnugo man keptataako. usseni a` laara ɗereeji kuuɗi jee kibod man ngam bayanu yanayu mawnugo. + + + + Walaa yanayu kibod ko wa`ini ko nasti. + + + + Usseni a` suɓa kibod Keyman ngam keɓa mawnugo bindi no haandi. + + + + Taytorɗe Bindi - Keyman + diff --git a/windows/src/desktop/kmshell/locale/ha-HG/strings.xml b/windows/src/desktop/kmshell/locale/ha-HG/strings.xml new file mode 100644 index 0000000000..b4110b0e8f --- /dev/null +++ b/windows/src/desktop/kmshell/locale/ha-HG/strings.xml @@ -0,0 +1,911 @@ + + + + + + + Keyman + + + + Segoe UI + + + + 9 + + + + &Eh + + + + &A\'a + + + + Ok + + + + Sokewa + + + + Rufewa + + + + Ok + + + + Sokewa + + + + Danna wannan alama domin zaɓar madanni + + + + Har yanzu keyman yana aiki. Danna wannan alamar domin amfani da madannin yarenka a kowane lokaci + + + + Keyman na kan aiki. Danna alamar Keyman a ɓangaren wajen sanarwa don amfani da keyboard a yarenka + + + + Haɗa Keyman + + + + Taimako + + + + Shirin Keyboard + + + + K + + + + Zaɓuka + + + + 0 + + + + Hotkeys + + + + H + + + + Taimako + + + + S + + + + Cigaba da tuntuɓa + + + + T + + + + Sunan ma\'ajiya: + + + + Nau\'ín ƙunshi: + + + + Nau\'ín madanni: + + + + Mawallafi: + + + + Shafin yanar gizo: + + + + Ƙunshi: + + + + Haruffa: + + + + Shirin Madanni: + + + + Shirin Keyboard: + + + + Yaren Madanni: + + + + Mayar da wani abu zuwa wani: + + + + Irin Tsarin: + + + + Fixed (Positional) + + + + Abinda aka tsara saboda Windows (Mnemonic) + + + + Yaruka: + + + + + + + + Ƙara yare + + + + Madannin kan screen: + + + + Na musamman + + + + An shigar + + + + Ba a shigar ba + + + + Adana bayanai: + + + + An shigar + + + + Ba a shigar ba + + + + Saƙo: + + + + Haƙƙin Mallaka: + + + + Ana amfani da canji nan take + + + + Shigar da madanni... + + + + Ɗakko madanni... + + + + Zaɓuɓɓukan madannai + + + + Ba ku da wasu madannai da aka shigar. Danna madannin Saukewa domin shigar da shirin madannin daga yanar gizon Keyman. + + + + + Za ka iya cire \'%1$s\' na madannan kaɗai idan ka kasance Mai Gudanarwa + + + + Madanni tare + + + + Duba wannan lambar sannan a shigar da wannan madanni ga wata na\'urar ko + + + + amfani da shi tare ta yanar gizo + + + + + + + + Gabaɗaya + + + + Farawa + + + + Madannan kan Screen + + + + Cigaba + + + + Canja madannan zuwa madannan hotkeys + + + + Kwaikwayi AltGr tare da Ctrl+Alt + + + + Amfani da hardware deadkeys a matsayin madannai na sarari + + + + Nuna alamar saƙonni + + + + Aika rahoton kura-kurai kai tsaye ga keyman.com + + + + A tura abinda ba a fahimta ba ga keyman.com + + + + Fara lokacin da Windows ya fara + + + + Shafin dake nuna hotuna idan an buɗe kwamfuta + + + + Shafin farko na kwamfuta + + + + A dinga duba keyman.com duk sati domin samun sabbin labarai + + + + Gwada ayyuka musu cin karo da juna lokacin da keyman ya fara + + + + Saki Shift/Ctrl/Alt Kan Madannin Screen bayan danna madannin + + + + Nuna Madannan Kan Screen koyaushe yayinda aka zaɓi madanni + + + + Koma ga Madannin Kan Screen da ya dace/Taikamaka kai tsaye yayinda aka zaɓi madanni + + + + Kunna ƙarin harafin Unicode (yana buƙatar farawa) + + + + Kunna Yaren \'Da ba a sani ba\' + + + + Cire kuskure + + + + Canja yaren Windows lokacin da aka zaɓi madannin Keyman + + + + Zaɓi shirin madanni domin dukkanin manhajojin + + + + Nemi izinin masu amfani kowane lokaci yayin shiga Windows Vista + + + + Tushen Madanni... + + + + + + + + + + + + + (babu) + + + + Kashe Keyman + + + + Buɗe Tsarin Madanni + + + + Nuna Tambarin Madannin Kan Screen + + + + Buɗe Kwanfigireshi + + + + Nuna Tambarin Taimakawa Harafi + + + + Nuna Tambarin Taswirar Harafi + + + + Buɗe wajen Gyaran Rubutu + + + + Buɗe wajen Sauya Yare + + + + Hotkeys na Bai-ɗaya + + + + Shirin Madannai + + + + Idan akwai wata matsalar amfani da Keyman a yi tambaya ga Dandalin Keyman. + + + + Buɗe Dandalin Keyman + + + + Ƙirƙira daga SIL Internationl + + + + Haƙƙin mallaka © SIL International All Rights Reserved. + + + + Nau\'i + + + + Links Masu amfani + + + + Taimakon Yanar gizo + + + + Dubawa don Sabuntawa + + + + Saitin Proxy... + + + + Saitin Tsare-tsaren Keyman... + + + + Bincikawa + + + + A sauke Madannai daga keyman.com + + + + Kada a shigar, a sauke kawai + + + + + An kasa sauke ƙunshin keyboard %2$d: %1$s + + + + < Baya + + + + A saka Ƙunshin/Madanni + + + + Cikakken bayani + + + + Karanta + + + + Shigarwa + + + + Rumbun bayanan Proxy Kwanfigareshin + + + + Rumbun adana bayanai: + + + + Port: + + + + Sunan mai amfani da shi: + + + + Lambar sirri: + + + + Saita Tushen Madanni + + + + Zaɓi tushen madannin rubutun Latin da kuke amfani dashi a cikin Windows. Madannan Keyman za su daidaita kai tsaye zuwa tsarin da kuka fi so. + + + + Canja Hotkeys + + + + + Zaɓi tsayayyen hotkey ko a zaɓi \'Custom\' a kuma danne Ctrl,Shift da/ko Alt sannan a rubuta irin hotkey ɗin yaren da ake so %1$s: + + + + Zaɓi tsayayyen hotkey ko a zaɓi \'Custom\' a kuma danne Ctrl,Shift da/ko Alt sannan a rubuta irin hotkey ɗin yaren da ake so: + + + + + Hotkey %1$s zai ci karo da amfani da madannan da aka saba amfani. Ya kamata ku yi amfani da aƙalla Ctrl ko Alt. Kuna so ku canza wannan yanzu \? + + + + + Hotkey %1$s ya ci karo da wanda aka zaɓa saboda madannan %2$s. Idan kuka ci gaba, za a goge hotkey saboda madannan %2$s. Ci gaba \? + + + + + Hotkey %1$s ya ci karo da wani hotkey. Idan kuka ci gaba, za a goge sauran hotkey ɗin. Ci gaba \? + + + + Akwai Tarin Sabbin Keyman + + + + Yanzu haka ana samun sabon Keyman da aka sabunta + + + + Taimaka zaɓi wanda kake son sakawa da aka sabunta: + + + + Za ka iya ɗauko sabon nau\'in: + + + + Shigar yanzu + + + + Soke + + + + Tsohon Nau\'i + + + + Tarin wanda aka Sabunta + + + + Girma + + + + + Keyman %1$s + + + + + Keyboard %1$s %2$s + + + + An kasa tuntuɓar keyman.com - yi haƙuri a tabbata ana da mahaɗar Intanet mai aiki sai a sake gwadawa. + + + + + An kasa tuntuɓar keyman.com - yi haƙuri a tabbata ana da mahaɗar Intanet mai aiki sai a sake gwadawa. kuskuren da aka samu shine: %1$s + + + + Ana samun sabon Keyman da aka sabunta + + + + Danna wannan alamar domin saukewa da saka sabon + + + + Duba da &saka Keyman ɗin da aka sabunta + + + + E&xit duba wanda aka sabunta a yanar gizo + + + + Keyman + + + + Kunna Keyman + + + + Fita + + + + Haɗawa + + + + A nuna wannan screen lokacin farawa + + + + Nuna cikin + + + + Nemo wasu yarukan da ake da su a yanar gizo... + + + + Taimaka fassara mai amfani da na\'urar... + + + + Hausa + + + + Hausa + + + + en + + + + en + + + + Keyman + + + + Sake saita Alamu + + + + Dukkanin alamun saƙonnin an sake saita su za su kuma bayyana. + + + + + Fita daga Keyman\? + + + + + Shin an tabbata ana son fita daga Keyman \? Har yanzu akwai jerin Madannan Keyman ɗinku cikin yarukan Windows, amma ba za su yi aiki ba har sai kun sake kunna Keyman. + + + + + An Rufe Madannin Kan Screen + + + + + An rufe Madannin Kan Screen. Amma har yanzu Keyman na aiki. Za a iya buɗe Madanin Kan Screen a kowane lokaci ta danna Alamar Keyman kuma a yi zaɓi \"A kan Madannin Screen\" + + + + Kar a sake nuna wannan alamar + + + + Taimakon Keyman + + + + Abubuwan Taimako + + + + Taimako Kan \" + + + + \" keyboard + + + + Duba Madannin Kan Screen + + + + Duba Maitaimakawa Font + + + + Duba Taswirar Harufa + + + + Buɗe Keyman Kwanfigareshin + + + + Buɗe Taimako + + + + Rufe Madannin Kan Screen + + + + &Kashe Keyman + + + + Madannan &Kan Screen + + + + &Maitaimakawa Font + + + + Taswirar &Harufa + + + + &Mai gyara Rubutu... + + + + &Kwanfigareshin... + + + + &Taimako... + + + + E&xit + + + + Ragewa lokacin da ba ya aiki + + + + Nuna Toolbar + + + + Adanawa a matsayin shafin yanar gizo... + + + + Wallafa... + + + + Keyman + + + + + Nau\'i %1$s + + + + &Wallafa... + + + + + Tuni an shigar da kunshi mai suna %1$s. Shin kuna son cire shi kuma a shigar da sabon \? + + + + + Tuni an shigar da ƙunshi mai suna %1$s. Idan kuka ci gaba, za a cire shi kafin a shigar da sabon. Ci gaba \? + + + + + Madanni \'%1$s\' wani ɓangare ne na ƙunshin \'%2$s\'. Dole ne a cire dukkan ƙunshin. Ci gaba \? + + + + + An kasa shigar da yaren madannai; Windows na da iyakar yaruka 4 masu \'juyawa\' da aka saba, kuma mai yiwuwa kun kai wannan iyaka. + + + + + Ƙunshin ko MadannIn \'%1$s\' bai haɗa da wani taimako na gabatarwa ba. + + + + Ba a tallafawa wannan tsarin aiki ba. + + + + KeymanDesktop.chm + + + + Ba a iya samo fayil ɗin taimakon ba. + + + + Codepage + + + + Unicode + + + + Sauke fayil + + + + + An tabbatar ana son cire madannin kan screen da aka saka domin %1$s\? + + + + + An tabbatar ana son cire madannin %1$s\? + + + + + Shin an tabbata ana son cire ƙunshin %1$s\? \n\n %2$s + + + + + Haka kuma za a cire wadannan font masu zuwa %1$s. + + + + Taswirar Harufa na da rumbun bayanan haruffa da ke da buƙatar ginawa kafin a yi amfani da su. Gina shi yanzu \? + + + + + Ba za a iya goge rumbum bayanan Harufa na Unicode don sake ginawa ba. Bayanin kura-kuran sune: %1$s + + + + + Ba za a iya ƙirƙirar rumbun bayanan Harufan Unicode kuma cire shi ba. Bayanan kura-kuran sune: %1$s + + + + + Ba a yi nasarar sauke rumbun bayanan harufan unicode ba (%1$s). Sake gina shi yanzu \? + + + + + Keyman ya ƙi fara aiki. Da fatan za a bincika saitunan tsaro domin tabbatar da cewa an bawa keyman.exe damar shiri kafin a ci gaba. Matsalar da ta afku ita ce: \n \n \"%1$s\" \n\nAna son gwadawa da sake kunna Keyman yanzu \? + + + + Za a adana bayanan cire kuskure na Keyman a cikin fayil ɗin wajen da ake kira %LOCALAPPDATA% \Keyman\Diag\system#.etl (inda # lamba ce). Ya kamata ku fita daga Keyman kafin yunƙurin goge wannan fayil ɗin. \n\n\ GARGADI: Wannan fayil ɗin zai iya girma cikin gaggawa. Cire matsalar na iya rage saurin na\'urar zai fi kyau a yi shi ne kawai idan taimakon fasaha ya ba da shawara. \n\n GARGAƊI NA SIRRI: Lura cewa dukkanin fayil ɗin cire kuskure yana rikodin dukkanin madannan da kuka danna. Yakamata kawai ku koma ɓangaren cire kuskure domin sanin tsawon lokacin kuskuren ko fannin bincike. + + + + Da fatan za a jira yayin neman fonts da ke da alaƙa da madannin %1$s + + + + Madannin %1$s ba madannin Unicode bane. ba za a iya samun Font kai tsaye ba. Da fatan za a bincika takardun madanni domin samun bayanan font. + + + + Ba wani shirin madanni da a yanzu aka saka ko aka sauke. + + + + Yi haƙuri a zaɓi Madannin Keyman domin gano font masu alaƙa. + + + + Mai gyaran Rubutu - Keyman + diff --git a/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml b/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml new file mode 100644 index 0000000000..68b89ec8d7 --- /dev/null +++ b/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml @@ -0,0 +1,912 @@ + + + + + + + Keyman + + + + Segoe UI + + + + 9 + + + + &Ane + + + + &Aa + + + + Bastuwa + + + + Tsanahe + + + + Hiɗante + + + + Bastuwa + + + + Tsanahe + + + + Piyana-piya ikon gé ksa keyboard + + + + Ba njane Keyman ŋanna awura. piyana-piya ikon gé ksa keyboard a nara ŋa ko mar gé lokte + + + + Aŋkwa Keyman ŋanna a maga slera. piyana-piya ikon Keyman am system gé naza yehakte gé maga sleran keyboard a nara ŋa + + + + Kaɓa ndra slera Keyman + + + + Mlá + + + + Ndra keyboard ŋanna + + + + K + + + + Ksa nazu a kátava + + + + O + + + + Hotkey + + + + H + + + + Mlá + + + + S + + + + Gejante + + + + T + + + + Zhera fayile: + + + + Jiba pakej: + + + + Jiba Keyboard: + + + + Dadda vinda: + + + + Website: + + + + Pakej: + + + + Fonts: + + + + Ndraha Keyboard: + + + + Ndra Keyboard: + + + + Nare Keyboard: + + + + Encodings: + + + + Jiba ndra ne: + + + + Ba nja ndra ne (mátá nja ne) + + + + Fatawe mara ndra Windoha (Mnemonic) + + + + Naraha: + + + + X + + + + Fare nare + + + + At sikirina Keyboard: + + + + Nalada maga duksá + + + + An-fa + + + + Femka nde + + + + Takarda bayani: + + + + An-fa + + + + Femka nde + + + + Sako: + + + + Hakkin malaka: + + + + A maga nde slera an mbeɗavaha ŋanna at watsire + + + + Femhe Keyboard... + + + + Tsukwanahe Keyboard... + + + + Keyboard ksa gergre + + + + Ekka ɓáaka keyboard na an-fa. Piyana-piya ɗyila Keyboard ŋanna gé tsukwanahe antara femhe ndra Keyboard a sawam Keyman website. + + + + + Ká naba ɗyanhe fa Keyboard \'%1$s\' ma seke ekka dadda maga slera anikwa + + + + Diga Keyboard + + + + Ksa fatawa una gé code naza maga slera an Keyboard ŋanna am nawura umbulé mtu + + + + diga am online + + + + + + + + Baɗumme + + + + Fantuwe + + + + Keyboard at sikirine + + + + Naza katafke + + + + Keyboard hotkeys toggle gé maga slera Keyboard + + + + Aza mbeɗa slera AltGr antara Ctrl+Alt + + + + Zlálávan hardware keyaha an mtsa ba seke ko mar gé keyaha + + + + Mara naza yehte sako + + + + Basha jare kuskure gé Keyman.com + + + + Diga kwakyira nda maga slera an Keyman.com ba kwal-zhere + + + + Fantuhe ma seke fantufe Windoha + + + + Mara cara sikirin + + + + Mara maraba sikirin + + + + Zharanahe keyman.com a ba at lokte ba ko mar gé make gé sha updates + + + + Gé gwadanahe applications na ɓáaka a jemhe an Keyman ma seke fantufe slera + + + + Ká ɓilanahe Shift/Ctrl/Alt on am sikirin Keyboard ma seke piyakana-piya key ŋanna + + + + A nja a ban kwa Keyboard Window at sikirine ma seke an ksa Keyman keyboard ŋanna gé slera + + + + Magamaga Switch a dem kalkale sikirin Keyboard/anuga mla ma seke ksanta-ksande keyboard ŋanna + + + + Kunnante-kunne Unicode naza mara umbulé gé karakta ŋanna (a kátá fantuhe adaliya) + + + + Kunnante-kunne Naru ʻdiyakandeʻ + + + + Debugging + + + + Kunnante-kunne nare Windows aza Keyman keyboard a ksante nde ŋanna + + + + Ksa jiba ndra keyboard ŋanna gé baɗumma appications + + + + Ba kullume mmaga slera an izinin ma ká maga slera an Windows Vista + + + + Base Keyboard... + + + + + + + + + + + + + (none) + + + + Jejja Keyman ŋanna + + + + Wurante-wura Keyboard Menu + + + + Mara at Sikirin Keyboard Pane + + + + Wurant-wura Configuration + + + + Mara Font Helper Pane + + + + Mara Karakta Map Pane + + + + Wurante-wura Text Editor + + + + Wurante-wura aza mbeɗa Nare + + + + General Hotkeys + + + + Ndraha Keyboard ŋanna + + + + Ma seke aŋkwa damuwa an maga slera Keyman, ká naba ndava nda Keyman Community Forum. + + + + Wurante-wura Keyman Community Forum + + + + Tá kaɓana nda SIL International + + + + Hakkin malaka © SIL International. Baɗumma Hakki a ba aza itare. + + + + Version + + + + Nampire Links + + + + Mla atakwan Online + + + + Zhara gé daula Updates + + + + Proxy saiti... + + + + Keyman System Saiti... + + + + Gwada Bincike + + + + Tsukwanahe Keyboard a sawam keyman.com + + + + Femka, amma tsukwana-tsukwa + + + + + At tsukwanaka keyboard ŋanna, kuskure %2$d: %1$s + + + + < Fitsanahe adeliye + + + + Femhe Keyboard/Pakeji + + + + Baɗumma bayani + + + + Karanta-karata ya + + + + Femhe gé ga slera + + + + Proxy Server Configuration + + + + Server: + + + + Port: + + + + Username: + + + + Password: + + + + Saitin Base Keyboard + + + + Ksante-ksa base Latin script +keyboard na ká ga slera anikwa na am Windows. Keyman keyboards anuga maga slera anikwa ba seke una ká waya naka ndra ŋanna. + + + + Mbeɗa Hotkey + + + + + Ksante-ksa hotkey mtu ksa \'Custom\' antara ká ŋannahe Ctrl,Shift antara/mtu Alt antara ká piyanahe jiba hotkey ká kátá naka naza nare ŋa %1$s: + + + + Ksante-ksa hotkey mtu ksa \'Custom\' antara ká ŋannahe Ctrl,Shift antara/mtu Alt antara ká piyanahe jiba hotkey ká kátá naka: + + + + + Hotkey ŋanna %1$s a magavka kalkale antara mazla Keyboard ŋanna. Mmaga slera seke an Ctrl or Alt. Ká kátáhe mbeɗa una ŋanna akina\? + + + + + Hotkey ŋanna %1$s a magavka kalkale antara mazla Keyboard ŋanna %2$s. Ma seke ká ba maga slera an hotkey gé keyboard %2$s anuga kya digashe. Ba ká maga adaliya\? + + + + + Gé hotkey %1$s a magavka an hotkey umbulé ŋanna. Ma seke ka naba maga ne dem katafke, anuga kiɗa umbula hotkey ŋanna. Mmaga a dem katafke \? + + + + Duksáhá na aŋkwa gé Updated Keyman + + + + Aŋkwa Updates aliyana gé Keyman + + + + Tazlaŋa ksa updates na ká waya naka gé femhe: + + + + Ká naba maga download a daula version a sawam: + + + + Femfe a ba akina + + + + Tsanahe + + + + Emgyigwa Version + + + + Updated Component + + + + Ƴaikira ne + + + + + Keyman %1$s + + + + + Keyboard %1$s %2$s + + + + At magavka shanahe keyman.com - tazlaŋa a nja aŋkwa Internet an ndzeɗa antara ká gwadanahe adaliya. + + + + + At magavka shanahe keyman.com - tazlaŋa a nja aŋkwa Internet an ndzeɗa antara ká gwadanahe adaliya. Kuskure na a shavante ŋane: %1$s + + + + Aŋkwa Updates a Keyman + + + + Piyana-piya una gé icon gé download antara femhe updates + + + + Zhara antara &femhe Keyman updates + + + + E&xit online update zhara + + + + Keyman + + + + Fantufe Keyman + + + + Shigashe + + + + Configuration + + + + Mara una gé sikirin am sarta fantuhe gé ga slera + + + + Nazu a marana ŋane + + + + Zhara umbulé gé naraha am online... + + + + Mla gé malante an puwa user interface... + + + + Mandara + + + + Mandara + + + + en + + + + en + + + + Keyman + + + + Maga saiti Hints adaliya + + + + Baɗumma labare elva hint ŋanna magamaga nde saiti adaliya gé mara ne adaliya. + + + + + Shigashe Keyman\? + + + + + Ba jirahe ká kátá shigashe am Keyman\? Keyman keyboards ŋa a de ba aŋkwa naraha Windows, amma a de ɓáaka a fantu slera sey ma fantufe Keyman adaliya. + + + + + At sikirin Keyboard an Hiɗa + + + + + Hiɗakante-hiɗa Keyboard at sikirin ŋanna. Keyman ba nja ne a maga slera. Ká naba wura Keyboard sikirin ma ndara a ba loktar kini aɗaba piyanahe Keyman Icon antara ksa \"kunna sikirin Keyboard\" + + + + Marka una gé hint adaliya + + + + Mla Keyman + + + + Duksáhá Mla + + + + Mla an kunna \" + + + + \" keyboard + + + + Zhara Keyboard at sikirine + + + + Zhara aza mla aɗaba Font + + + + Zhara Karakta Map + + + + Wurante-wura Keyman Configuration + + + + Wurante-wura Mla + + + + Hiɗante-hiɗa Keyboard at sikirine + + + + Jejja Keyman &ŋanna + + + + &Keyboard at sikirine + + + + &Mla aɗaba Font + + + + Karakta &Map + + + + &Text Editor... + + + + &Configuration... + + + + &Mla... + + + + E&xit + + + + Anuga maga Fade ma seke ɓáaka a ga slera + + + + Mara Toolbar + + + + Save ba seke Web Peji... + + + + Bárá-at kaɗkaɗe... + + + + Keyman + + + + + Version %1$s + + + + &Bárá-at kaɗkaɗe... + + + + + Pakeji antara zhere %1$s aŋkwa an fa. Ká kátáhe ɗyanhe femhe una ŋanna antara femhe daula ne\? + + + + + Keyboard antara zhera ne \'%1$s\' aŋkwa an fa. ma seke fakarfe a dem katafke, anuga tiɗa erwa ŋanna kabu a fa daula ne ŋanna. ksakarksahe\? + + + + + Keyboard \'%1$s\' ŋane a ba atakwan pakeji \'%2$s\'. Sey ma tiɗakse-tiɗe baɗumma pakeji ŋanna. Ksakarksahe\? + + + + + A magavka femhe keyboard nare ŋanna; Windows aŋkwa zlaktane 4 custom \'transient\' naraha, antara matake shakanashe zlaktane gé magave. + + + + + Pakeji ŋanna mtu keyboard \'%1$s\' janemka antara duksá mla zuŋŋwire. + + + + Ɓáaka ga slera am jiba una gé operating system. + + + + KeymanDesktop.chm + + + + A shavantika file mla ŋanna. + + + + Codepeji + + + + Unicode + + + + Tsukwa File ŋanna + + + + + Ba jirahe ká kátá tiɗa keyboard sikirine na an fa na %1$s\? + + + + + Ba jirahe ká kátá femhe keyboard %1$s\? + + + + + Ba jirahe ká kátá femhe pakeji %1$s\?\n\n%2$s + + + + + Una gé mazlaha fonts kini anuga a de tiɗa ashigashe: %1$s. + + + + Karakta Map an database gé karakta a kátá kaɓana nde kabu a fantu maga slera. Kaɓana-kaɓa akina\? + + + + + Unicode Karakta database at gaggaka nde gé kaɓanahe adaliya. Baɗumma bayani kuskure ŋane: %1$s + + + + + Unicode Karakta database at magavka antara janfuje nde maga slera. Baɗumma bayani kuskure itare: %1$s + + + + + Unicode karakta database ɗalatika ɗumme shagera (%1$s). Kaɓana-kaɓa adaliya akina\? + + + + + Keyman kwala-kwale fantu. Tazlaŋa zharana-zhara saitin program keyman.exe a fantuhe kabu a fantufe maga slera ŋanna.kuskure ŋane sasa:\n\n\"%1$s\"\n\nKá kátáhe gwadanahe adaliya antara fantu Keyman ŋanna adaliya akina\? + + + + Keyman debug labare ŋanna anuga a de puwa am log file a ɗavan %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). Ká nuga shigashe am Keyman kabu ká gwada gagga file.\n\n\WARNING: File ŋanna anuga nye ba ƴaike a ba at watsire. Magave debugging anaba gulla maga slera system antara sawari a sawa aza nda technical support.\n\nPRIVACY WARNING: Tazlaŋa diyasdiya gé kini debug logfile a maga rakodini baɗumma keystrokes ŋanna ká vinda ká. Ká kunna debug log gé naza lokte debugging mtu diagnostic session. + + + + Tazlaŋa páká gé naza lokte cekwa a ɗatsa fonts harzhe an keyboard ŋanna %1$s + + + + Keyboard %1$s ŋanenka Unicode keyboard. Fonts a tá shantika nde paɗare ba estuwa. Tazlaŋa zharana-zhara bayani at kaɗkaɗe keyboard aɗaba labare font ŋanna. + + + + Ɓáaka ndra keyboard akina an fa mtu an burha. + + + + Tazlaŋa ksa Keyman keyboard gé shante jiba fonts ŋanna. + + + + Text Editor - Keyman + diff --git a/windows/src/desktop/setup/locale/bwr-NT/strings.xml b/windows/src/desktop/setup/locale/bwr-NT/strings.xml new file mode 100644 index 0000000000..0e57bf4ae9 --- /dev/null +++ b/windows/src/desktop/setup/locale/bwr-NT/strings.xml @@ -0,0 +1,80 @@ + + + Bura-Pabir + $APPNAME $VERSION Setup + Instal $APPNAME $VERSION + $APPNAME $VERSION kǝ wutǝ ka hara instal ɓɗakǝlaka. + Gǝ tamata abbǝr ga aɗa hira kara lǝkǝma ka hara instal ar $APPNAME ni wa ya? + Akwa ɗǝmɓǝla bundle... + Akwa pakǝ packageyeri... + Akwa pakǝ suayeri na bǝlin akwa onlayin... + Akwa pakǝ jiliyeri natǝ mdǝ hara instal... + Akwa kuriyari... + + • $APPNAME %0:s %1:s + + • %0:s %1:s %2:s + + • %0:s %1:s ar %2:s %3:s + Suma aɗiwa natǝ mda ata hara instal ni. + + (%0:s dawunlodi) + Setup an ata hara instal ni: + $APPNAME $VERSION tsa ni gapani kula ntǝ kwaba + &Karatǝ lasisi ni + Instal &suayeri natǝ mda ata cara + $APPNAME Setup + OK + &Instal + Awo + E&xit + Akwa hara instal $APPNAME + Akwa kǝlǝɓǝla jili na waci ni + Ku kurtǝ ka ka kuri hara instal ni + Doli sai magǝ jaktǝ kǝlar Windows kwapu Setup ni ku kuri. Sakatǝ gǝ jaktǝ kǝlar Windows ni, Setup ata lǝkǝma. + +Jaktǝ kǝlari kulini? + Windows aɗa wutǝdzi ka jaktǝ kǝlardzi ka kǝrni wa. Kagǝ jaktǝ kǝlar Windows kwapu gǝ ngkǝrtǝ kǝlar $APPNAME. + Doli sai magǝ jaktǝ kǝlar Windows kwapu Setup ni Kuri. Sakatǝ gǝ jaktǝ kǝlar Windows, Setup ata kuri. + $APPNAME $VERSION ata bara Windows 7 wala nakǝmari kwapu tsa hara instal. Amma, tsaku ngaɓǝla aɗi Keyman Desctop 7, 8 wala 9. Ga ata bara kara hara instal ar keyboardyeri natǝ mdǝ pakta akwa installa ngǝni akwa jili natǝ mdǝ hara instal ar Keyman Desktop ni ya? + Jili $APPNAME ngǝni ata bara Windows 7 wala na kǝmari kwapu tsa hara instal. Ga ata bara kara hara dawunlodi ar Keyman Desktop 8 ya? + Suayeri natǝ mda ata cara na ar isntal + Suayeri natǝ mda ata cara na ar hara isntal + Default $APPNAME setiyeri + Modulyeri natǝ mda ata hara instal wala jakǝ ala duna + Hulfur mnya ar keyboard na kǝɓa kari + Jili natǝ mda ata hara instal + Baɗita $APPNAME sakatǝ Windows kǝkǝlardzi + Baɗita $APPNAME sakatǝ instal ni kǝ kuri + Pakǝɓǝla suayeri na bǝlin akwa onlayin saka ka saka + Jaktǝ dunar jili keyboardyeri na waci natǝ mdǝ hara instal ɗa ka jili na $VERSION + Ngkǝrɓǝla labar na ar hangkur kǝthlǝr tǝ ga ana hara kula ngkǝr thlǝma ga aka keyman.com + + Setup ar jili: %0:s + Instal $APPNAME + Jaktǝ dunar $APPNAME + + $APPNAME %0:s kǝ kurtǝ ka hara instal. + + Dawunlodi jili %0:s (%1:s) + + Jili %0:s + + Instal %0:s + + Dawunlodi jili %0:s (%1:s) + + Jili %0:s + + Car hulfur mnya natǝ gǝ hira kara pakta ka %0:s keyboard ni + Hulfur mnya na default + + Akwa hara dawunlodi %0:s + + Akwa hara dawunlodi %0:s + Setup ar $APPNAME aɗa tsoktǝ ka kǝɓa ka kwyman.com kamnyar katsa hara dawunlodi ar kariyeri na damwa. + +Usa kara wulha abbǝr ka akwa onlayin, antǝ kara hamta $APPNAME dunar Setup kamnyar wutǝ intanet akwa setiyeri ar firewall. + +Ntsari Abort kamnyar lǝɓǝla akwa Setup, Jaktǝ ngkǝrta kamnyar ngkǝrtǝ hara dawunlodi ar kariyeri ni ka damwa, wala fwari kari kamnyar lǝkǝma kari akwa offlayin. + diff --git a/windows/src/desktop/setup/locale/ff-NG/strings.xml b/windows/src/desktop/setup/locale/ff-NG/strings.xml new file mode 100644 index 0000000000..02247aea4d --- /dev/null +++ b/windows/src/desktop/setup/locale/ff-NG/strings.xml @@ -0,0 +1,80 @@ + + + Ɗemngal Fulfulde + $APPNAME $VERSION Wa`uggo + Wa`unu $APPNAME $VERSION + $APPNAME $VERSION wa`aama no haandi. + A` tabbiti a` ɗo yiɗi wurtugo wa`innol man na $APPNAME? + Ɗo wurtina bondul... + Ɗo larta pakej + Ɗo larta dow lenguru ngam kesitiggo... + Ɗo larta vashon ko wa`inni... + Ɗo timmina dow... + + • $APPNAME %0:s %1:s + + • %0:s %1:s %2:s + + • %0:s %1:s jee %2:s %3:s + Walaa ko wa`ina. + + (%0:s jippunu) + Wo`innol man wa`an: + $APPNAME $VERSION ɗum caahu on nden koomoy waaway naftira + &Janngu lasisi man + Cuɓol &wa`innol + $APPNAME Wo`innol + TOH + &Wa`innu + Wurtu + W&urtu + Ɗo wa`inna $APPNAME + Ɗo itta jee ɓoyma + Tmmini wa`ingo + Sayto a` kesiti lenguru windo bako wa`innol man timma. To a` kesiti lenguru windo, wa`innol man jokkitay. + +A` waɗan kesitol man jotta? + Lenguru windo man sali lorugo kesita bee hoore mum. Sayto a` kesiti lenguru windo bako a` foonda waɗita $APPNAME. + Doole sayto a` kesiti windo bako wa`innol timma. To a` kesiti lenguru windo wa`innol man timman. + $APPNAME $VERSION ɗo mari haaje windo 7 ko ko ɓuri bako wa`a. Haa fahin, Keyman dow komputa 7, 8 ko 9 keptaama. A` ɗo yiɗi wa`inna kibod man na ko kawti nder wa`a ɗo dow Keyman jee dow komputa ko wa`ini ma? + Nonde $APPNAME ɗo mari haaje lenguru windo 7 ko ko ɓuri ɗum bako wa`a. A` ɗo yiɗi jippina Keyman jee dow Komputa 8? + Cuɓol wa`innol + Cuɓol wa`innol + Setinji ko woni dow komputa $APPNAME + Yebre man ngam wa`inna ko kesita + Ɗemngal kibod ko wari bee man + Nonde ko wa`inta + Fuɗɗu $APPNAME to lenguru windo man fuɗɗi + Fuɗɗu $APPNAME to wa`ingo man timmini + A` tefa kesitiggo dow online wakkati ɓaawo wakkati + A` kesita kibod ko wa`ini jee ɓoyma yaha nonde $VERSION + A` senda wi`ɗitol naftirol dow sirru bee keyman.com + + Nonde wa`innol: %0:s + Wa`unu $APPNAME + Kesitu $APPNAME + + $APPNAME %0:s arti wa`i. + + Jippunu nonde %0:s (%1:s) + + Nonde %0:s + + Wa`unu %0:s + + Jippunu nonde %0:s (%1:s) + + Nonde %0:s + + A` suɓa ɗemngal ngal a` yiɗi kawrugo bee %0:s kibod + Ɗemngal ko naftirta + + Jippingo %0:s + + Jippingo %0:s + $APPNAME Nangataa keyman.com ngam jippina ɓeddaari kare kuuɗe. + +Usseni laaru dow a` ɗo online, nden a` hokka $APPNAME laawol wa`innol ngam keɓa nasta nder intanet dow settinji ma. + +A` ɓiɗɗa Abort ngam a` wurta wa`innol, a` foondita ngam a` fonnda jippingo kare kuuɗe fahin, ko bo a` wurta haala man ngam a` jokkita ha yaasi intanet. + diff --git a/windows/src/desktop/setup/locale/ha-HG/strings.xml b/windows/src/desktop/setup/locale/ha-HG/strings.xml new file mode 100644 index 0000000000..42b626c579 --- /dev/null +++ b/windows/src/desktop/setup/locale/ha-HG/strings.xml @@ -0,0 +1,80 @@ + + + Hausa + Saita $APPNAME $VERSION + Shigar da $APPNAME $VERSION + An sami nasarar shigar da $APPNAME $VERSION. + An tabbatar ana son soke shigar da $APPNAME? + Ana ciro tarin... + Ana duba ƙunshin... + Dubawa kan yanar gizo domin a sabunta... + Ana duba nau\'ín da aka shigar... + Ana kammalawa... + + • $APPNAME %0:s %1:s + + • %0:s %1:s %2:s + + • %0:s %1:s for %2:s %3:s + Babu abinda za a shigar. + + (sauke %0:s) + Saitin zai shigar: + $APPNAME $VERSION kyauta ne a buɗe + &Karanta lasisin + Shigarwa &zaɓi + Saitin $APPNAME + OK + &Shigarwa + Soke + E&xit + Shigar da $APPNAME + Cire tsohon nau\'in + An kammala shigarwa + Dole ne ku sake kunna Windows kafin Saitin ya kammala. Lokacin da kuka sake kunna Windows, Saitin zai ci gaba. + +Sake kunnawa yanzu? + Windows ɗin bai sami damar sake kunnuwa kai tsaye ba. Ya kamata ku sake kunna Windows ɗin kafin gwadawa da fara $APPNAME. + Dole ne a sake kunna Windows ɗin domin kammala saitin, Lokacin da aka kuma kunna Windows, Saitin zai kammala. + $APPNAME $VERSION na buƙatar Windows 7 ko daga baya don shigarwa. Haka kuma, an gano Keyman Desktop 7, 8 ko 9. Shin ana son shigar da madannan da aka haɗa a cikin wannan mashiga cikin sigar Keyman Desktop ɗin da aka shigar? + Wannan nau\'in $APPNAME na buƙatar Windows 7 ko daga baya domin shigarwa. Kuna son saukar da Keyman Desktop 8? + Zaɓukan shigarwa + Zaɓukan shigarwa + Saitin default $APPNAME + Abubuwa masu alaƙa da za a shigar ko haɓakawa + Madannai masu Alaƙa da Yare + Shigar da Nau\'i + Kunna $APPNAME yayinda aka kunna Window + Kunna $APPNAME yayinda aka kammala shigarwa + Duba sabuntawa a yanar gizo lokaci zuwa lokaci + Haɓaka madannan da aka shigar daga tsohon nau\'i zuwa nau\'in $VERSION + Tura abubuwan da ba a fahimta ba ga keyman.com + + Saita nau\'in; %0:s + Shigar da $APPNAME + Haɓaka $APPNAME + + An riga da an shigar da $APPNAME %0:s. + + Sauke nau\'in %0:s (%1:s) + + Nau\'in %0:s + + Shigar da %0:s + + Sauke nau\'in %0:s (%1:s) + + Nau\'in %0:s + + A zaɓi yaren da ake son haɗawa da madannin %0:s + Default yare + + Ana sauke %0:s + + Ana sauke %0:s + Saitin $APPNAME bai sami haɗewa zuwa keyman.com domin saukar da ƙarin abubuwa ba. + +Da fatan za a duba cewa kuna kan yanar gizo, kuma a bawa Saitin $APPNAME izinin shiga Intanet a cikin saitunan firewall. + +Danna Abort domin fita daga Saitin, Sake gwadawa da sake saukar da abubuwa, ko Barinsa domin ci gaba offline. + diff --git a/windows/src/desktop/setup/locale/mfi-NG/strings.xml b/windows/src/desktop/setup/locale/mfi-NG/strings.xml new file mode 100644 index 0000000000..aeb9063c8d --- /dev/null +++ b/windows/src/desktop/setup/locale/mfi-NG/strings.xml @@ -0,0 +1,80 @@ + + + Mandara + $APPNAME $VERSION Setup + Femhe$APPNAME $VERSION + $APPNAME $VERSION zlawzla nde a fa ne ba shagera. + Ba jirahe ká kátá tsanahe femhe $APPNAME? + Tiɗa bundle... + Zhara pakej... + Zhara daula ne am online... + Zhara jiba versions na an fa na... + Zlanahe... + + • $APPNAME %0:s %1:s + + • %0:s %1:s %2:s + + • %0:s %1:s for %2:s %3:s + Ɓáaka nazu a femhe nde adaliya. + + (%0:s tsukwanahe) + Saitin ŋanna a fantufe femhe: + $APPNAME $VERSION ŋane a ban dey antara paɗare shante + &Maga karatu lasisi + Femhe &naza ksa gergre + $APPNAME Setup + Bastuwa + &Femhe + Tsanahe + E&xit + Fa ne gé fantu slera $APPNAME + Tiɗa emgyigwa Versions + Zlawzle fa ne gé slera + Sey ma fantufe Windows kabu a fantu maga slera kalkale. Ma seke fantufe Windows ŋanna, Saitin ŋanna anuga a za katafke. + +Fantufahe akina? + Fantuka Windows ŋanna ga slera anirane. Shagera a fantuhe gé Windows ŋanna kabu ga gwada antara fantu $APPNAME. + Dole sey ká fantu Windows kabu ká zla saiti ŋanna. Ma seke fakantufe Windows, anuga a zla saitin ŋanna. + $APPNAME $VERSION Jiba Windows 7 ŋanna a kátáva mtu a de fa nde am katafke. An kya ŋalape umbulé, Keyman Desktop 7, 8 mtu 9 aŋkwa nde a diyase. Ká kátáhe femhe keyboards ŋanna tirba una a fava am nazu an fa gé Keyman Desktop? + Una gé version $APPNAME a kátá Windows 7 mtu gé femhe an katafke. Ká kátáhe tsukwanahe Keyman Desktop 8? + Ksa naza femhe gergre + Femhe naza ksa gergre + Default $APPNAME saitin + Femhe Modules mtu daula ne ŋanna + Nalaka Keyboard Nare + Version ŋanna a de fava na + Fantuhe $APPNAME ma seke Windows fantufe + Fantuhe $APPNAME ma seke zlawzle fa ne ŋanna + Zhara daula duksá online a sawam lokte gé lokte + Farfe ndzeɗa keyboards an fa gé slera antara emgyigwa versions gé version $VERSION + Diga kwakyira maga slera an Keyman.com ba kwal-zhere + + Saitin version: %0:s + Femhe $APPNAME + Farfe ndzeɗa $APPNAME + + $APPNAME %0:s zlawzle femfande gé maga slera. + + Tsukwanahe version %0:s (%1:s) + + Version %0:s + + Femhe %0:s + + Tsukwanahe version %0:s (%1:s) + + Version %0:s + + Ksa nare ŋanna ká kátá naka maga slera anikwa %0:s keyboard + Naru an ksa gé maga slera anikwa + + Tsukwa ne %0:s + + Tsukwa ne %0:s + $APPNAME Saitin ŋanna jemka an keyman.com gé tsukwanahe daula duksáhá. + +Tazlaŋa zharana-zhara ká ba online, antara ká vante $APPNAME dama saiti gé sha Internet am saiti firewall a ŋa. + +Piyana-piya Abort gé tsanahe saiti ŋanna, ká maganahe adaliya antara tsukwanahe duksáhá umbulé adaliya, mtu ká ɗyante gé maga ne offline. + diff --git a/windows/src/developer/TIKE/Makefile b/windows/src/developer/TIKE/Makefile index 8de3a3160c..956cb62805 100644 --- a/windows/src/developer/TIKE/Makefile +++ b/windows/src/developer/TIKE/Makefile @@ -4,7 +4,7 @@ !include ..\..\Defines.mak -build: version.res manifest.res icons dirs xml pull-core +build: version.res manifest.res icons dirs xml xsd pull-core cd $(ROOT)\src\developer\tike $(DELPHI_MSBUILD) tike.dproj /p:Platform=Win32 $(SENTRYTOOL_DELPHIPREP) $(WIN32_TARGET_PATH)\tike.exe -dpr tike.dpr @@ -14,6 +14,9 @@ build: version.res manifest.res icons dirs xml pull-core $(COPY) $(ROOT)\..\common\core\desktop\build\x86\$(TARGET_PATH)\src\kmnkbp0-0.dll $(PROGRAM)\developer if exist $(WIN32_TARGET_PATH)\tike.dbg $(COPY) $(WIN32_TARGET_PATH)\tike.dbg $(DEBUGPATH)\developer +xsd: + $(COPY) $(KEYMAN_ROOT)\common\schemas\kps\kps.xsd $(PROGRAM)\developer + xml: pull-keymanweb pull-npm sentry-init.js cd $(ROOT)\src\developer\tike\xml -del /q $(ROOT)\bin\developer\xml\* diff --git a/windows/src/developer/TIKE/Tike.dpr b/windows/src/developer/TIKE/Tike.dpr index fab4ce67f7..aaaf6f5409 100644 --- a/windows/src/developer/TIKE/Tike.dpr +++ b/windows/src/developer/TIKE/Tike.dpr @@ -292,7 +292,8 @@ uses Keyman.UI.Debug.CharacterGridRenderer in 'debug\Keyman.UI.Debug.CharacterGridRenderer.pas', UfrmDebugStatus_Platform in 'debug\UfrmDebugStatus_Platform.pas' {frmDebugStatus_Platform}, UfrmDebugStatus_Options in 'debug\UfrmDebugStatus_Options.pas' {frmDebugStatus_Options}, - Keyman.Developer.System.KeymanDeveloperPaths in 'main\Keyman.Developer.System.KeymanDeveloperPaths.pas'; + Keyman.Developer.System.KeymanDeveloperPaths in 'main\Keyman.Developer.System.KeymanDeveloperPaths.pas' {$R *.RES}, + Keyman.Developer.System.ValidateKpsFile in '..\..\global\delphi\general\Keyman.Developer.System.ValidateKpsFile.pas'; {$R *.RES} {$R ICONS.RES} diff --git a/windows/src/developer/TIKE/Tike.dproj b/windows/src/developer/TIKE/Tike.dproj index e174c9396d..a3261490c2 100644 --- a/windows/src/developer/TIKE/Tike.dproj +++ b/windows/src/developer/TIKE/Tike.dproj @@ -86,6 +86,7 @@ CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + KeymanDeveloper90.ico Tike_Icon.ico @@ -103,6 +104,7 @@ 1033 true 2 + KeymanDeveloper90.ico DEBUG;$(DCC_Define) @@ -560,6 +562,7 @@
$R *.RES
+ Cfg_2 diff --git a/windows/src/developer/TIKE/Tike.res b/windows/src/developer/TIKE/Tike.res index f4007c2e4a..e9ef8de684 100644 Binary files a/windows/src/developer/TIKE/Tike.res and b/windows/src/developer/TIKE/Tike.res differ diff --git a/windows/src/developer/TIKE/Tike_Icon.ico b/windows/src/developer/TIKE/Tike_Icon.ico deleted file mode 100644 index 189719fc05..0000000000 Binary files a/windows/src/developer/TIKE/Tike_Icon.ico and /dev/null differ diff --git a/windows/src/developer/TIKE/child/UfrmDebug.pas b/windows/src/developer/TIKE/child/UfrmDebug.pas index 9b419fbb3b..4833f51f70 100644 --- a/windows/src/developer/TIKE/child/UfrmDebug.pas +++ b/windows/src/developer/TIKE/child/UfrmDebug.pas @@ -825,6 +825,26 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); end; end; + procedure DoHandleShortcut(vk: UINT); + begin + // Because we disable shortcuts in the debug memo, there are a small set of + // editor Ctrl+Key shortcuts we need to rehandle here. Note that Ctrl+Ins, + // Shift+Ins, Shift+Del appear to be handled natively by the debug memo + // control (Windows code?), as are all cursor movement / selection keys, + // apart from Ctrl+A + + if GetKeyState(VK_CONTROL) >= 0 then + Exit; + + case vk of + Ord('A'): memo.SelectAll; + Ord('C'): memo.CopyToClipboard; + Ord('V'): memo.PasteFromClipboard; + Ord('X'): memo.CutToClipboard; + Ord('Y'): ; // redo not supported in edit + Ord('Z'): memo.Undo; + end; + end; procedure DoEmitKeystroke(dwData: DWord); var @@ -851,6 +871,8 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); else msg.Msg := WM_KEYUP; memo.Dispatch(msg); + DoHandleShortcut(LOBYTE(dwData)); + RealignMemoSelectionState(state); end; end; @@ -1116,6 +1138,7 @@ begin begin StatusText := 'Simple Test'; memo.ReadOnly := False; + memo.IsDebugging := True; end; duiDebugging: begin @@ -1124,6 +1147,7 @@ begin StartBatch; StatusText := 'Debugging'; memo.ReadOnly := True; + memo.IsDebugging := False; end; duiFocusedForInput: begin @@ -1131,6 +1155,7 @@ begin FinishBatch; StatusText := 'Focused for input'; memo.ReadOnly := False; + memo.IsDebugging := True; //SelectSystemLayout(True); end; duiReadyForInput: @@ -1143,6 +1168,7 @@ begin FinishBatch; StatusText := 'Ready for input'; memo.ReadOnly := True; + memo.IsDebugging := False; end; end; duiPaused: @@ -1152,6 +1178,7 @@ begin FUIDisabled := False; // I4033 StatusText := 'Paused'; memo.ReadOnly := True; + memo.IsDebugging := False; end; end; UpdateDebugStatusForm; // I4809 diff --git a/windows/src/developer/TIKE/compile/CompileKeymanWeb.pas b/windows/src/developer/TIKE/compile/CompileKeymanWeb.pas index 086700df0d..55e59479a7 100644 --- a/windows/src/developer/TIKE/compile/CompileKeymanWeb.pas +++ b/windows/src/developer/TIKE/compile/CompileKeymanWeb.pas @@ -251,6 +251,7 @@ type function JavaScript_SetupDebug: string; function JavaScript_SetupEpilog: string; function JavaScript_SetupProlog: string; + function IsKeyboardVersion15OrLater: Boolean; public function Compile(AOwnerProject: TProject; const InFile: string; const OutFile: string; Debug: Boolean; Callback: TCompilerCallbackW): Boolean; // I3681 // I4140 // I4688 // I4866 constructor Create; @@ -1629,12 +1630,26 @@ var ((ch >= $00A0) and (ch <= $10FFFF)); end; + function AreValidUnicodeValues(const value: string): Boolean; + var + v: string; + values: TArray; + begin + values := value.Split(['_']); + if (Length(values) > 1) and not IsKeyboardVersion15OrLater then + Exit(False); + for v in values do + if not IsValidUnicodeValue(StrToIntDef('$'+v, 0)) then + Exit(False); + Result := True; + end; + function KeyIdType(const FId: string): TKeyIdType; // I4142 begin Result := Key_Invalid; case UpCase(FId[1]) of 'T': Result := Key_Touch; - 'U': if (Copy(FId, 1, 2) = 'U_') and IsValidUnicodeValue(StrToIntDef('$'+Copy(FId,3,MaxInt), 0)) then Result := Key_Unicode; // I4198 + 'U': if (Copy(FId, 1, 2) = 'U_') and AreValidUnicodeValues(FId.Substring(2)) then Result := Key_Unicode; // I4198 else if FindVKeyName(FId) <> $FFFF then Result := Key_Constant; end; end; @@ -2328,6 +2343,11 @@ begin Result := fk.version >= VERSION_140; end; +function TCompileKeymanWeb.IsKeyboardVersion15OrLater: Boolean; +begin + Result := fk.version >= VERSION_150; +end; + procedure TCompileKeymanWeb.CheckStoreForInvalidFunctions(key: PFILE_KEY; store: PFILE_STORE); // I1520 var n: Integer; diff --git a/windows/src/developer/TIKE/main/RedistFiles.pas b/windows/src/developer/TIKE/main/RedistFiles.pas index ab40c29b31..7cf0840591 100644 --- a/windows/src/developer/TIKE/main/RedistFiles.pas +++ b/windows/src/developer/TIKE/main/RedistFiles.pas @@ -124,13 +124,28 @@ begin end; function GetRedistProjectTemplatePath: string; +var + root: string; +const + DevProjectTemplatePath = 'windows\src\developer\kmconvert\data\'; begin - Result := GetDebugPath('Debug_RedistTemplatePath', ExtractFilePath(ParamStr(0))+'projects\templates\'); + if TKeymanPaths.RunningFromSource(root) + then Result := root + DevProjectTemplatePath + else Result := ExtractFilePath(ParamStr(0))+'projects\templates\'; + Result := GetDebugPath('Debug_RedistTemplatePath', Result); end; function GetDebugKMCmpDllPath: string; +var + root: string; +const + DevCompilerPath = 'windows\bin\developer'; begin - Result := GetDebugPath('Debug_KMCMPDLLPath', ExtractFilePath(ParamStr(0))); + if TKeymanPaths.RunningFromSource(root) and + FileExists(root + DevCompilerPath + '\kmcmpdll.dll') + then Result := root + DevCompilerPath + else Result := ExtractFilePath(ParamStr(0)); + Result := GetDebugPath('Debug_KMCMPDLLPath', Result); end; function GetXMLTemplatePath: string; diff --git a/windows/src/developer/TIKE/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas b/windows/src/developer/TIKE/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas index 46a01b4a1b..5a88a47922 100644 --- a/windows/src/developer/TIKE/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas +++ b/windows/src/developer/TIKE/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas @@ -29,12 +29,14 @@ uses compile, CompilePackage, CompilePackageInstaller, + Keyman.Developer.System.ValidateKpsFile, PackageInfo, utilexecute; function TkpsProjectFileAction.CompilePackage(APack: TKPSFile; FSilent: Boolean): Boolean; var pack: TKPSFile; + ASchemaPath: string; begin HasCompileWarning := False; // I4706 if APack = nil then @@ -46,6 +48,16 @@ begin else pack := APack; + ASchemaPath := ExtractFilePath(ParamStr(0)); + if (ASchemaPath <> '') and (FileExists(ASchemaPath + 'kps.xsd')) then + begin + if not TValidateKpsFile.Execute(FileName, ASchemaPath + 'kps.xsd', OwnerProject.Log) then + begin + Log(plsFailure, 'Package '+FileName+' had validation errors.', 0, 0); + Exit(False); + end; + end; + try try Result := DoCompilePackage(pack, SelfMessage, FSilent, OwnerProject.Options.CheckFilenameConventions, TargetFilename); diff --git a/windows/src/developer/TIKE/xml/kmw/index.html b/windows/src/developer/TIKE/xml/kmw/index.html index 6efd5b724e..1c1f7e48ea 100644 --- a/windows/src/developer/TIKE/xml/kmw/index.html +++ b/windows/src/developer/TIKE/xml/kmw/index.html @@ -24,19 +24,19 @@
-
+
-
+
-
+

+
+
+ +
+
diff --git a/windows/src/developer/TIKE/xml/kmw/test.css b/windows/src/developer/TIKE/xml/kmw/test.css index 7293143b58..8e28739948 100644 --- a/windows/src/developer/TIKE/xml/kmw/test.css +++ b/windows/src/developer/TIKE/xml/kmw/test.css @@ -109,6 +109,10 @@ h2 { /** Mobile, Tablet **/ +.touch-device #control-devices { + display: none; +} + .install-link .ios, .install-link .android { display: none; @@ -190,7 +194,7 @@ h2 { background-repeat: no-repeat; padding-left: 97px; background-position-y: -358px; - height: 360px; + height: 384px; width: 927px; padding-top: 62px; } @@ -205,7 +209,7 @@ h2 { background-image: url(phone-iphone5-portrait.png); background-position-x: 0px; background-position-y: -794px; - height: 374px; + height: 444px; width: 600px; background-repeat: no-repeat; padding-left: 91px; @@ -218,6 +222,10 @@ h2 { border-radius: 0 0 3px 3px; } +#input-area { + line-height: 0; +} + #input-area, #character-grid { width: 100%; } \ No newline at end of file diff --git a/windows/src/developer/TIKE/xml/kmw/test.js b/windows/src/developer/TIKE/xml/kmw/test.js index 02cc5d0f33..462c89b6e3 100644 --- a/windows/src/developer/TIKE/xml/kmw/test.js +++ b/windows/src/developer/TIKE/xml/kmw/test.js @@ -182,16 +182,23 @@ window.onload = function() { ); let newOSK = null; - let deviceSelect = document.getElementById('device-select'); - if(deviceSelect.value == '') deviceSelect.value = 'desktop'; + let deviceSelect = null; - deviceSelect.addEventListener('change', function() { - setOSK(); - ta1.focus(); - }); + if(!keyman.util.isTouchDevice()) { + deviceSelect = document.getElementById('device-select'); + if(deviceSelect.value == '') deviceSelect.value = 'desktop'; + deviceSelect.addEventListener('change', function() { + setOSK(); + ta1.focus(); + }); + } function setOSK() { + if(keyman.util.isTouchDevice()) { + return; + } + const devices = { Windows: { browser: 'chrome', formFactor: 'desktop', OS: 'windows', touchable: false, dimensions: [640, 300] }, macOS: { browser: 'chrome', formFactor: 'desktop', OS: 'macosx', touchable: false, dimensions: [640, 300] }, @@ -228,8 +235,10 @@ window.onload = function() { setOSK(); keyman.addEventListener('keyboardchange', function(keyboardProperties) { - keyman.osk = newOSK; - newOSK.activeKeyboard = keyman.core.activeKeyboard; + if(newOSK) { + keyman.osk = newOSK; + newOSK.activeKeyboard = keyman.core.activeKeyboard; + } keyboardSelect.value = keyboardProperties.internalName; keyman.alignInputs(); //console.log('keyboardchange:'+JSON.stringify(keyboardProperties)+' [active='+keyman.getActiveKeyboard()+';'+keyman.core.activeKeyboard+']'); diff --git a/windows/src/developer/developer.sln b/windows/src/developer/developer.sln index 5ec342e151..33dbf50512 100644 --- a/windows/src/developer/developer.sln +++ b/windows/src/developer/developer.sln @@ -11,6 +11,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmanalyze", "kmanalyze\kman EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IMSample", "samples\imsample\IMSample.vcxproj", "{0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kcframe", "kmcmpdll\kcframe\kcframe.vcxproj", "{E92CC897-8228-4728-8110-028088D7A99C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -47,6 +49,14 @@ Global {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}.Release|x64.ActiveCfg = Release|Win32 {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}.Release|x86.ActiveCfg = Release|Win32 {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}.Release|x86.Build.0 = Release|Win32 + {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x64.ActiveCfg = Debug|x64 + {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x64.Build.0 = Debug|x64 + {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x86.ActiveCfg = Debug|Win32 + {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x86.Build.0 = Debug|Win32 + {E92CC897-8228-4728-8110-028088D7A99C}.Release|x64.ActiveCfg = Release|x64 + {E92CC897-8228-4728-8110-028088D7A99C}.Release|x64.Build.0 = Release|x64 + {E92CC897-8228-4728-8110-028088D7A99C}.Release|x86.ActiveCfg = Release|Win32 + {E92CC897-8228-4728-8110-028088D7A99C}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/windows/src/developer/inst/kmdev.wxs b/windows/src/developer/inst/kmdev.wxs index 3160aa086a..ef3e1218bb 100644 --- a/windows/src/developer/inst/kmdev.wxs +++ b/windows/src/developer/inst/kmdev.wxs @@ -213,6 +213,10 @@ + + + + diff --git a/windows/src/developer/kmcmpdll/Compiler.cpp b/windows/src/developer/kmcmpdll/Compiler.cpp index b902be8b64..d1ed792a99 100644 --- a/windows/src/developer/kmcmpdll/Compiler.cpp +++ b/windows/src/developer/kmcmpdll/Compiler.cpp @@ -1220,6 +1220,7 @@ DWORD ProcessSystemStore(PFILE_KEYBOARD fk, DWORD SystemID, PFILE_STORE sp) else if (wcsncmp(p, L"9.0", 3) == 0) fk->version = VERSION_90; else if (wcsncmp(p, L"10.0", 4) == 0) fk->version = VERSION_100; else if (wcsncmp(p, L"14.0", 4) == 0) fk->version = VERSION_140; // Adds support for #917 -- context() with notany() for KeymanWeb + else if (wcsncmp(p, L"15.0", 4) == 0) fk->version = VERSION_150; // Adds support for U_xxxx_yyyy #2858 else return CERR_InvalidVersion; if (fk->version < VERSION_60) FOldCharPosMatching = TRUE; diff --git a/windows/src/developer/kmcmpdll/kcframe.cpp b/windows/src/developer/kmcmpdll/kcframe/kcframe.cpp similarity index 61% rename from windows/src/developer/kmcmpdll/kcframe.cpp rename to windows/src/developer/kmcmpdll/kcframe/kcframe.cpp index 2a704c9d43..d51c2fbc54 100644 --- a/windows/src/developer/kmcmpdll/kcframe.cpp +++ b/windows/src/developer/kmcmpdll/kcframe/kcframe.cpp @@ -1,30 +1,29 @@ /* Name: kcframe Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 24 Aug 2015 Modified Date: 24 Aug 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 24 Aug 2015 - mcdurdin - I4865 - Add treat hints and warnings as errors into project - + 24 Aug 2015 - mcdurdin - I4866 - Add warn on deprecated features to project and compile - + */ #include #include #include - -//extern "C" BOOL __declspec(dllexport) CompileKeyboardFile(PSTR pszInfile, PSTR pszOutfile, BOOL FSaveDebug, CompilerMessageProc pMsgProc) +#include int WINAPI msgproc(int line, DWORD dwMsgCode, LPSTR szText) { @@ -34,11 +33,19 @@ int WINAPI msgproc(int line, DWORD dwMsgCode, LPSTR szText) int main(int argc, char *argv[]) { + if (argc == 2 && strcmp(argv[1], "--sizeof") == 0) { + printf("FILE_KEYBOARD_SIZE = %d\n", sizeof(FILE_KEYBOARD)); + printf("FILE_GROUP_SIZE = %d\n", sizeof(FILE_GROUP)); + printf("FILE_STORE_SIZE = %d\n", sizeof(FILE_STORE)); + printf("FILE_KEY_SIZE = %d\n", sizeof(FILE_KEY)); + printf("FILE_DEADKEY_SIZE = %d\n", sizeof(FILE_DEADKEY)); + return 0; + } if(argc < 3) { puts("Usage: kcframe infile.kmn outfile.kmx"); return 1; } - return CompileKeyboardFile(argv[1], argv[2], TRUE, FALSE, TRUE, msgproc) ? 0 : 1; // I4865 // I4866 + return CompileKeyboardFile(argv[1], argv[2], TRUE, FALSE, TRUE, msgproc) ? 0 : 1; } diff --git a/windows/src/developer/kmcmpdll/kcframe.vcxproj b/windows/src/developer/kmcmpdll/kcframe/kcframe.vcxproj similarity index 94% rename from windows/src/developer/kmcmpdll/kcframe.vcxproj rename to windows/src/developer/kmcmpdll/kcframe/kcframe.vcxproj index eb02ce1ad5..e4b305d0a9 100644 --- a/windows/src/developer/kmcmpdll/kcframe.vcxproj +++ b/windows/src/developer/kmcmpdll/kcframe/kcframe.vcxproj @@ -88,20 +88,22 @@ - $(SolutionDir)$(Platform)\$(Configuration)\ + $(ProjectDir)..\bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ - $(SolutionDir)$(Platform)\$(Configuration)\ + $(ProjectDir)..\bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ $(ProjectName).x64 + $(ProjectDir)..\bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ $(ProjectName).x64 + $(ProjectDir)..\bin\$(Platform)\$(Configuration)\ @@ -112,7 +114,7 @@ MaxSpeed OnlyExplicitInline - .;..\..\global\inc;%(AdditionalIncludeDirectories) + .;..\..\..\global\inc;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true MultiThreaded @@ -147,7 +149,7 @@ MaxSpeed OnlyExplicitInline - .;..\..\global\inc;%(AdditionalIncludeDirectories) + .;..\..\..\global\inc;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true MultiThreaded @@ -180,7 +182,7 @@ Disabled - .;..\..\global\inc;%(AdditionalIncludeDirectories) + .;..\..\..\global\inc;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true EnableFastChecks @@ -216,7 +218,7 @@ Disabled - .;..\..\global\inc;%(AdditionalIncludeDirectories) + .;..\..\..\global\inc;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebug @@ -258,9 +260,8 @@ - + {7e26fe08-721e-424b-9da0-4a0dca88a86e} - false diff --git a/windows/src/developer/kmcmpdll/kcframe.vcxproj.filters b/windows/src/developer/kmcmpdll/kcframe/kcframe.vcxproj.filters similarity index 100% rename from windows/src/developer/kmcmpdll/kcframe.vcxproj.filters rename to windows/src/developer/kmcmpdll/kcframe/kcframe.vcxproj.filters diff --git a/windows/src/developer/kmcomp/kccompilepackage.pas b/windows/src/developer/kmcomp/kccompilepackage.pas index 3455f9165b..8bdbcd26c7 100644 --- a/windows/src/developer/kmcomp/kccompilepackage.pas +++ b/windows/src/developer/kmcomp/kccompilepackage.pas @@ -33,13 +33,14 @@ uses kpsfile; function DoKCCompilePackage(FileName: string; AWarnAsError, ACheckFilenameConventions, AInstaller: Boolean; - const AInstallerMSI: string; AUpdateInstaller: Boolean): Boolean; // I4706 + const AInstallerMSI: string; AUpdateInstaller: Boolean; const ASchemaPath: string): Boolean; // I4706 implementation uses Keyman.Developer.System.Project.ProjectLog, - Keyman.Developer.System.Project.ProjectLogConsole; + Keyman.Developer.System.Project.ProjectLogConsole, + Keyman.Developer.System.ValidateKpsFile; type TKCCompilePackage = class @@ -52,7 +53,8 @@ begin TProjectLogConsole.Instance.Log(State, pack.Filename, Msg, 0, 0); end; -function DoKCCompilePackage(FileName: string; AWarnAsError, ACheckFilenameConventions, AInstaller: Boolean; const AInstallerMSI: string; AUpdateInstaller: Boolean): Boolean; // I4706 +function DoKCCompilePackage(FileName: string; AWarnAsError, ACheckFilenameConventions, AInstaller: Boolean; + const AInstallerMSI: string; AUpdateInstaller: Boolean; const ASchemaPath: string): Boolean; // I4706 var tcp: TKCCompilePackage; pack: TKPSFile; @@ -68,6 +70,17 @@ begin TProjectLogConsole.Instance.Log(plsFatal, FileName, 'Package file does not exist', 0, 0); Exit; end; + + if (ASchemaPath <> '') and (FileExists(ASchemaPath + 'kps.xsd')) then + begin + if not TValidateKpsFile.Execute(FileName, ASchemaPath + 'kps.xsd', + TProjectLogConsole.Instance.Log) then + begin + TProjectLogConsole.Instance.Log(plsFailure, FileName, 'Package '+FileName+' had validation errors.', 0, 0); + Exit; + end; + end; + pack.FileName := FileName; pack.LoadXML; diff --git a/windows/src/developer/kmcomp/kmcomp.dpr b/windows/src/developer/kmcomp/kmcomp.dpr index b6fc244be8..f44e1ca399 100644 --- a/windows/src/developer/kmcomp/kmcomp.dpr +++ b/windows/src/developer/kmcomp/kmcomp.dpr @@ -121,7 +121,8 @@ uses Keyman.System.Standards.LangTagsRegistry in '..\..\global\delphi\standards\Keyman.System.Standards.LangTagsRegistry.pas', Keyman.Developer.System.Project.UrlRenderer in '..\TIKE\project\Keyman.Developer.System.Project.UrlRenderer.pas', Keyman.Developer.System.ValidateRepoChanges in 'Keyman.Developer.System.ValidateRepoChanges.pas', - Keyman.Developer.System.KeymanDeveloperPaths in '..\TIKE\main\Keyman.Developer.System.KeymanDeveloperPaths.pas'; + Keyman.Developer.System.KeymanDeveloperPaths in '..\TIKE\main\Keyman.Developer.System.KeymanDeveloperPaths.pas', + Keyman.Developer.System.ValidateKpsFile in '..\..\global\delphi\general\Keyman.Developer.System.ValidateKpsFile.pas'; {$R icons.RES} {$R version.res} diff --git a/windows/src/developer/kmcomp/kmcomp.dproj b/windows/src/developer/kmcomp/kmcomp.dproj index b01d7d3fd4..6c4bb206e4 100644 --- a/windows/src/developer/kmcomp/kmcomp.dproj +++ b/windows/src/developer/kmcomp/kmcomp.dproj @@ -110,7 +110,7 @@ RELEASE;$(DCC_Define) - -validate-repo-changes C:\Projects\keyman\keyboards compare-bcp47-scripts + c:\temp\foo.kps -schema-path C:\Projects\keyman\app\common\schemas\kps\ true 2 1033 @@ -277,6 +277,7 @@ + Cfg_2 Base @@ -348,13 +349,13 @@ true - + kmcomp.exe true - + kmcomp.exe true diff --git a/windows/src/developer/kmcomp/main.pas b/windows/src/developer/kmcomp/main.pas index a3a65d36be..003ecb772d 100644 --- a/windows/src/developer/kmcomp/main.pas +++ b/windows/src/developer/kmcomp/main.pas @@ -272,7 +272,7 @@ begin else if LowerCase(ExtractFileExt(FParamInfile)) = '.kpj' then // I4699 Ferror := not DoKCCompileProject(FParamInfile, FDebug, FClean, FWarnAsError, FCheckFilenameConventions, FParamTarget) // I4706 // I4707 else if LowerCase(ExtractFileExt(FParamInfile)) = '.kps' then - FError := not DoKCCompilePackage(FParamInfile, FWarnAsError, FInstaller, FCheckFilenameConventions, FInstallerMSI, FUpdateInstaller) // I4706 + FError := not DoKCCompilePackage(FParamInfile, FWarnAsError, FInstaller, FCheckFilenameConventions, FInstallerMSI, FUpdateInstaller, FJsonSchemaPath) // I4706 else FError := not CompileKeyboard(FParamInfile, FParamOutfile, FDebug, FWarnAsError); // I4706 diff --git a/windows/src/engine/keyman32/appint/aiTIP.cpp b/windows/src/engine/keyman32/appint/aiTIP.cpp index c14ceee360..9043c9ece1 100644 --- a/windows/src/engine/keyman32/appint/aiTIP.cpp +++ b/windows/src/engine/keyman32/appint/aiTIP.cpp @@ -95,9 +95,6 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPActivateEx(BOOL FActivate) { // } -/* - Update the Keyman toggle state based on the key event -*/ void ProcessToggleChange(UINT key) { // I4793 UINT flag = 0; @@ -129,6 +126,7 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPProcessKey(WPARAM wParam, LPARAM BOOL isUp = keyFlags & KF_UP ? TRUE : FALSE; BOOL extended = keyFlags & KF_EXTENDED ? TRUE : FALSE; BYTE scan = keyFlags & 0xFF; + BOOL isUsingCoreProcessor = Globals::get_CoreIntegration(); SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: Enter VirtualKey=%s lParam=%x IsUp=%d Extended=%d Updateable=%d Preserved=%d", Debug_VirtualKey((WORD) wParam), lParam, isUp, extended, Updateable, Preserved); @@ -161,44 +159,77 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPProcessKey(WPARAM wParam, LPARAM } DWORD LocalShiftState = Globals::get_ShiftState(); - - if(!Preserved) { - switch(wParam) { - case VK_CAPITAL: - if(!isUp) ProcessToggleChange((UINT) wParam); // I4793 - if (!Updateable) { - // We only want to process the Caps Lock key event once -- - // in the first pass (!Updateable). - KeyCapsLockPress(isUp); // I4548 + // Only the modifer flag 'f_ShiftState' is changed before sending the key stroke to the + // core processor. The core processor has the keyboard Caps Lock stores and will + // queue an action 'KM_KBP_IT_CAPSLOCK'. In processing the action the Windows engine will synthesise keystrokes + // to ensure caps lock is in the correct state. + if (isUsingCoreProcessor) { + if (!Preserved) { + switch (wParam) { + case VK_MENU: + case VK_CONTROL: + ProcessModifierChange((UINT)wParam, isUp, extended); + return FALSE; + case VK_NUMLOCK: + if (!isUp) + ProcessToggleChange((UINT)wParam); // I4793 + return FALSE; + case VK_CAPITAL: + if (!isUp) + ProcessToggleChange((UINT)wParam); // I4793 + break; + case VK_SHIFT: + ProcessModifierChange((UINT)wParam, isUp, extended); + break; } - return FALSE; - case VK_SHIFT: - if (!Updateable) { - // We only want to process the Shift key event once -- - // in the first pass (!Updateable). - KeyShiftPress(isUp); // I4548 + } else { + // Mask out Ctrl, Shift and Alt and include new modifiers // I4548 + DWORD NewShiftState = TSFShiftToShift(lParam); // I3588 + SendDebugMessageFormat( + 0, sdmGlobal, 0, "TIPProcessKey: TSFShiftToShift start with %x, include %x", LocalShiftState, NewShiftState); + *Globals::ShiftState() = (LocalShiftState & K_NOTMODIFIERFLAG) | NewShiftState; // I3588 + } + } else { // using windows processor TODO: #5442 Remove this else block + if (!Preserved) { + switch (wParam) { + case VK_CAPITAL: + if (!isUp) + ProcessToggleChange((UINT)wParam); // I4793 + if (!Updateable) { + // We only want to process the Caps Lock key event once -- + // in the first pass (!Updateable). + KeyCapsLockPress(isUp); // I4548 + } + return FALSE; + case VK_SHIFT: + if (!Updateable) { + // We only want to process the Shift key event once -- + // in the first pass (!Updateable). + KeyShiftPress(isUp); // I4548 + } + // Fall through + case VK_MENU: + case VK_CONTROL: + ProcessModifierChange((UINT)wParam, isUp, extended); + return FALSE; + case VK_NUMLOCK: + if (!isUp) + ProcessToggleChange((UINT)wParam); // I4793 + return FALSE; } - // Fall through - case VK_MENU: - case VK_CONTROL: - ProcessModifierChange((UINT) wParam, isUp, extended); - return FALSE; - - case VK_NUMLOCK: - if(!isUp) ProcessToggleChange((UINT) wParam); // I4793 - return FALSE; + // This would only get here if none of the above cases matched why not use default in the switch? + if (isUp) { + return FALSE; // return value ignored in this case; we only needed it for testing anyway + } + } else { + // Mask out Ctrl, Shift and Alt and include new modifiers // I4548 + DWORD NewShiftState = TSFShiftToShift(lParam); // I3588 + SendDebugMessageFormat( + 0, sdmGlobal, 0, "TIPProcessKey: TSFShiftToShift start with %x, include %x", LocalShiftState, NewShiftState); + *Globals::ShiftState() = (LocalShiftState & K_NOTMODIFIERFLAG) | NewShiftState; // I3588 } - if(isUp) { - return FALSE; // return value ignored in this case; we only needed it for testing anyway - } - } else { - // Mask out Ctrl, Shift and Alt and include new modifiers // I4548 - DWORD NewShiftState = TSFShiftToShift(lParam); // I3588 - SendDebugMessageFormat(0, sdmGlobal, 0, "TIPProcessKey: TSFShiftToShift start with %x, include %x", LocalShiftState, NewShiftState); - *Globals::ShiftState() = (LocalShiftState & K_NOTMODIFIERFLAG) | NewShiftState; // I3588 - } + } // TODO: #5442 Remove this else block ^^ - BOOL isUsingCoreProcessor = Globals::get_CoreIntegration(); _td->TIPFUpdateable = Updateable; _td->TIPFPreserved = Preserved; // I4290 @@ -206,6 +237,7 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPProcessKey(WPARAM wParam, LPARAM _td->state.msg.lParam = 0; _td->state.msg.message = wm_keymankeydown; _td->state.vkey = (WORD) wParam; + _td->state.isDown = !isUp; if (isUsingCoreProcessor) { _td->state.lpCoreKb = _td->lpActiveKeyboard->lpCoreKeyboard; diff --git a/windows/src/engine/keyman32/appint/aiTIP.h b/windows/src/engine/keyman32/appint/aiTIP.h index bb0c790d49..7ff5aff4ef 100644 --- a/windows/src/engine/keyman32/appint/aiTIP.h +++ b/windows/src/engine/keyman32/appint/aiTIP.h @@ -113,4 +113,12 @@ public: BOOL IsLegacy() { return useLegacy; } }; +/** + * ProcessToggleChange + * Toggles the state of FLAGS in the Globals::ShiftState bit mask + * Supports VK_CAPITAL and VK_NUMLOCK + * It DOES NOT generate a system event change for these flags + * @param key + */ +void ProcessToggleChange(UINT key); #endif diff --git a/windows/src/engine/keyman32/capsstate.h b/windows/src/engine/keyman32/capsstate.h index f86984de4c..aeb3e29000 100644 --- a/windows/src/engine/keyman32/capsstate.h +++ b/windows/src/engine/keyman32/capsstate.h @@ -1,24 +1,25 @@ /* Name: capsstate Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 11 Dec 2009 Modified Date: 11 Dec 2009 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 11 Dec 2009 - mcdurdin - I934 - x64 - Initial version */ #ifndef __CAPSSTATE_H #define __CAPSSTATE_H +BOOL IsCapsLockOn(void); void ResetCapsLock(void); void KeyCapsLockPress(BOOL FIsUp); void KeyShiftPress(BOOL FIsUp); diff --git a/windows/src/engine/keyman32/keyboardoptions.cpp b/windows/src/engine/keyman32/keyboardoptions.cpp index 1b5c26f2c8..6e272ad415 100644 --- a/windows/src/engine/keyman32/keyboardoptions.cpp +++ b/windows/src/engine/keyman32/keyboardoptions.cpp @@ -1,18 +1,18 @@ /* Name: keyboardoptions Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 25 May 2010 Modified Date: 28 Nov 2012 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 25 May 2010 - mcdurdin - I1632 - Keyboard Options 31 May 2010 - mcdurdin - I2397 - Assertion failure - keyboard options loading twice 28 Nov 2012 - mcdurdin - I3594 - V9.0 - Remove old SelectKeyboard code and related messages @@ -31,6 +31,12 @@ static km_kbp_cp* CloneKMKBPCP(const km_kbp_cp* cp) { return clone; } +static km_kbp_cp* CloneKMKBPCPFromWSTR(LPWSTR buf) { + km_kbp_cp* clone = new km_kbp_cp[wcslen(buf) + 1]; + wcscpy_s(reinterpret_cast(clone), wcslen(buf) + 1, buf); + return clone; +} + void LoadKeyboardOptions(LPINTKEYBOARDINFO kp) { // I3594 IntLoadKeyboardOptions(REGSZ_KeyboardOptions, kp); @@ -230,63 +236,55 @@ void LoadKeyboardOptionsREGCore(LPINTKEYBOARDINFO kp, km_kbp_state* const state) IntLoadKeyboardOptionsCore(REGSZ_KeyboardOptions, kp, state); } -BOOL IntLoadKeyboardOptionsCore(LPCSTR key, LPINTKEYBOARDINFO kp, km_kbp_state* state) +BOOL IntLoadKeyboardOptionsCore(LPCSTR key, LPINTKEYBOARDINFO kp, km_kbp_state* const state) { assert(key != NULL); assert(kp != NULL); - RegistryReadOnly r(HKEY_CURRENT_USER); - if (r.OpenKeyReadOnly(REGSZ_KeymanActiveKeyboards) && r.OpenKeyReadOnly(kp->Name) && r.OpenKeyReadOnly(key)) - { - WCHAR buf[256]; - int n = 0; - // Get the list of default options to determine size of list - const km_kbp_keyboard_attrs* keyboardAttrs; - km_kbp_status err_status = km_kbp_keyboard_get_attrs(kp->lpCoreKeyboard, &keyboardAttrs); - if (err_status != KM_KBP_STATUS_OK) { - SendDebugMessageFormat( - 0, sdmKeyboard, 0, "LoadKeyboardOptionsREGCore: km_kbp_keyboard_get_attrs failed with error status [%d]", err_status); - return FALSE; - } - size_t listSize = km_kbp_options_list_size(keyboardAttrs->default_options); - km_kbp_option_item* keyboardOpts = new km_kbp_option_item[listSize+1]; + // Get the list of default options to determine size of list + const km_kbp_keyboard_attrs* keyboardAttrs; + km_kbp_status err_status = km_kbp_keyboard_get_attrs(kp->lpCoreKeyboard, &keyboardAttrs); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmKeyboard, 0, "LoadKeyboardOptionsREGCore: km_kbp_keyboard_get_attrs failed with error status [%d]", err_status); + return FALSE; + } - while ((r.GetValueNames(buf, sizeof(buf) / sizeof(buf[0]), n)) && (n < (int)listSize)) - { - buf[255] = 0; - WCHAR val[256]; - - if (r.ReadString(buf, val, sizeof(val) / sizeof(val[0])) && val[0]) - { - val[255] = 0; - keyboardOpts[n].scope = KM_KBP_OPT_KEYBOARD; - - km_kbp_cp* cp = new km_kbp_cp[wcslen(buf) + 1]; - wcscpy_s(reinterpret_cast(cp), wcslen(buf) + 1, buf); - keyboardOpts[n].key = cp; - - cp = new km_kbp_cp[wcslen(val) + 1]; - wcscpy_s(reinterpret_cast(cp), wcslen(val) + 1, val); - keyboardOpts[n].value = cp; - } - n++; - } - - keyboardOpts[n] = KM_KBP_OPTIONS_END; - // once we have the option list we can then update the options using the public api call - err_status = km_kbp_state_options_update(state, keyboardOpts); - if (err_status != KM_KBP_STATUS_OK) { - SendDebugMessageFormat( - 0, sdmKeyboard, 0, "LoadKeyboardOptionsREGCore: km_kbp_state_options_update failed with error status [%d]", err_status); - } - for (int i = 0; i < (int)listSize + 1; i++) { - delete[] keyboardOpts[i].key; - delete[] keyboardOpts[i].value; - } - delete[] keyboardOpts; + size_t listSize = km_kbp_options_list_size(keyboardAttrs->default_options); + if (listSize == 0){ return TRUE; } - return FALSE; + km_kbp_option_item* keyboardOpts = new km_kbp_option_item[listSize + 1]; + + RegistryReadOnly r(HKEY_CURRENT_USER); + BOOL hasData = r.OpenKeyReadOnly(REGSZ_KeymanActiveKeyboards) && r.OpenKeyReadOnly(kp->Name) && r.OpenKeyReadOnly(key); + + int n = 0; + for (auto kpc = keyboardAttrs->default_options; kpc->key; kpc++) { + keyboardOpts[n].scope = KM_KBP_OPT_KEYBOARD; + keyboardOpts[n].key = kpc->key; + LPCWSTR coreKey = reinterpret_cast(kpc->key); + WCHAR val[256]; + if (hasData && r.ReadString(coreKey, val, sizeof(val) / sizeof(val[0])) && val[0]) { + keyboardOpts[n].value = CloneKMKBPCPFromWSTR(val); + } else { + keyboardOpts[n].value = CloneKMKBPCP(kpc->value); + } + n++; + } + keyboardOpts[n] = KM_KBP_OPTIONS_END; + + // once we have the option list we can then update the options using the public api call + err_status = km_kbp_state_options_update(state, keyboardOpts); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmKeyboard, 0, "LoadKeyboardOptionsREGCore: km_kbp_state_options_update failed with error status [%d]", err_status); + } + for (int i = 0; i < n; i++) { + delete[] keyboardOpts[i].value; + } + delete[] keyboardOpts; + return TRUE; } BOOL diff --git a/windows/src/engine/keyman32/kmprocess.cpp b/windows/src/engine/keyman32/kmprocess.cpp index 502779ca81..cefe5237e9 100644 --- a/windows/src/engine/keyman32/kmprocess.cpp +++ b/windows/src/engine/keyman32/kmprocess.cpp @@ -134,13 +134,13 @@ BOOL ProcessHook() keyinfo.VirtualKey = _td->state.vkey; keyinfo.Character = _td->state.charCode; keyinfo.DeadKeyCharacter = 0; // I4582 - keyinfo.IsUp = _td->state.msg.message == wm_keymankeyup; + keyinfo.IsUp = !_td->state.isDown; if(_td->app->IsUnicode()) _td->app->QueueDebugInformation(QID_BEGIN_UNICODE, NULL, NULL, NULL, NULL, (DWORD_PTR) &keyinfo); else _td->app->QueueDebugInformation(QID_BEGIN_ANSI, NULL, NULL, NULL, NULL, (DWORD_PTR) &keyinfo); } - + if (isUsingCoreProcessor) { SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessActions: "); @@ -154,10 +154,10 @@ BOOL ProcessHook() return FALSE; } km_kbp_context_items_dispose(citems); - //_td->state.vkey == VK_DOWN if (KM_KBP_STATUS_OK != - (km_kbp_status_codes)km_kbp_process_event( - _td->lpActiveKeyboard->lpCoreKeyboardState, _td->state.vkey, static_cast(Globals::get_ShiftState()), 1)) { + (km_kbp_status_codes)km_kbp_process_event(_td->lpActiveKeyboard->lpCoreKeyboardState, _td->state.vkey, + static_cast(Globals::get_ShiftState()), + (uint8_t)_td->state.isDown)) { return FALSE; } diff --git a/windows/src/engine/keyman32/kmprocessactions.cpp b/windows/src/engine/keyman32/kmprocessactions.cpp index 9f13bc4afb..914ca6f55f 100644 --- a/windows/src/engine/keyman32/kmprocessactions.cpp +++ b/windows/src/engine/keyman32/kmprocessactions.cpp @@ -77,6 +77,44 @@ static BOOL processInvalidateContext( return TRUE; } +static BOOL +processCapsLock(const km_kbp_action_item* actionItem, BOOL isUp, BOOL Updateable) { + + // We only want to process the Caps Lock key event once -- + // in the first pass (!Updateable). + if (Updateable){ + return TRUE; + } + + if (actionItem->capsLock) { + // This case would occur for the keyboard system store setting `store(&CapsOnOnly) '1'` + if (isUp && !IsCapsLockOn()) { // I267 - 24/11/2006 invert GetKeyState test + SendDebugMessageFormat(0, sdmGlobal, 0, "processCapsLock: TURN CAPS ON: FIsUp=%d CapsState=%d", isUp, IsCapsLockOn()); + keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, 0, 0); + keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, KEYEVENTF_KEYUP, 0); + } + + // This case would occur for the keyboard system store setting `store(&CapsAlwaysOff) '1'` + // A trick is being played here of synthesising a release the CAPSLOCK key event + // then a depress CAPSLOCK key event + else if (!isUp && IsCapsLockOn()) { // I267 - 24/11/2006 invert GetKeyState test + SendDebugMessageFormat(0, sdmGlobal, 0, "processCapsLock: TURN CAPS OFF: FIsUp=%d CapsState=%d", isUp, IsCapsLockOn()); + keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, KEYEVENTF_KEYUP, 0); + keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, 0, 0); + } + } + else { + // This case would occur for the keyboard system store setting `store(&ShiftFreesCaps) '1'` + if (!isUp && IsCapsLockOn()) { + SendDebugMessageFormat(0, sdmGlobal, 0, "processCapsLock: TURN CAPS OFF: FIsUp=%d CapsState=%d", isUp, IsCapsLockOn()); + keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, 0, 0); + keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, KEYEVENTF_KEYUP, 0); + } + } + + return TRUE; +} + BOOL ProcessActions(BOOL* emitKeyStroke) { PKEYMAN64THREADDATA _td = ThreadGlobals(); @@ -110,6 +148,9 @@ BOOL ProcessActions(BOOL* emitKeyStroke) case KM_KBP_IT_INVALIDATE_CONTEXT: continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState); break; + case KM_KBP_IT_CAPSLOCK: + continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable); + break; case KM_KBP_IT_END: // fallthrough default: diff --git a/windows/src/engine/keyman32/preservedkeymap.cpp b/windows/src/engine/keyman32/preservedkeymap.cpp index 677ba5e193..cd13a82f8c 100644 --- a/windows/src/engine/keyman32/preservedkeymap.cpp +++ b/windows/src/engine/keyman32/preservedkeymap.cpp @@ -38,11 +38,26 @@ class PreservedKeyMap { public: BOOL MapKeyboard(KEYBOARD *pKeyboard, PreservedKey **pPreservedKeys, size_t *cPreservedKeys); + /** + * Updates a map of preserved keys (with GUID) that are used in keyboard rules. + * Passing in NULL for pPreservedKeys will cause cPreservedKeys to be set to the number + * of preserved keys needed for the supplied pKeyboard; this should be used in creating + * pPreservedKeys list to sufficient size. When pPreservedKeys list is passed the + * cPreservedKeys will be the actual count of the number of unique pPreservedKeys + * + * @param pKeyboard the keyboard for which the rules will be extracted from + * @param pPreservedKeys preallocated array for preserved keys to be filled in, or nullptr to retrieve required size + * @param cPreservedKeys number of preserved keys in pPreservedKeys - or the size pPreservedKeys needs to be + * @return BOOL return TRUE on success + */ + BOOL MapKeyboardCore(km_kbp_keyboard *pKeyboard, PreservedKey **pPreservedKeys, size_t *cPreservedKeys); + private: BOOL m_BaseKeyboardUsesAltGr; // I4592 UINT ShiftToTSFShift(UINT ShiftFlags); BOOL MapUSCharToVK(UINT *puKey, UINT *puShiftFlags); BOOL MapKeyRule(KEY *pKey, TF_PRESERVEDKEY *pPreservedKey); + BOOL MapKeyRuleCore(km_kbp_keyboard_key *pKeyRule, TF_PRESERVEDKEY *pPreservedKey); BOOL IsMatchingKey(PreservedKey *pKey, PreservedKey *pKeys, size_t cKeys); }; @@ -221,6 +236,38 @@ BOOL PreservedKeyMap::MapKeyRule(KEY *pKey, TF_PRESERVEDKEY *pPreservedKey) return TRUE; } +BOOL +PreservedKeyMap::MapKeyRuleCore(km_kbp_keyboard_key *pKeyRule, TF_PRESERVEDKEY *pPreservedKey) { + UINT ShiftFlags; + UINT Key; + + Key = pKeyRule->key; + ShiftFlags = pKeyRule->modifier_flag; + + if (Key == VK_BACK || Key == VK_RETURN || Key == VK_TAB) // I4575 + { + // + // We never map backspace, return or tab because these are the only supported virtual key outputs, + // and result in recursion. Sadly, this is an imperfect solution forced upon us by preserved key + // limitations. + // + // Other virtual key output will be blocked with this version. + return FALSE; + } + + if (Key > 255) { + // + // Touch-defined keys have a value > 255, but these should never be preserved + // + return FALSE; + } + + pPreservedKey->uVKey = (UINT)USVKToScanCodeToLayoutVK((WORD)Key); // I3762 + pPreservedKey->uModifiers = ShiftToTSFShift(ShiftFlags); + + return TRUE; +} + BOOL PreservedKeyMap::IsMatchingKey(PreservedKey *pKey, PreservedKey *pKeys, size_t cKeys) { for(size_t i = 0; i < cKeys; i++) @@ -233,6 +280,7 @@ BOOL PreservedKeyMap::IsMatchingKey(PreservedKey *pKey, PreservedKey *pKeys, siz return FALSE; } +// TODO: 5442 - Remove once core processor verfied BOOL PreservedKeyMap::MapKeyboard(KEYBOARD *pKeyboard, PreservedKey **pPreservedKeys, size_t *cPreservedKeys) { size_t cKeys = 0, n; @@ -318,13 +366,114 @@ BOOL PreservedKeyMap::MapKeyboard(KEYBOARD *pKeyboard, PreservedKey **pPreserved return TRUE; } +BOOL +PreservedKeyMap::MapKeyboardCore(km_kbp_keyboard *pKeyboard, PreservedKey **pPreservedKeys, size_t *cPreservedKeys) { + size_t cKeys = 0, cRules = 0, n = 0; + DWORD i; + + m_BaseKeyboardUsesAltGr = KeyboardGivesCtrlRAltForRAlt(); // I4592 + + // This is not the same as m_BaseKeyboardUsesAltGr -- we are turning + // the simulation back on after (possibly) turning it off, for a + // consistent experience. TODO: determine if m_BaseKeyboardUseAltGr + // is still needed given we always use kbdus as base for Keyman 10+ + BOOL bSimulateAltGr = Globals::get_SimulateAltGr(); + + // We only want to translate RALT and RALT+SHIFT for Ctrl+Alt rules. + // So we exclude all our other favourite modifier keys. + const UINT RALT_MATCHING_MASK = TF_MOD_CONTROL | TF_MOD_ALT | TF_MOD_LCONTROL | TF_MOD_RCONTROL | TF_MOD_LALT | TF_MOD_RALT; + + // This is where we will call down to the api to get list of keys used in the keyboard rules + km_kbp_keyboard_key *kb_key_list; + + km_kbp_status err_status = km_kbp_keyboard_get_key_list(pKeyboard, &kb_key_list); + if ((err_status != KM_KBP_STATUS_OK) || (kb_key_list ==nullptr)) { + return FALSE; + } + + km_kbp_keyboard_key *key_rule_it = kb_key_list; + for (; key_rule_it->key; ++key_rule_it) { + ++cRules; + } + cKeys = cRules; + if (cKeys == 0) { + km_kbp_keyboard_key_list_dispose(kb_key_list); + return FALSE; + } + + if (bSimulateAltGr) { + // We might need twice as many preserved keys to map both LCtrl+LAlt+x and RAlt+x + cKeys *= 2; + } + + if (pPreservedKeys == NULL) { + *cPreservedKeys = cKeys; + km_kbp_keyboard_key_list_dispose(kb_key_list); + return TRUE; + } + + if (*cPreservedKeys < cKeys) { + km_kbp_keyboard_key_list_dispose(kb_key_list); + return FALSE; + } + + PreservedKey *pKeys = *pPreservedKeys; + + + for (i = 0; i < cRules; i++) { + // If we have a key rule for the key, we should preserve it + if (MapKeyRuleCore(&kb_key_list[i], &pKeys[n].key)) { + // Don't attempt to add the same preserved key twice. Bad things happen + if (!IsMatchingKey(&pKeys[n], pKeys, n)) { + CoCreateGuid(&pKeys[n].guid); + n++; + + if (bSimulateAltGr && (pKeys[n - 1].key.uModifiers & RALT_MATCHING_MASK) == TF_MOD_RALT) { + // Do this for RALT and RALT+SHIFT only, so we've tested against that mask + // Copy the key and fix modifiers + pKeys[n].key = pKeys[n - 1].key; + pKeys[n].key.uModifiers = (pKeys[n].key.uModifiers & ~TF_MOD_RALT) | TF_MOD_LCONTROL | TF_MOD_LALT; + CoCreateGuid(&pKeys[n].guid); + n++; + } + } + } + } + + km_kbp_keyboard_key_list_dispose(kb_key_list); + + *cPreservedKeys = n; // return actual count of allocated keys, usually smaller than allocated count + return TRUE; +} + + + + + extern "C" __declspec(dllexport) BOOL WINAPI GetKeyboardPreservedKeys(PreservedKey **pPreservedKeys, size_t *cPreservedKeys) { PreservedKeyMap pkm; PKEYMAN64THREADDATA _td = ThreadGlobals(); - if(!_td) return FALSE; - if(!_td->lpActiveKeyboard) return FALSE; - if(!_td->lpActiveKeyboard->Keyboard) return FALSE; + if (!_td) { + return FALSE; + } + if (!_td->lpActiveKeyboard) { + return FALSE; + } + // It could be an active core keyboard + if (Globals::get_CoreIntegration()) { + + if (!_td->lpActiveKeyboard->lpCoreKeyboard) { + return FALSE; + } + // use api to get key rules + return pkm.MapKeyboardCore(_td->lpActiveKeyboard->lpCoreKeyboard, pPreservedKeys, cPreservedKeys); + + } else { // TODO: 5442 Remove else + if (!_td->lpActiveKeyboard->Keyboard) { + return FALSE; + } + return pkm.MapKeyboard(_td->lpActiveKeyboard->Keyboard, pPreservedKeys, cPreservedKeys); + } - return pkm.MapKeyboard(_td->lpActiveKeyboard->Keyboard, pPreservedKeys, cPreservedKeys); } diff --git a/windows/src/global/delphi/comp/KeymanDeveloperDebuggerMemo.pas b/windows/src/global/delphi/comp/KeymanDeveloperDebuggerMemo.pas index b527493bf1..fa062e6862 100644 --- a/windows/src/global/delphi/comp/KeymanDeveloperDebuggerMemo.pas +++ b/windows/src/global/delphi/comp/KeymanDeveloperDebuggerMemo.pas @@ -23,6 +23,7 @@ uses System.Classes, Winapi.Messages, Winapi.Windows, + Vcl.Controls, Vcl.StdCtrls; type @@ -37,9 +38,11 @@ type private FOnMessage: TKeymanDeveloperDebuggerMessageEvent; FAllowUnicodeInput: Boolean; + FIsDebugging: Boolean; procedure SetAllowUnicode(const Value: Boolean); function GetSelection: TMemoSelection; procedure SetSelection(const Value: TMemoSelection); + procedure CNKeyDown(var Message: TWMKeyDown); message CN_KEYDOWN; protected procedure CreateHandle; override; procedure WndProc(var Message: TMessage); override; @@ -49,6 +52,7 @@ type property AllowUnicode: Boolean read FAllowUnicodeInput write SetAllowUnicode default True; property OnMessage: TKeymanDeveloperDebuggerMessageEvent read FOnMessage write FOnMessage; property Selection: TMemoSelection read GetSelection write SetSelection; + property IsDebugging: Boolean read FIsDebugging write FIsDebugging; end; procedure Register; @@ -57,6 +61,18 @@ implementation { TKeymanDeveloperDebuggerMemo } +procedure TKeymanDeveloperDebuggerMemo.CNKeyDown(var Message: TWMKeyDown); +begin + // Shortcuts for actions are handled with CN_KEYDOWN before we get WM_KEYDOWN, + // so if we are debugging we want to filter those out so that the debugger + // gets access to them instead of us. Note that this means that editor + // shortcuts such as Ctrl+A (select all) will not be processed in the normal + // way, so the host of this control needs to look after that. + if IsDebugging + then Exit + else inherited; +end; + constructor TKeymanDeveloperDebuggerMemo.Create(AOwner: TComponent); begin FAllowUnicodeInput := True; diff --git a/windows/src/global/delphi/comp/OnScreenKeyboard.pas b/windows/src/global/delphi/comp/OnScreenKeyboard.pas index 1a48b0d051..329ea0d6af 100644 --- a/windows/src/global/delphi/comp/OnScreenKeyboard.pas +++ b/windows/src/global/delphi/comp/OnScreenKeyboard.pas @@ -391,20 +391,27 @@ var minsz: Integer; i: Integer; n: Integer; + Canvas: TCanvas; + b: TBitmap; begin - minsz := MAXINT; - for i := 0 to FKeys.Count - 1 do - if FKeys[i].KeyType <> kktNormal then - begin - n := FKeys[i].CalcFontSize(Canvas); - if (n > 0) and (n < minsz) then - minsz := n; - end; + b := TBitmap.Create; + try + minsz := MAXINT; + for i := 0 to FKeys.Count - 1 do + if FKeys[i].KeyType <> kktNormal then + begin + n := FKeys[i].CalcFontSize(b.Canvas); + if (n > 0) and (n < minsz) then + minsz := n; + end; - if minsz = MAXINT then minsz := 0; - for i := 0 to FKeys.Count - 1 do - if FKeys[i].KeyType <> kktNormal then - FKeys[i].FontSize := minsz; + if minsz = MAXINT then minsz := 0; + for i := 0 to FKeys.Count - 1 do + if FKeys[i].KeyType <> kktNormal then + FKeys[i].FontSize := minsz; + finally + b.Free; + end; end; procedure TOnScreenKeyboard.CreateParams(var params: TCreateParams); @@ -852,13 +859,16 @@ begin ResizeKeys; - if FDisplay102Key or FEuroLayout then + if HandleAllocated then begin - r := Rect(k.FX, k.FY, F102Key.FX+F102Key.FW, F102Key.FY+F102Key.FH); - FillBkRect(Canvas.Handle, r); + if FDisplay102Key or FEuroLayout then + begin + r := Rect(k.FX, k.FY, F102Key.FX+F102Key.FW, F102Key.FY+F102Key.FH); + FillBkRect(Canvas.Handle, r); + end; + DoDrawKey(Canvas, k); + DoDrawKey(Canvas, F102Key); end; - DoDrawKey(Canvas, k); - DoDrawKey(Canvas, F102Key); end; procedure TOnScreenKeyboard.SetDisplayUnderlyingChar(const Value: Boolean); @@ -1028,7 +1038,8 @@ begin CalcKeyFontSizes; - InvalidateRect(Handle, nil, True); + if HandleAllocated then + InvalidateRect(Handle, nil, True); end; { TOnScreenKeyboardKeys } diff --git a/windows/src/global/delphi/general/Keyman.Developer.System.ValidateKpsFile.pas b/windows/src/global/delphi/general/Keyman.Developer.System.ValidateKpsFile.pas new file mode 100644 index 0000000000..991fe1249f --- /dev/null +++ b/windows/src/global/delphi/general/Keyman.Developer.System.ValidateKpsFile.pas @@ -0,0 +1,70 @@ +unit Keyman.Developer.System.ValidateKpsFile; + +interface + +uses + Keyman.Developer.System.Project.ProjectLog; + +type + TValidateKpsFile = class + private + FOnLogEvent: TProjectLogObjectEvent; + FKpsFile: string; + FKpsXsdPath: string; + constructor Create(const AKpsFile, AKpsXsdPath: string); + function Validate: Boolean; + property OnLogEvent: TProjectLogObjectEvent read FOnLogEvent write FOnLogEvent; + public + class function Execute(const KpsFile, KpsXsdPath: string; FCallback: TProjectLogObjectEvent): Boolean; + end; + +implementation + +uses + Winapi.MsXml; + +{ TValidateKpsFile } + +constructor TValidateKpsFile.Create(const AKpsFile, AKpsXsdPath: string); +begin + inherited Create; + FKpsFile := AKpsFile; + FKpsXsdPath := AKpsXsdPath; +end; + +class function TValidateKpsFile.Execute(const KpsFile, KpsXsdPath: string; + FCallback: TProjectLogObjectEvent): Boolean; +var + v: TValidateKpsFile; +begin + v := TValidateKpsFile.Create(KpsFile, KpsXsdPath); + try + v.OnLogEvent := FCallback; + Result := v.Validate; + finally + v.Free; + end; +end; + +function TValidateKpsFile.Validate: Boolean; +var + xml, xsd: IXMLDOMDocument2; + cache: IXMLDOMSchemaCollection; +begin + xsd := CoDOMDocument60.Create; + xsd.Async := False; + xsd.load(FKpsXsdPath); + + cache := CoXMLSchemaCache60.Create; + cache.add('', xsd); + + xml := CoDOMDocument60.Create; + xml.async := False; + xml.schemas := cache; + + Result := xml.load(FKpsFile); + if not Result then + FOnLogEvent(plsError, FKpsFile, 'Validation error: '+xml.parseError.reason, 1, xml.parseError.line); +end; + +end. diff --git a/windows/src/global/delphi/general/kmxfileconsts.pas b/windows/src/global/delphi/general/kmxfileconsts.pas index 83531d768d..064465db5d 100644 --- a/windows/src/global/delphi/general/kmxfileconsts.pas +++ b/windows/src/global/delphi/general/kmxfileconsts.pas @@ -97,9 +97,10 @@ const VERSION_90 = $00000900; VERSION_100 = $00000A00; VERSION_140 = $00000E00; + VERSION_150 = $00000F00; VERSION_MIN = VERSION_50; - VERSION_MAX = VERSION_140; + VERSION_MAX = VERSION_150; VERSION_MASK_MINOR = $00FF; VERSION_MASK_MAJOR = $FF00; diff --git a/windows/src/global/inc/Compiler.h b/windows/src/global/inc/Compiler.h index 291770780d..4a45e04a17 100644 --- a/windows/src/global/inc/Compiler.h +++ b/windows/src/global/inc/Compiler.h @@ -70,9 +70,10 @@ #define VERSION_90 0x00000900 #define VERSION_100 0x00000A00 #define VERSION_140 0x00000E00 +#define VERSION_150 0x00000F00 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_140 +#define VERSION_MAX VERSION_150 /* Special flag for WM_CHAR/WM_KEY???/WM_SYSKEY???: says that key has been diff --git a/windows/src/global/inc/keymanengine.h b/windows/src/global/inc/keymanengine.h index 574162049f..364fdc4b6f 100644 --- a/windows/src/global/inc/keymanengine.h +++ b/windows/src/global/inc/keymanengine.h @@ -110,6 +110,7 @@ typedef struct tagKMSTATE WORD vkey; // I934 WCHAR charCode; // I4582 BOOL windowunicode; // I4287 + BOOL isDown; LPKEYBOARD lpkb; km_kbp_keyboard* lpCoreKb; // future use with IMDLL LPGROUP startgroup; // TODO: 5442 will remove this once windows core is deprecated diff --git a/windows/src/test/manual-tests/caps-lock-stores/TestReadme.md b/windows/src/test/manual-tests/caps-lock-stores/TestReadme.md new file mode 100644 index 0000000000..1f231cbe63 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/TestReadme.md @@ -0,0 +1,169 @@ +## Caps Lock +The test cases below expect the usage of the capslock.kmn keyboard in this directory. That keyboard outputs _pass_ or _fail_ if following the test cases. + +### Prerequisites before each test + +- System keyboard layout is **en-US** +- Install a keyboard that doesn't use any of the caps lock stores, e.g. `capslock.kmp`. +- CapsLock is currently **on** +- Currently active keyboard is the **capslock.kmp** keyboard + +### Test cases + +- **TEST_CAPSLOCK-1**: uppercase with virtual key + - press and release `a` + + Expected result: + - `pass.` (with other keyboards uppercase `A`) + +- **TEST_CAPSLOCK-2**: lowercase with virtual key + - press and hold 'Shift' + - press and release `b` + - release `Shift` + + Expected result: + - `pass.` (with other keyboards lowercase `b`) + +- **TEST_CAPSLOCK-3**: capslock ignored for numbers + - press and hold 'Shift' + - press and release `3` + - release `Shift` + + Expected result: + - `pass.` (with other keyboards `#`) + +- **TEST_CAPSLOCK-4**: uppercase + - press and release `c` + + Expected result: + - `pass.` (with other keyboards uppercase `C`) + +- **TEST_CAPSLOCK-5**: lowercase + - press and hold 'Shift' + - press and release `d` + - release `Shift` + + Expected result: + - `pass.` (with other keyboards lowercase `d`) + +## CapsAlwaysOff + +For these tests, use a keyboard with the `CapsAlwaysOff` store set. We call this keyboard _capsalwaysoff_ below. + +Any keyboard with that store set will work; if you don't have one at hand you can use the caps_always_off.kmn keyboard. The *caps_always_off.kmp* keyboard will prevent switching caps lock on. As a sanity check to verify that Keyman is actually active, pressing the key `a` will output `ncaps_little_a`, and `Shift+a` will output `ncaps_shift_A`. + +**Note:** When testing in a virtual machine, use an on-screen keyboard (in VirtualBox: Input/Keyboard/Soft Keyboard) and observe the caps lock indicator of the on-screen keyboard. Using the hardware keyboard might show side effects with caps lock. + +### Prerequisites before each test + +- Install a keyboard that has `CapsAlwaysOff` store set, e.g. `caps_always_off.kmp`. +- CapsLock is currently off +- Currently active keyboard is a non-Keyman keyboard + +### Test cases + +- **TEST_CAPSOFF-1**: sanity check + - switch to capsalwaysoff keyboard + - press and release `a` + + Expected result: + - output: `ncaps_little_a` + +- **TEST_CAPSOFF-2**: caps lock stays off + - switch to capsalwaysoff keyboard + - press and release `CapsLock` key + - press and release `a` + + Expected result: + - caps lock indicator stays turned off + - output: `ncaps_little_a` + +- **TEST_CAPSOFF-3**: no caps lock while holding capslock key + - switch to capsalwaysoff keyboard + - press and hold `CapsLock` key + - press and release `a` + - release `CapsLock` key + + Expected result: + - output: `ncaps_little_a` + +- **TEST_CAPSOFF-4**: no caps lock while holding capslock key + - switch to capsalwaysoff keyboard + - press and hold `CapsLock` key + - press and hold `Shift` key + - press and release `a` + - release `CapsLock` and `Shift` keys + + Expected result: + - output: `ncaps_shift_A` + +- **TEST_CAPSOFF-5**: switching turns off caps lock + - turn on caps lock + - switch to capsalwaysoff keyboard + - press and release `a` + + Expected result: + - caps lock indicator turned off + - output: `ncaps_little_a` + +**Note:** For `Test caps off 5` using a soft keyboard provided in virtual box will fail to turn the cap lock indicator off. This because the soft keyboard does pre processing of the shift key combined with any key pressed and the shift key stroke does not get sent to the keyman engine. + +## CapsOnOnly/ShiftFreesCaps + +For these tests, use a keyboard with the `CapsOnOnly` and `ShiftFreesCaps` stores set. We call this keyboard _shift_frees_caps_ below. + +Any keyboard with these stores set will work; if you don't have one at hand you can use the shift_frees_caps.kmp + +The _shift_frees_caps.kmp_ keyboard will enable caps lock by pressing the `CapsLock` key, and will turn capslock off by pressing the `Shift` key. The keyboard outputs _pass_ or _fail_ if following the test cases. + +**Note:** When testing in a virtual machine, use an on-screen keyboard (in VirtualBox: Input/Keyboard/Soft Keyboard) and observe the caps lock indicator of the on-screen keyboard. Using the hardware keyboard might show side effects with caps lock. + +### Prerequisites before each test + +- Install a keyboard that has the `CapsOnOnly` and `ShiftFreesCaps` stores set, e.g. + `shift_frees_caps.kmp`. +- CapsLock is currently off +- Currently active keyboard is _shift_frees_caps_ keyboard + +### Test cases + +- **TEST_CAPSONLY-1**: no caps + - press and release `1` + + Expected result: + - output: `pass.` + +- **TEST_CAPSONLY-2**: caps + - press and release `CapsLock` + - press and release `2` + + Expected result: + - caps lock indicator turned on + - output: `pass.` + +- **TEST_CAPSONLY-3**: caps doesn't toggle + - press and release `CapsLock` + - press and release `CapsLock` + - press and release `6` + + Expected result: + - caps lock indicator turned on + - output: `pass.` + +- **TEST_CAPSONLY-4**: shift turns off + - press and release `CapsLock` + - press and hold `Shift` + - press and release `3` + - release `Shift` + + Expected result: + - caps lock indicator turned off + - output: `pass.` + +- **TEST_CAPSONLY-5**: shift by itself turns off + - press and release `CapsLock` + - press and release `Shift` + + Expected result: + - caps lock indicator turned off + - (no output) diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/HISTORY.md b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/HISTORY.md new file mode 100644 index 0000000000..a3aacb3e43 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/HISTORY.md @@ -0,0 +1,6 @@ +Caps Always Off Change History +==================== + +1.0 (2021-07-20) +---------------- +* Created by diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/LICENSE.md b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/LICENSE.md new file mode 100644 index 0000000000..58fdf630c6 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (C) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/README.md b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/README.md new file mode 100644 index 0000000000..65f82ca309 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/README.md @@ -0,0 +1,24 @@ +Caps Always Off keyboard +============== + +Copyright (C) + +Version 1.0 + +Description +----------- + +Caps Always Off generated from template + +Links +----- + +Supported Platforms +------------------- + * macOS + * iPhone + * Android tablet + * Mobile devices + * Desktop devices + * Tablet devices + diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.keyboard_info b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.keyboard_info new file mode 100644 index 0000000000..bb9f739f70 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.keyboard_info @@ -0,0 +1,7 @@ +{ + "license": "mit", + "languages": [ + "en" + ], + "description": "Caps Always Off generated from template" +} diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.kpj b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.kpj new file mode 100644 index 0000000000..095264252b --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.kpj @@ -0,0 +1,102 @@ + + + + $PROJECTPATH\build + true + true + true + keyboard + + + + id_f15f3b8db9e0698449971d5937637713 + caps_always_off.kmn + source\caps_always_off.kmn + 1.0 + .kmn +
+ Caps Always Off + Copyright (C) +
+
+ + id_750e2505c1572a7c9afd11a3ff351848 + caps_always_off.kps + source\caps_always_off.kps + + .kps +
+ Caps Always Off + Copyright (C) +
+
+ + id_a2153a6538d083540d28614f6591d09e + caps_always_off.kmx + source\..\build\caps_always_off.kmx + + .kmx + id_750e2505c1572a7c9afd11a3ff351848 + + + id_8a1a4e692f610dbc49f604bef7c7e42d + caps_always_off.js + source\..\build\caps_always_off.js + + .js + id_750e2505c1572a7c9afd11a3ff351848 + + + id_8be1d798612b2ef55188d01b971cd8b0 + caps_always_off.kvk + source\..\build\caps_always_off.kvk + + .kvk + id_750e2505c1572a7c9afd11a3ff351848 + + + id_356e5d149c1e539356d72698c1e401a6 + welcome.htm + source\welcome.htm + + .htm + id_750e2505c1572a7c9afd11a3ff351848 + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_750e2505c1572a7c9afd11a3ff351848 + + + id_ede98e4633e239f933cbfd1f4e1b766c + HISTORY.md + HISTORY.md + + .md + + + id_53e892b8b41cc4caece1cfd5ef21d6e7 + LICENSE.md + LICENSE.md + + .md + + + id_0730bb7c2e8f9ea2438b52e419dd86c9 + README.md + README.md + + .md + + + id_60f11cd4dd88c1ad232f0099b86b071f + caps_always_off.keyboard_info + caps_always_off.keyboard_info + + .keyboard_info + +
+
diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.keyman-touch-layout b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.keyman-touch-layout new file mode 100644 index 0000000000..149069b062 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.keyman-touch-layout @@ -0,0 +1,1316 @@ +{ + "tablet": { + "font": "Tahoma", + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "q" + }, + { + "id": "K_W", + "text": "w" + }, + { + "id": "K_E", + "text": "e" + }, + { + "id": "K_R", + "text": "r" + }, + { + "id": "K_T", + "text": "t" + }, + { + "id": "K_Y", + "text": "y" + }, + { + "id": "K_U", + "text": "u" + }, + { + "id": "K_I", + "text": "i" + }, + { + "id": "K_O", + "text": "o" + }, + { + "id": "K_P", + "text": "p" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "a", + "pad": 70 + }, + { + "id": "K_S", + "text": "s" + }, + { + "id": "K_D", + "text": "d" + }, + { + "id": "K_F", + "text": "f" + }, + { + "id": "K_G", + "text": "g" + }, + { + "id": "K_H", + "text": "h" + }, + { + "id": "K_J", + "text": "j" + }, + { + "id": "K_K", + "text": "k" + }, + { + "id": "K_L", + "text": "l" + }, + { + "sp": "10", + "width": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_Z", + "text": "z" + }, + { + "id": "K_X", + "text": "x" + }, + { + "id": "K_C", + "text": "c" + }, + { + "id": "K_V", + "text": "v" + }, + { + "id": "K_B", + "text": "b" + }, + { + "id": "K_N", + "text": "n" + }, + { + "id": "K_M", + "text": "m" + }, + { + "id": "K_PERIOD", + "text": ".", + "sk": [ + { + "text": ",", + "id": "K_COMMA" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "Q" + }, + { + "id": "K_W", + "text": "W" + }, + { + "id": "K_E", + "text": "E" + }, + { + "id": "K_R", + "text": "R" + }, + { + "id": "K_T", + "text": "T" + }, + { + "id": "K_Y", + "text": "Y" + }, + { + "id": "K_U", + "text": "U" + }, + { + "id": "K_I", + "text": "I" + }, + { + "id": "K_O", + "text": "O" + }, + { + "id": "K_P", + "text": "P" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "A", + "pad": 70 + }, + { + "id": "K_S", + "text": "S" + }, + { + "id": "K_D", + "text": "D" + }, + { + "id": "K_F", + "text": "F" + }, + { + "id": "K_G", + "text": "G" + }, + { + "id": "K_H", + "text": "H" + }, + { + "id": "K_J", + "text": "J" + }, + { + "id": "K_K", + "text": "K" + }, + { + "id": "K_L", + "text": "L" + }, + { + "sp": "10", + "width": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_Z", + "text": "Z" + }, + { + "id": "K_X", + "text": "X" + }, + { + "id": "K_C", + "text": "C" + }, + { + "id": "K_V", + "text": "V" + }, + { + "id": "K_B", + "text": "B" + }, + { + "id": "K_N", + "text": "N" + }, + { + "id": "K_M", + "text": "M" + }, + { + "id": "K_PERIOD", + "text": ".", + "layer": "default", + "sk": [ + { + "text": ",", + "id": "K_COMMA", + "layer": "default" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE", + "layer": "default" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH", + "layer": "default" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON", + "layer": "default" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "1" + }, + { + "id": "K_2", + "text": "2" + }, + { + "id": "K_3", + "text": "3" + }, + { + "id": "K_4", + "text": "4" + }, + { + "id": "K_5", + "text": "5" + }, + { + "id": "K_6", + "text": "6" + }, + { + "id": "K_7", + "text": "7" + }, + { + "id": "K_8", + "text": "8" + }, + { + "id": "K_9", + "text": "9" + }, + { + "id": "K_0", + "text": "0" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_4", + "text": "$", + "layer": "shift", + "pad": 70 + }, + { + "id": "K_2", + "text": "@", + "layer": "shift" + }, + { + "id": "K_3", + "text": "#", + "layer": "shift" + }, + { + "id": "K_5", + "text": "%", + "layer": "shift" + }, + { + "id": "K_7", + "text": "&", + "layer": "shift" + }, + { + "id": "K_HYPHEN", + "text": "_", + "layer": "shift" + }, + { + "id": "K_EQUAL", + "text": "=", + "layer": "default" + }, + { + "id": "K_BKSLASH", + "text": "|", + "layer": "shift" + }, + { + "id": "K_BKSLASH", + "text": "\\", + "layer": "default" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "1" + }, + { + "id": "K_LBRKT", + "text": "[", + "sk": [ + { + "id": "U_00AB", + "text": "\u00AB" + }, + { + "id": "K_COMMA", + "text": "<", + "layer": "shift" + }, + { + "id": "K_LBRKT", + "text": "{", + "layer": "shift" + } + ] + }, + { + "id": "K_9", + "text": "(", + "layer": "shift" + }, + { + "id": "K_0", + "text": ")", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "]", + "sk": [ + { + "id": "U_00BB", + "text": "\u00BB" + }, + { + "id": "K_PERIOD", + "text": ">", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "}", + "layer": "shift" + } + ] + }, + { + "id": "K_EQUAL", + "text": "+", + "layer": "shift" + }, + { + "id": "K_HYPHEN", + "text": "-", + "layer": "default" + }, + { + "id": "K_8", + "text": "*", + "layer": "shift" + }, + { + "id": "K_SLASH", + "text": "/", + "layer": "default" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LOWER", + "text": "*abc*", + "width": "140", + "sp": "1", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + } + ] + }, + "phone": { + "font": "Tahoma", + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "q" + }, + { + "id": "K_W", + "text": "w" + }, + { + "id": "K_E", + "text": "e" + }, + { + "id": "K_R", + "text": "r" + }, + { + "id": "K_T", + "text": "t" + }, + { + "id": "K_Y", + "text": "y" + }, + { + "id": "K_U", + "text": "u" + }, + { + "id": "K_I", + "text": "i" + }, + { + "id": "K_O", + "text": "o" + }, + { + "id": "K_P", + "text": "p" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "a", + "pad": "50" + }, + { + "id": "K_S", + "text": "s" + }, + { + "id": "K_D", + "text": "d" + }, + { + "id": "K_F", + "text": "f" + }, + { + "id": "K_G", + "text": "g" + }, + { + "id": "K_H", + "text": "h" + }, + { + "id": "K_J", + "text": "j" + }, + { + "id": "K_K", + "text": "k" + }, + { + "id": "K_L", + "text": "l" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_Z", + "text": "z" + }, + { + "id": "K_X", + "text": "x" + }, + { + "id": "K_C", + "text": "c" + }, + { + "id": "K_V", + "text": "v" + }, + { + "id": "K_B", + "text": "b" + }, + { + "id": "K_N", + "text": "n" + }, + { + "id": "K_M", + "text": "m" + }, + { + "id": "K_PERIOD", + "text": ".", + "sk": [ + { + "text": ",", + "id": "K_COMMA" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "150", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "Q" + }, + { + "id": "K_W", + "text": "W" + }, + { + "id": "K_E", + "text": "E" + }, + { + "id": "K_R", + "text": "R" + }, + { + "id": "K_T", + "text": "T" + }, + { + "id": "K_Y", + "text": "Y" + }, + { + "id": "K_U", + "text": "U" + }, + { + "id": "K_I", + "text": "I" + }, + { + "id": "K_O", + "text": "O" + }, + { + "id": "K_P", + "text": "P" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "A", + "pad": "50" + }, + { + "id": "K_S", + "text": "S" + }, + { + "id": "K_D", + "text": "D" + }, + { + "id": "K_F", + "text": "F" + }, + { + "id": "K_G", + "text": "G" + }, + { + "id": "K_H", + "text": "H" + }, + { + "id": "K_J", + "text": "J" + }, + { + "id": "K_K", + "text": "K" + }, + { + "id": "K_L", + "text": "L" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_Z", + "text": "Z" + }, + { + "id": "K_X", + "text": "X" + }, + { + "id": "K_C", + "text": "C" + }, + { + "id": "K_V", + "text": "V" + }, + { + "id": "K_B", + "text": "B" + }, + { + "id": "K_N", + "text": "N" + }, + { + "id": "K_M", + "text": "M" + }, + { + "id": "K_PERIOD", + "text": ".", + "layer": "default", + "sk": [ + { + "text": ",", + "id": "K_COMMA", + "layer": "default" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE", + "layer": "default" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH", + "layer": "default" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON", + "layer": "default" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "150", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "1" + }, + { + "id": "K_2", + "text": "2" + }, + { + "id": "K_3", + "text": "3" + }, + { + "id": "K_4", + "text": "4" + }, + { + "id": "K_5", + "text": "5" + }, + { + "id": "K_6", + "text": "6" + }, + { + "id": "K_7", + "text": "7" + }, + { + "id": "K_8", + "text": "8" + }, + { + "id": "K_9", + "text": "9" + }, + { + "id": "K_0", + "text": "0" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_4", + "layer": "shift", + "text": "$", + "pad": "50" + }, + { + "id": "K_2", + "layer": "shift", + "text": "@" + }, + { + "id": "K_3", + "layer": "shift", + "text": "#" + }, + { + "id": "K_5", + "layer": "shift", + "text": "%" + }, + { + "id": "K_6", + "layer": "shift", + "text": "&" + }, + { + "id": "K_HYPHEN", + "layer": "shift", + "text": "_" + }, + { + "id": "K_EQUAL", + "text": "=", + "layer": "default" + }, + { + "id": "K_BKSLASH", + "layer": "shift", + "text": "|" + }, + { + "id": "K_BKSLASH", + "text": "\\", + "layer": "default" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_LBRKT", + "text": "[", + "pad": "110", + "sk": [ + { + "id": "U_00AB", + "text": "\u00AB" + }, + { + "id": "K_COMMA", + "text": "<", + "layer": "shift" + }, + { + "id": "K_LBRKT", + "text": "{", + "layer": "shift" + } + ] + }, + { + "id": "K_9", + "layer": "shift", + "text": "(" + }, + { + "id": "K_0", + "layer": "shift", + "text": ")" + }, + { + "id": "K_RBRKT", + "text": "]", + "sk": [ + { + "id": "U_00BB", + "text": "\u00BB" + }, + { + "id": "K_PERIOD", + "text": ">", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "}", + "layer": "shift" + } + ] + }, + { + "id": "K_EQUAL", + "layer": "shift", + "text": "+" + }, + { + "id": "K_HYPHEN", + "text": "-" + }, + { + "id": "K_8", + "layer": "shift", + "text": "*" + }, + { + "id": "K_SLASH", + "text": "/" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LOWER", + "text": "*abc*", + "width": "150", + "sp": "1", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + } + ] + } +} diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kmn b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kmn new file mode 100644 index 0000000000..ed9560a8af --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kmn @@ -0,0 +1,20 @@ +c Caps Lock cannot be turned on while the keyboard is active, and it turns Caps Lock off when the keyboard is switched on. +c caps_always_off generated from template at 2021-07-20 17:34:24 +c with name "Caps Always Off" +store(&NAME) 'Caps Always Off' +store(©RIGHT) '© SIL International' +store(&VERSION) '10.0' +store(&KEYBOARDVERSION) '1.0' +store(&TARGETS) 'macosx iphone androidtablet mobile desktop tablet' +store(&VISUALKEYBOARD) 'caps_always_off.kvks' +store(&LAYOUTFILE) 'caps_always_off.keyman-touch-layout' +store(&MESSAGE) 'Caps Lock cannot be turned on while the keyboard is active, and it turns Caps Lock off when the keyboard is switched on.' +store(&capsAlwaysOff) "1" + +begin Unicode > use(main) + +group(main) using keys ++ [NCAPS K_A] > 'ncaps_little_a ' ++ [NCAPS SHIFT K_A] > 'ncaps_shift_A ' ++ [CAPS K_A] > 'CAPS_A ' ++ [CAPS SHIFT K_A] > 'CAPS_shift_a ' diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kps b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kps new file mode 100644 index 0000000000..2a3b673b86 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kps @@ -0,0 +1,67 @@ + + + + 14.0.236.0 + 7.0 + + + + readme.htm + + + + + + + + + + Caps Always Off + Copyright (C) + + + + + + ..\build\caps_always_off.kmx + + 0 + .kmx + + + ..\build\caps_always_off.js + + 0 + .js + + + ..\build\caps_always_off.kvk + + 0 + .kvk + + + welcome.htm + + 0 + .htm + + + readme.htm + + 0 + .htm + + + + + Caps Always Off + caps_always_off + 1.0 + + English + + + + + diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kvks b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kvks new file mode 100644 index 0000000000..4df2ad2c0b --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/caps_always_off.kvks @@ -0,0 +1,8 @@ + + +
+ 10.0 + caps_always_off + +
+
diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/readme.htm b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/readme.htm new file mode 100644 index 0000000000..10b37bacf4 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/readme.htm @@ -0,0 +1,24 @@ + + + + + + Caps Always Off + + + + +

Caps Always Off

+ +

+ Caps Always Off 1.0 generated from template. +

+ +

Copyright (C)

+ + + diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/welcome.htm b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/welcome.htm new file mode 100644 index 0000000000..0a449178f2 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/source/welcome.htm @@ -0,0 +1,28 @@ + + + + + + Start Using Caps Always Off + + + + +

Start Using Caps Always Off

+ +

+ Caps Always Off 1.0 generated from template. +

+ +

Keyboard Layout

+ + + +

Copyright (C)

+ + + \ No newline at end of file diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/HISTORY.md b/windows/src/test/manual-tests/caps-lock-stores/capslock/HISTORY.md new file mode 100644 index 0000000000..6333f95165 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/HISTORY.md @@ -0,0 +1,6 @@ +Caps Lock Change History +==================== + +1.0 (2021-07-27) +---------------- +* Created by diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/LICENSE.md b/windows/src/test/manual-tests/caps-lock-stores/capslock/LICENSE.md new file mode 100644 index 0000000000..58fdf630c6 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (C) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/README.md b/windows/src/test/manual-tests/caps-lock-stores/capslock/README.md new file mode 100644 index 0000000000..05061e32fa --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/README.md @@ -0,0 +1,24 @@ +Caps Lock keyboard +============== + +Copyright (C) + +Version 1.0 + +Description +----------- + +Caps Lock generated from template + +Links +----- + +Supported Platforms +------------------- + * macOS + * iPhone + * Android tablet + * Mobile devices + * Desktop devices + * Tablet devices + diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.keyboard_info b/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.keyboard_info new file mode 100644 index 0000000000..b05d23ac13 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.keyboard_info @@ -0,0 +1,7 @@ +{ + "license": "mit", + "languages": [ + "en" + ], + "description": "Caps Lock generated from template" +} diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.kpj b/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.kpj new file mode 100644 index 0000000000..34c03b5ce4 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.kpj @@ -0,0 +1,102 @@ + + + + $PROJECTPATH\build + True + True + True + keyboard + + + + id_d3943885834b659380353456119cfe8a + capslock.kmn + source\capslock.kmn + 1.0 + .kmn +
+ Caps Lock + Copyright (C) +
+
+ + id_d397d2f3123cc43db71dba35a5fd95a7 + capslock.kps + source\capslock.kps + + .kps +
+ Caps Lock + Copyright (C) +
+
+ + id_ede98e4633e239f933cbfd1f4e1b766c + HISTORY.md + HISTORY.md + + .md + + + id_53e892b8b41cc4caece1cfd5ef21d6e7 + LICENSE.md + LICENSE.md + + .md + + + id_0730bb7c2e8f9ea2438b52e419dd86c9 + README.md + README.md + + .md + + + id_621d2fc48f94f0e3862c706c37ff7a8b + capslock.keyboard_info + capslock.keyboard_info + + .keyboard_info + + + id_894c97d2a53dc029f8ca18fd3e0c2ff0 + capslock.kmx + source\..\build\capslock.kmx + + .kmx + id_d397d2f3123cc43db71dba35a5fd95a7 + + + id_7801536673087ceb05e15ca4bf96b691 + capslock.js + source\..\build\capslock.js + + .js + id_d397d2f3123cc43db71dba35a5fd95a7 + + + id_68e444d3620281e9e3531745eeb9fc4e + capslock.kvk + source\..\build\capslock.kvk + + .kvk + id_d397d2f3123cc43db71dba35a5fd95a7 + + + id_356e5d149c1e539356d72698c1e401a6 + welcome.htm + source\welcome.htm + + .htm + id_d397d2f3123cc43db71dba35a5fd95a7 + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_d397d2f3123cc43db71dba35a5fd95a7 + +
+
diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.keyman-touch-layout b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.keyman-touch-layout new file mode 100644 index 0000000000..149069b062 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.keyman-touch-layout @@ -0,0 +1,1316 @@ +{ + "tablet": { + "font": "Tahoma", + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "q" + }, + { + "id": "K_W", + "text": "w" + }, + { + "id": "K_E", + "text": "e" + }, + { + "id": "K_R", + "text": "r" + }, + { + "id": "K_T", + "text": "t" + }, + { + "id": "K_Y", + "text": "y" + }, + { + "id": "K_U", + "text": "u" + }, + { + "id": "K_I", + "text": "i" + }, + { + "id": "K_O", + "text": "o" + }, + { + "id": "K_P", + "text": "p" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "a", + "pad": 70 + }, + { + "id": "K_S", + "text": "s" + }, + { + "id": "K_D", + "text": "d" + }, + { + "id": "K_F", + "text": "f" + }, + { + "id": "K_G", + "text": "g" + }, + { + "id": "K_H", + "text": "h" + }, + { + "id": "K_J", + "text": "j" + }, + { + "id": "K_K", + "text": "k" + }, + { + "id": "K_L", + "text": "l" + }, + { + "sp": "10", + "width": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_Z", + "text": "z" + }, + { + "id": "K_X", + "text": "x" + }, + { + "id": "K_C", + "text": "c" + }, + { + "id": "K_V", + "text": "v" + }, + { + "id": "K_B", + "text": "b" + }, + { + "id": "K_N", + "text": "n" + }, + { + "id": "K_M", + "text": "m" + }, + { + "id": "K_PERIOD", + "text": ".", + "sk": [ + { + "text": ",", + "id": "K_COMMA" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "Q" + }, + { + "id": "K_W", + "text": "W" + }, + { + "id": "K_E", + "text": "E" + }, + { + "id": "K_R", + "text": "R" + }, + { + "id": "K_T", + "text": "T" + }, + { + "id": "K_Y", + "text": "Y" + }, + { + "id": "K_U", + "text": "U" + }, + { + "id": "K_I", + "text": "I" + }, + { + "id": "K_O", + "text": "O" + }, + { + "id": "K_P", + "text": "P" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "A", + "pad": 70 + }, + { + "id": "K_S", + "text": "S" + }, + { + "id": "K_D", + "text": "D" + }, + { + "id": "K_F", + "text": "F" + }, + { + "id": "K_G", + "text": "G" + }, + { + "id": "K_H", + "text": "H" + }, + { + "id": "K_J", + "text": "J" + }, + { + "id": "K_K", + "text": "K" + }, + { + "id": "K_L", + "text": "L" + }, + { + "sp": "10", + "width": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_Z", + "text": "Z" + }, + { + "id": "K_X", + "text": "X" + }, + { + "id": "K_C", + "text": "C" + }, + { + "id": "K_V", + "text": "V" + }, + { + "id": "K_B", + "text": "B" + }, + { + "id": "K_N", + "text": "N" + }, + { + "id": "K_M", + "text": "M" + }, + { + "id": "K_PERIOD", + "text": ".", + "layer": "default", + "sk": [ + { + "text": ",", + "id": "K_COMMA", + "layer": "default" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE", + "layer": "default" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH", + "layer": "default" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON", + "layer": "default" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "1" + }, + { + "id": "K_2", + "text": "2" + }, + { + "id": "K_3", + "text": "3" + }, + { + "id": "K_4", + "text": "4" + }, + { + "id": "K_5", + "text": "5" + }, + { + "id": "K_6", + "text": "6" + }, + { + "id": "K_7", + "text": "7" + }, + { + "id": "K_8", + "text": "8" + }, + { + "id": "K_9", + "text": "9" + }, + { + "id": "K_0", + "text": "0" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_4", + "text": "$", + "layer": "shift", + "pad": 70 + }, + { + "id": "K_2", + "text": "@", + "layer": "shift" + }, + { + "id": "K_3", + "text": "#", + "layer": "shift" + }, + { + "id": "K_5", + "text": "%", + "layer": "shift" + }, + { + "id": "K_7", + "text": "&", + "layer": "shift" + }, + { + "id": "K_HYPHEN", + "text": "_", + "layer": "shift" + }, + { + "id": "K_EQUAL", + "text": "=", + "layer": "default" + }, + { + "id": "K_BKSLASH", + "text": "|", + "layer": "shift" + }, + { + "id": "K_BKSLASH", + "text": "\\", + "layer": "default" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "1" + }, + { + "id": "K_LBRKT", + "text": "[", + "sk": [ + { + "id": "U_00AB", + "text": "\u00AB" + }, + { + "id": "K_COMMA", + "text": "<", + "layer": "shift" + }, + { + "id": "K_LBRKT", + "text": "{", + "layer": "shift" + } + ] + }, + { + "id": "K_9", + "text": "(", + "layer": "shift" + }, + { + "id": "K_0", + "text": ")", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "]", + "sk": [ + { + "id": "U_00BB", + "text": "\u00BB" + }, + { + "id": "K_PERIOD", + "text": ">", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "}", + "layer": "shift" + } + ] + }, + { + "id": "K_EQUAL", + "text": "+", + "layer": "shift" + }, + { + "id": "K_HYPHEN", + "text": "-", + "layer": "default" + }, + { + "id": "K_8", + "text": "*", + "layer": "shift" + }, + { + "id": "K_SLASH", + "text": "/", + "layer": "default" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LOWER", + "text": "*abc*", + "width": "140", + "sp": "1", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + } + ] + }, + "phone": { + "font": "Tahoma", + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "q" + }, + { + "id": "K_W", + "text": "w" + }, + { + "id": "K_E", + "text": "e" + }, + { + "id": "K_R", + "text": "r" + }, + { + "id": "K_T", + "text": "t" + }, + { + "id": "K_Y", + "text": "y" + }, + { + "id": "K_U", + "text": "u" + }, + { + "id": "K_I", + "text": "i" + }, + { + "id": "K_O", + "text": "o" + }, + { + "id": "K_P", + "text": "p" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "a", + "pad": "50" + }, + { + "id": "K_S", + "text": "s" + }, + { + "id": "K_D", + "text": "d" + }, + { + "id": "K_F", + "text": "f" + }, + { + "id": "K_G", + "text": "g" + }, + { + "id": "K_H", + "text": "h" + }, + { + "id": "K_J", + "text": "j" + }, + { + "id": "K_K", + "text": "k" + }, + { + "id": "K_L", + "text": "l" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_Z", + "text": "z" + }, + { + "id": "K_X", + "text": "x" + }, + { + "id": "K_C", + "text": "c" + }, + { + "id": "K_V", + "text": "v" + }, + { + "id": "K_B", + "text": "b" + }, + { + "id": "K_N", + "text": "n" + }, + { + "id": "K_M", + "text": "m" + }, + { + "id": "K_PERIOD", + "text": ".", + "sk": [ + { + "text": ",", + "id": "K_COMMA" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "150", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "Q" + }, + { + "id": "K_W", + "text": "W" + }, + { + "id": "K_E", + "text": "E" + }, + { + "id": "K_R", + "text": "R" + }, + { + "id": "K_T", + "text": "T" + }, + { + "id": "K_Y", + "text": "Y" + }, + { + "id": "K_U", + "text": "U" + }, + { + "id": "K_I", + "text": "I" + }, + { + "id": "K_O", + "text": "O" + }, + { + "id": "K_P", + "text": "P" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "A", + "pad": "50" + }, + { + "id": "K_S", + "text": "S" + }, + { + "id": "K_D", + "text": "D" + }, + { + "id": "K_F", + "text": "F" + }, + { + "id": "K_G", + "text": "G" + }, + { + "id": "K_H", + "text": "H" + }, + { + "id": "K_J", + "text": "J" + }, + { + "id": "K_K", + "text": "K" + }, + { + "id": "K_L", + "text": "L" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_Z", + "text": "Z" + }, + { + "id": "K_X", + "text": "X" + }, + { + "id": "K_C", + "text": "C" + }, + { + "id": "K_V", + "text": "V" + }, + { + "id": "K_B", + "text": "B" + }, + { + "id": "K_N", + "text": "N" + }, + { + "id": "K_M", + "text": "M" + }, + { + "id": "K_PERIOD", + "text": ".", + "layer": "default", + "sk": [ + { + "text": ",", + "id": "K_COMMA", + "layer": "default" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE", + "layer": "default" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH", + "layer": "default" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON", + "layer": "default" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "150", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "1" + }, + { + "id": "K_2", + "text": "2" + }, + { + "id": "K_3", + "text": "3" + }, + { + "id": "K_4", + "text": "4" + }, + { + "id": "K_5", + "text": "5" + }, + { + "id": "K_6", + "text": "6" + }, + { + "id": "K_7", + "text": "7" + }, + { + "id": "K_8", + "text": "8" + }, + { + "id": "K_9", + "text": "9" + }, + { + "id": "K_0", + "text": "0" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_4", + "layer": "shift", + "text": "$", + "pad": "50" + }, + { + "id": "K_2", + "layer": "shift", + "text": "@" + }, + { + "id": "K_3", + "layer": "shift", + "text": "#" + }, + { + "id": "K_5", + "layer": "shift", + "text": "%" + }, + { + "id": "K_6", + "layer": "shift", + "text": "&" + }, + { + "id": "K_HYPHEN", + "layer": "shift", + "text": "_" + }, + { + "id": "K_EQUAL", + "text": "=", + "layer": "default" + }, + { + "id": "K_BKSLASH", + "layer": "shift", + "text": "|" + }, + { + "id": "K_BKSLASH", + "text": "\\", + "layer": "default" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_LBRKT", + "text": "[", + "pad": "110", + "sk": [ + { + "id": "U_00AB", + "text": "\u00AB" + }, + { + "id": "K_COMMA", + "text": "<", + "layer": "shift" + }, + { + "id": "K_LBRKT", + "text": "{", + "layer": "shift" + } + ] + }, + { + "id": "K_9", + "layer": "shift", + "text": "(" + }, + { + "id": "K_0", + "layer": "shift", + "text": ")" + }, + { + "id": "K_RBRKT", + "text": "]", + "sk": [ + { + "id": "U_00BB", + "text": "\u00BB" + }, + { + "id": "K_PERIOD", + "text": ">", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "}", + "layer": "shift" + } + ] + }, + { + "id": "K_EQUAL", + "layer": "shift", + "text": "+" + }, + { + "id": "K_HYPHEN", + "text": "-" + }, + { + "id": "K_8", + "layer": "shift", + "text": "*" + }, + { + "id": "K_SLASH", + "text": "/" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LOWER", + "text": "*abc*", + "width": "150", + "sp": "1", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + } + ] + } +} diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kmn b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kmn new file mode 100644 index 0000000000..ee63259d14 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kmn @@ -0,0 +1,28 @@ +c capslock generated from template at 2021-07-27 17:32:04 +c with name "Caps Lock" +store(&VERSION) '10.0' +store(&NAME) 'Caps Lock' +store(©RIGHT) 'Copyright (C)' +store(&KEYBOARDVERSION) '1.0' +store(&TARGETS) 'macosx iphone androidtablet mobile desktop tablet' +store(&VISUALKEYBOARD) 'capslock.kvks' +store(&LAYOUTFILE) 'capslock.keyman-touch-layout' + +begin Unicode > use(main) + +group(main) using keys + ++ [NCAPS K_A] > 'fail.' ++ [CAPS K_A] > 'pass.' + ++ [NCAPS SHIFT K_B] > 'fail.' ++ [CAPS SHIFT K_B] > 'pass.' + ++ '#' > 'pass.' ++ '3' > 'fail.' + ++ 'c' > 'fail.' ++ 'C' > 'pass.' + ++ 'd' > 'pass.' ++ 'D' > 'fail.' diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kps b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kps new file mode 100644 index 0000000000..29312d85fb --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kps @@ -0,0 +1,67 @@ + + + + 14.0.236.0 + 7.0 + + + + readme.htm + + + + + + + + + + Caps Lock + Copyright (C) + + + + + + ..\build\capslock.kmx + + 0 + .kmx + + + ..\build\capslock.js + + 0 + .js + + + ..\build\capslock.kvk + + 0 + .kvk + + + welcome.htm + + 0 + .htm + + + readme.htm + + 0 + .htm + + + + + Caps Lock + capslock + 1.0 + + English + + + + + diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kvks b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kvks new file mode 100644 index 0000000000..f41250750e --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/capslock.kvks @@ -0,0 +1,8 @@ + + +
+ 10.0 + capslock + +
+
diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/source/readme.htm b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/readme.htm new file mode 100644 index 0000000000..c363fcfeb4 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/readme.htm @@ -0,0 +1,24 @@ + + + + + + Caps Lock + + + + +

Caps Lock

+ +

+ Caps Lock 1.0 generated from template. +

+ +

Copyright (C)

+ + + diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/welcome.htm b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/welcome.htm similarity index 64% rename from windows/src/test/manual-tests/caps_lock_headers/source/welcome.htm rename to windows/src/test/manual-tests/caps-lock-stores/capslock/source/welcome.htm index 5155c2ff91..bfbff5fd41 100644 --- a/windows/src/test/manual-tests/caps_lock_headers/source/welcome.htm +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/source/welcome.htm @@ -2,7 +2,8 @@ - Start Using caps_lock_shift_frees_caps + + Start Using Caps Lock + + + +

CapsOnOnly and ShiftFreesCaps features

+ +

+ CapsOnOnly and ShiftFreesCaps features 1.0 generated from template. +

+ +

Copyright (C)

+ + + diff --git a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.keyman-touch-layout b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.keyman-touch-layout new file mode 100644 index 0000000000..149069b062 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.keyman-touch-layout @@ -0,0 +1,1316 @@ +{ + "tablet": { + "font": "Tahoma", + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "q" + }, + { + "id": "K_W", + "text": "w" + }, + { + "id": "K_E", + "text": "e" + }, + { + "id": "K_R", + "text": "r" + }, + { + "id": "K_T", + "text": "t" + }, + { + "id": "K_Y", + "text": "y" + }, + { + "id": "K_U", + "text": "u" + }, + { + "id": "K_I", + "text": "i" + }, + { + "id": "K_O", + "text": "o" + }, + { + "id": "K_P", + "text": "p" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "a", + "pad": 70 + }, + { + "id": "K_S", + "text": "s" + }, + { + "id": "K_D", + "text": "d" + }, + { + "id": "K_F", + "text": "f" + }, + { + "id": "K_G", + "text": "g" + }, + { + "id": "K_H", + "text": "h" + }, + { + "id": "K_J", + "text": "j" + }, + { + "id": "K_K", + "text": "k" + }, + { + "id": "K_L", + "text": "l" + }, + { + "sp": "10", + "width": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_Z", + "text": "z" + }, + { + "id": "K_X", + "text": "x" + }, + { + "id": "K_C", + "text": "c" + }, + { + "id": "K_V", + "text": "v" + }, + { + "id": "K_B", + "text": "b" + }, + { + "id": "K_N", + "text": "n" + }, + { + "id": "K_M", + "text": "m" + }, + { + "id": "K_PERIOD", + "text": ".", + "sk": [ + { + "text": ",", + "id": "K_COMMA" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "Q" + }, + { + "id": "K_W", + "text": "W" + }, + { + "id": "K_E", + "text": "E" + }, + { + "id": "K_R", + "text": "R" + }, + { + "id": "K_T", + "text": "T" + }, + { + "id": "K_Y", + "text": "Y" + }, + { + "id": "K_U", + "text": "U" + }, + { + "id": "K_I", + "text": "I" + }, + { + "id": "K_O", + "text": "O" + }, + { + "id": "K_P", + "text": "P" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "A", + "pad": 70 + }, + { + "id": "K_S", + "text": "S" + }, + { + "id": "K_D", + "text": "D" + }, + { + "id": "K_F", + "text": "F" + }, + { + "id": "K_G", + "text": "G" + }, + { + "id": "K_H", + "text": "H" + }, + { + "id": "K_J", + "text": "J" + }, + { + "id": "K_K", + "text": "K" + }, + { + "id": "K_L", + "text": "L" + }, + { + "sp": "10", + "width": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_Z", + "text": "Z" + }, + { + "id": "K_X", + "text": "X" + }, + { + "id": "K_C", + "text": "C" + }, + { + "id": "K_V", + "text": "V" + }, + { + "id": "K_B", + "text": "B" + }, + { + "id": "K_N", + "text": "N" + }, + { + "id": "K_M", + "text": "M" + }, + { + "id": "K_PERIOD", + "text": ".", + "layer": "default", + "sk": [ + { + "text": ",", + "id": "K_COMMA", + "layer": "default" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE", + "layer": "default" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH", + "layer": "default" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON", + "layer": "default" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "1" + }, + { + "id": "K_2", + "text": "2" + }, + { + "id": "K_3", + "text": "3" + }, + { + "id": "K_4", + "text": "4" + }, + { + "id": "K_5", + "text": "5" + }, + { + "id": "K_6", + "text": "6" + }, + { + "id": "K_7", + "text": "7" + }, + { + "id": "K_8", + "text": "8" + }, + { + "id": "K_9", + "text": "9" + }, + { + "id": "K_0", + "text": "0" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_4", + "text": "$", + "layer": "shift", + "pad": 70 + }, + { + "id": "K_2", + "text": "@", + "layer": "shift" + }, + { + "id": "K_3", + "text": "#", + "layer": "shift" + }, + { + "id": "K_5", + "text": "%", + "layer": "shift" + }, + { + "id": "K_7", + "text": "&", + "layer": "shift" + }, + { + "id": "K_HYPHEN", + "text": "_", + "layer": "shift" + }, + { + "id": "K_EQUAL", + "text": "=", + "layer": "default" + }, + { + "id": "K_BKSLASH", + "text": "|", + "layer": "shift" + }, + { + "id": "K_BKSLASH", + "text": "\\", + "layer": "default" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "110", + "sp": "1" + }, + { + "id": "K_LBRKT", + "text": "[", + "sk": [ + { + "id": "U_00AB", + "text": "\u00AB" + }, + { + "id": "K_COMMA", + "text": "<", + "layer": "shift" + }, + { + "id": "K_LBRKT", + "text": "{", + "layer": "shift" + } + ] + }, + { + "id": "K_9", + "text": "(", + "layer": "shift" + }, + { + "id": "K_0", + "text": ")", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "]", + "sk": [ + { + "id": "U_00BB", + "text": "\u00BB" + }, + { + "id": "K_PERIOD", + "text": ">", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "}", + "layer": "shift" + } + ] + }, + { + "id": "K_EQUAL", + "text": "+", + "layer": "shift" + }, + { + "id": "K_HYPHEN", + "text": "-", + "layer": "default" + }, + { + "id": "K_8", + "text": "*", + "layer": "shift" + }, + { + "id": "K_SLASH", + "text": "/", + "layer": "default" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "90", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LOWER", + "text": "*abc*", + "width": "140", + "sp": "1", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "630", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + } + ] + }, + "phone": { + "font": "Tahoma", + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "q" + }, + { + "id": "K_W", + "text": "w" + }, + { + "id": "K_E", + "text": "e" + }, + { + "id": "K_R", + "text": "r" + }, + { + "id": "K_T", + "text": "t" + }, + { + "id": "K_Y", + "text": "y" + }, + { + "id": "K_U", + "text": "u" + }, + { + "id": "K_I", + "text": "i" + }, + { + "id": "K_O", + "text": "o" + }, + { + "id": "K_P", + "text": "p" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "a", + "pad": "50" + }, + { + "id": "K_S", + "text": "s" + }, + { + "id": "K_D", + "text": "d" + }, + { + "id": "K_F", + "text": "f" + }, + { + "id": "K_G", + "text": "g" + }, + { + "id": "K_H", + "text": "h" + }, + { + "id": "K_J", + "text": "j" + }, + { + "id": "K_K", + "text": "k" + }, + { + "id": "K_L", + "text": "l" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_Z", + "text": "z" + }, + { + "id": "K_X", + "text": "x" + }, + { + "id": "K_C", + "text": "c" + }, + { + "id": "K_V", + "text": "v" + }, + { + "id": "K_B", + "text": "b" + }, + { + "id": "K_N", + "text": "n" + }, + { + "id": "K_M", + "text": "m" + }, + { + "id": "K_PERIOD", + "text": ".", + "sk": [ + { + "text": ",", + "id": "K_COMMA" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "150", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "Q" + }, + { + "id": "K_W", + "text": "W" + }, + { + "id": "K_E", + "text": "E" + }, + { + "id": "K_R", + "text": "R" + }, + { + "id": "K_T", + "text": "T" + }, + { + "id": "K_Y", + "text": "Y" + }, + { + "id": "K_U", + "text": "U" + }, + { + "id": "K_I", + "text": "I" + }, + { + "id": "K_O", + "text": "O" + }, + { + "id": "K_P", + "text": "P" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "A", + "pad": "50" + }, + { + "id": "K_S", + "text": "S" + }, + { + "id": "K_D", + "text": "D" + }, + { + "id": "K_F", + "text": "F" + }, + { + "id": "K_G", + "text": "G" + }, + { + "id": "K_H", + "text": "H" + }, + { + "id": "K_J", + "text": "J" + }, + { + "id": "K_K", + "text": "K" + }, + { + "id": "K_L", + "text": "L" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_Z", + "text": "Z" + }, + { + "id": "K_X", + "text": "X" + }, + { + "id": "K_C", + "text": "C" + }, + { + "id": "K_V", + "text": "V" + }, + { + "id": "K_B", + "text": "B" + }, + { + "id": "K_N", + "text": "N" + }, + { + "id": "K_M", + "text": "M" + }, + { + "id": "K_PERIOD", + "text": ".", + "layer": "default", + "sk": [ + { + "text": ",", + "id": "K_COMMA", + "layer": "default" + }, + { + "text": "!", + "id": "K_1", + "layer": "shift" + }, + { + "text": "?", + "id": "K_SLASH", + "layer": "shift" + }, + { + "text": "'", + "id": "K_QUOTE", + "layer": "default" + }, + { + "text": "\"", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "\\", + "id": "K_BKSLASH", + "layer": "default" + }, + { + "text": ":", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": ";", + "id": "K_COLON", + "layer": "default" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "*123*", + "width": "150", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "1" + }, + { + "id": "K_2", + "text": "2" + }, + { + "id": "K_3", + "text": "3" + }, + { + "id": "K_4", + "text": "4" + }, + { + "id": "K_5", + "text": "5" + }, + { + "id": "K_6", + "text": "6" + }, + { + "id": "K_7", + "text": "7" + }, + { + "id": "K_8", + "text": "8" + }, + { + "id": "K_9", + "text": "9" + }, + { + "id": "K_0", + "text": "0" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_4", + "layer": "shift", + "text": "$", + "pad": "50" + }, + { + "id": "K_2", + "layer": "shift", + "text": "@" + }, + { + "id": "K_3", + "layer": "shift", + "text": "#" + }, + { + "id": "K_5", + "layer": "shift", + "text": "%" + }, + { + "id": "K_6", + "layer": "shift", + "text": "&" + }, + { + "id": "K_HYPHEN", + "layer": "shift", + "text": "_" + }, + { + "id": "K_EQUAL", + "text": "=", + "layer": "default" + }, + { + "id": "K_BKSLASH", + "layer": "shift", + "text": "|" + }, + { + "id": "K_BKSLASH", + "text": "\\", + "layer": "default" + }, + { + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_LBRKT", + "text": "[", + "pad": "110", + "sk": [ + { + "id": "U_00AB", + "text": "\u00AB" + }, + { + "id": "K_COMMA", + "text": "<", + "layer": "shift" + }, + { + "id": "K_LBRKT", + "text": "{", + "layer": "shift" + } + ] + }, + { + "id": "K_9", + "layer": "shift", + "text": "(" + }, + { + "id": "K_0", + "layer": "shift", + "text": ")" + }, + { + "id": "K_RBRKT", + "text": "]", + "sk": [ + { + "id": "U_00BB", + "text": "\u00BB" + }, + { + "id": "K_PERIOD", + "text": ">", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "}", + "layer": "shift" + } + ] + }, + { + "id": "K_EQUAL", + "layer": "shift", + "text": "+" + }, + { + "id": "K_HYPHEN", + "text": "-" + }, + { + "id": "K_8", + "layer": "shift", + "text": "*" + }, + { + "id": "K_SLASH", + "text": "/" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LOWER", + "text": "*abc*", + "width": "150", + "sp": "1", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "610", + "sp": "0" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "150", + "sp": "1" + } + ] + } + ] + } + ] + } +} diff --git a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kmn b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kmn new file mode 100644 index 0000000000..298e4b2757 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kmn @@ -0,0 +1,40 @@ +c shift_frees_caps generated from template at 2021-07-28 10:48:25 +c with name "CapsOnOnly and ShiftFreesCaps features" +store(&VERSION) '10.0' +store(&NAME) 'CapsOnOnly and ShiftFreesCaps features' +store(©RIGHT) 'Copyright (C)' +store(&KEYBOARDVERSION) '1.0' +store(&TARGETS) 'macosx iphone androidtablet mobile desktop tablet' +store(&VISUALKEYBOARD) 'shift_frees_caps.kvks' +store(&LAYOUTFILE) 'shift_frees_caps.keyman-touch-layout' + +c Description: Tests Caps Lock env set +c keys: [K_1][K_CAPS][K_2][SHIFT K_3][K_4][K_CAPS][K_5][K_CAPS][K_6] +c expected: pass.pass.pass.pass.pass.pass. +c capsLock: 0 +c context: + +store(&shiftfreescaps) '1' +store(&capsononly) '1' + +begin Unicode > use(Main) + +group(Main) using keys + ++ [CAPS K_1] > 'fail.' ++ [NCAPS K_1] > 'pass.' + ++ [NCAPS K_2] > 'fail.' ++ [CAPS K_2] > 'pass.' + ++ [CAPS SHIFT K_3] > 'fail.' ++ [NCAPS SHIFT K_3] > 'pass.' + ++ [CAPS K_4] > 'fail.' ++ [NCAPS K_4] > 'pass.' + ++ [NCAPS K_5] > 'fail.' ++ [CAPS K_5] > 'pass.' + ++ [NCAPS K_6] > 'fail.' ++ [CAPS K_6] > 'pass.' diff --git a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kps b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kps new file mode 100644 index 0000000000..fe4adf265c --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kps @@ -0,0 +1,67 @@ + + + + 14.0.236.0 + 7.0 + + + + readme.htm + + + + + + + + + + CapsOnOnly and ShiftFreesCaps features + Copyright (C) + + + + + + ..\build\shift_frees_caps.kmx + + 0 + .kmx + + + ..\build\shift_frees_caps.js + + 0 + .js + + + ..\build\shift_frees_caps.kvk + + 0 + .kvk + + + welcome.htm + + 0 + .htm + + + readme.htm + + 0 + .htm + + + + + CapsOnOnly and ShiftFreesCaps features + shift_frees_caps + 1.0 + + English + + + + + diff --git a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kvks b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kvks new file mode 100644 index 0000000000..73a5f1f791 --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/shift_frees_caps.kvks @@ -0,0 +1,8 @@ + + +
+ 10.0 + shift_frees_caps + +
+
diff --git a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/welcome.htm b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/welcome.htm new file mode 100644 index 0000000000..9e38af5b4c --- /dev/null +++ b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/source/welcome.htm @@ -0,0 +1,28 @@ + + + + + + Start Using CapsOnOnly and ShiftFreesCaps features + + + + +

Start Using CapsOnOnly and ShiftFreesCaps features

+ +

+ CapsOnOnly and ShiftFreesCaps features 1.0 generated from template. +

+ +

Keyboard Layout

+ + + +

Copyright (C)

+ + + \ No newline at end of file diff --git a/windows/src/test/manual-tests/caps_lock_headers/HISTORY.md b/windows/src/test/manual-tests/caps_lock_headers/HISTORY.md deleted file mode 100644 index f2b6acd03b..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/HISTORY.md +++ /dev/null @@ -1,6 +0,0 @@ -caps_lock_shift_frees_caps Change History -==================== - -1.0 (2019-01-07) ----------------- -* Created by diff --git a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_always_off.kmx b/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_always_off.kmx deleted file mode 100644 index 793c77615f..0000000000 Binary files a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_always_off.kmx and /dev/null differ diff --git a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_always_off.kvk b/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_always_off.kvk deleted file mode 100644 index 06fe5344c1..0000000000 Binary files a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_always_off.kvk and /dev/null differ diff --git a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_on_only.kmx b/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_on_only.kmx deleted file mode 100644 index 3ec75e634b..0000000000 Binary files a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_on_only.kmx and /dev/null differ diff --git a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_on_only.kvk b/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_on_only.kvk deleted file mode 100644 index 0e1ea70914..0000000000 Binary files a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_caps_on_only.kvk and /dev/null differ diff --git a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_headers.kmp b/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_headers.kmp deleted file mode 100644 index a74095c428..0000000000 Binary files a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_headers.kmp and /dev/null differ diff --git a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kmp b/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kmp deleted file mode 100644 index ebb4429abe..0000000000 Binary files a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kmp and /dev/null differ diff --git a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kmx b/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kmx deleted file mode 100644 index 4942a3dc27..0000000000 Binary files a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kmx and /dev/null differ diff --git a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kvk b/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kvk deleted file mode 100644 index 2cd73a93a3..0000000000 Binary files a/windows/src/test/manual-tests/caps_lock_headers/build/caps_lock_shift_frees_caps.kvk and /dev/null differ diff --git a/windows/src/test/manual-tests/caps_lock_headers/caps_lock_headers.keyboard_info b/windows/src/test/manual-tests/caps_lock_headers/caps_lock_headers.keyboard_info deleted file mode 100644 index 75346d748f..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/caps_lock_headers.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - - ], - "description": "caps_lock_shift_frees_caps generated from template" -} diff --git a/windows/src/test/manual-tests/caps_lock_headers/caps_lock_headers.kpj b/windows/src/test/manual-tests/caps_lock_headers/caps_lock_headers.kpj deleted file mode 100644 index 1bef06328b..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/caps_lock_headers.kpj +++ /dev/null @@ -1,119 +0,0 @@ - - - - $PROJECTPATH\build - True - True - - - - id_fb55c4befba94fb9e7468d5126e5ed10 - caps_lock_shift_frees_caps.kmn - source\caps_lock_shift_frees_caps.kmn - 1.0 - .kmn -
- caps_lock_shift_frees_caps - © 2019 SIL International -
-
- - id_bae62fccce40fcec206e0258206bd18c - caps_lock_caps_always_off.kmn - source\caps_lock_caps_always_off.kmn - 1.0 - .kmn -
- caps_lock_caps_on_only - © 2019 SIL International -
-
- - id_7fff108dc6e58551d8bd46f89477c870 - caps_lock_caps_on_only.kmn - source\caps_lock_caps_on_only.kmn - 1.0 - .kmn -
- caps_lock_caps_always_off - © 2019 SIL International -
-
- - id_b84bf1d230e3916202970f55640a5eea - caps_lock_headers.kps - source\caps_lock_headers.kps - 1.0 - .kps -
- caps_lock_shift_frees_caps - © - 1.0 -
-
- - id_cd9d739b26ce5dd73af0540ad4365954 - caps_lock_shift_frees_caps.kmx - source\..\build\caps_lock_shift_frees_caps.kmx - - .kmx - id_b84bf1d230e3916202970f55640a5eea - - - id_a5299cc81250ba8131c83b259bb365f8 - caps_lock_shift_frees_caps.kvk - source\..\build\caps_lock_shift_frees_caps.kvk - - .kvk - id_b84bf1d230e3916202970f55640a5eea - - - id_356e5d149c1e539356d72698c1e401a6 - welcome.htm - source\welcome.htm - - .htm - id_b84bf1d230e3916202970f55640a5eea - - - id_8da344c4cea6f467013357fe099006f5 - readme.htm - source\readme.htm - - .htm - id_b84bf1d230e3916202970f55640a5eea - - - id_0e43bd214d366e11a424144a99978808 - caps_lock_caps_on_only.kvk - source\..\build\caps_lock_caps_on_only.kvk - - .kvk - id_b84bf1d230e3916202970f55640a5eea - - - id_55c8833c53e3295cd9424a114280a63b - caps_lock_caps_always_off.kmx - source\..\build\caps_lock_caps_always_off.kmx - - .kmx - id_b84bf1d230e3916202970f55640a5eea - - - id_141ea53b18baa35cf62ededea4321523 - caps_lock_caps_always_off.kvk - source\..\build\caps_lock_caps_always_off.kvk - - .kvk - id_b84bf1d230e3916202970f55640a5eea - - - id_7b23e6d989803a371ca8e77bc862d26f - caps_lock_caps_on_only.kmx - source\..\build\caps_lock_caps_on_only.kmx - - .kmx - id_b84bf1d230e3916202970f55640a5eea - -
-
diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_always_off.kmn b/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_always_off.kmn deleted file mode 100644 index 278300b8e2..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_always_off.kmn +++ /dev/null @@ -1,18 +0,0 @@ -c caps_lock_caps_on_only generated from template at 2019-01-07 14:12:26 - -store(&VERSION) '10.0' -store(&NAME) 'caps_lock_caps_on_only' -store(©RIGHT) '© 2019 SIL International' -store(&KEYBOARDVERSION) '1.0' -store(&TARGETS) 'windows' -store(&VISUALKEYBOARD) 'caps_lock_caps_on_only.kvks' -store(&CAPSONONLY) '1' - -begin Unicode > use(main) - -group(main) using keys - -+ [NCAPS K_A] > 'lower-a' -+ [CAPS K_A] > 'capital-a' -+ [NCAPS SHIFT K_A] > 'shift-a' -+ [CAPS SHIFT K_A] > 'capital-shift-a' diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_always_off.kvks b/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_always_off.kvks deleted file mode 100644 index 981d5d664a..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_always_off.kvks +++ /dev/null @@ -1,117 +0,0 @@ - - -
- 10.0 - caps_lock_caps_always_off - - - -
- - - ` - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 0 - - - = - q - w - e - r - t - y - u - i - o - p - [ - ] - \ - lower-a - s - d - f - g - h - j - k - l - ; - ' - \ - z - x - c - v - b - n - m - , - . - / - - - - ~ - ! - @ - # - $ - % - ^ - & - * - ( - ) - _ - + - Q - W - E - R - T - Y - U - I - O - P - { - } - | - shift-a - S - D - F - G - H - J - K - L - : - " - | - Z - X - C - V - B - N - M - < - > - ? - - - - - - -
diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_on_only.kmn b/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_on_only.kmn deleted file mode 100644 index 384300f06a..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_on_only.kmn +++ /dev/null @@ -1,18 +0,0 @@ -c caps_lock_caps_always_off generated from template at 2019-01-07 14:12:26 - -store(&VERSION) '10.0' -store(&NAME) 'caps_lock_caps_always_off' -store(©RIGHT) '© 2019 SIL International' -store(&KEYBOARDVERSION) '1.0' -store(&TARGETS) 'windows' -store(&VISUALKEYBOARD) 'caps_lock_caps_always_off.kvks' -store(&CAPSALWAYSOFF) '1' - -begin Unicode > use(main) - -group(main) using keys - -+ [NCAPS K_A] > 'lower-a' -+ [CAPS K_A] > 'capital-a' -+ [NCAPS SHIFT K_A] > 'shift-a' -+ [CAPS SHIFT K_A] > 'capital-shift-a' diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_on_only.kvks b/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_on_only.kvks deleted file mode 100644 index c5c54455cf..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_caps_on_only.kvks +++ /dev/null @@ -1,117 +0,0 @@ - - -
- 10.0 - caps_lock_caps_always_off - - - -
- - - ` - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 0 - - - = - q - w - e - r - t - y - u - i - o - p - [ - ] - \ - lower-a - s - d - f - g - h - j - k - l - ; - ' - \ - z - x - c - v - b - n - m - , - . - / - - - - ~ - ! - @ - # - $ - % - ^ - & - * - ( - ) - _ - + - Q - W - E - R - T - Y - U - I - O - P - { - } - | - shift-a - S - D - F - G - H - J - K - L - : - " - | - Z - X - C - V - B - N - M - < - > - ? - - - - - - -
diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_headers.kps b/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_headers.kps deleted file mode 100644 index 4e74dfe003..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_headers.kps +++ /dev/null @@ -1,95 +0,0 @@ - - - - 11.0.1300.0 - 7.0 - - - - readme.htm - - - - - - - - - - caps_lock_shift_frees_caps - © - - 1.0 - - - - ..\build\caps_lock_shift_frees_caps.kmx - - 0 - .kmx - - - ..\build\caps_lock_shift_frees_caps.kvk - - 0 - .kvk - - - welcome.htm - - 0 - .htm - - - readme.htm - - 0 - .htm - - - ..\build\caps_lock_caps_on_only.kvk - File caps_lock_caps_on_only.kvk - 0 - .kvk - - - ..\build\caps_lock_caps_always_off.kmx - Keyboard caps_lock_caps_on_only - 0 - .kmx - - - ..\build\caps_lock_caps_always_off.kvk - File caps_lock_caps_always_off.kvk - 0 - .kvk - - - ..\build\caps_lock_caps_on_only.kmx - Keyboard caps_lock_caps_always_off - 0 - .kmx - - - - - caps_lock_shift_frees_caps - caps_lock_shift_frees_caps - 1.0 - - - - caps_lock_caps_on_only - caps_lock_caps_always_off - 1.0 - - - - caps_lock_caps_always_off - caps_lock_caps_on_only - 1.0 - - - - - diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_shift_frees_caps.kmn b/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_shift_frees_caps.kmn deleted file mode 100644 index 515921b9bd..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_shift_frees_caps.kmn +++ /dev/null @@ -1,17 +0,0 @@ -c caps_lock_shift_frees_caps generated from template at 2019-01-07 14:12:26 -store(&VERSION) '10.0' -store(&NAME) 'caps_lock_shift_frees_caps' -store(©RIGHT) '© 2019 SIL International' -store(&KEYBOARDVERSION) '1.0' -store(&TARGETS) 'windows' -store(&VISUALKEYBOARD) 'caps_lock_shift_frees_caps.kvks' -store(&SHIFTFREESCAPS) '1' - -begin Unicode > use(main) - -group(main) using keys - -+ [NCAPS K_A] > 'lower-a' -+ [CAPS K_A] > 'capital-a' -+ [NCAPS SHIFT K_A] > 'shift-a' -+ [CAPS SHIFT K_A] > 'capital-shift-a' diff --git a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_shift_frees_caps.kvks b/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_shift_frees_caps.kvks deleted file mode 100644 index db31de4b42..0000000000 --- a/windows/src/test/manual-tests/caps_lock_headers/source/caps_lock_shift_frees_caps.kvks +++ /dev/null @@ -1,115 +0,0 @@ - - -
- 10.0 - caps_lock_shift_frees_caps - -
- - - ` - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 0 - - - = - q - w - e - r - t - y - u - i - o - p - [ - ] - \ - lower-a - s - d - f - g - h - j - k - l - ; - ' - \ - z - x - c - v - b - n - m - , - . - / - - - - ~ - ! - @ - # - $ - % - ^ - & - * - ( - ) - _ - + - Q - W - E - R - T - Y - U - I - O - P - { - } - | - shift-a - S - D - F - G - H - J - K - L - : - " - | - Z - X - C - V - B - N - M - < - > - ? - - - - - - -
diff --git a/windows/src/test/unit-tests/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr b/windows/src/test/unit-tests/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr index 7398d29d05..af974f1dc8 100644 --- a/windows/src/test/unit-tests/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr +++ b/windows/src/test/unit-tests/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr @@ -99,7 +99,8 @@ uses Keyman.System.PackageInfoRefreshLexicalModels in '..\..\..\global\delphi\packages\Keyman.System.PackageInfoRefreshLexicalModels.pas', Keyman.System.Standards.LangTagsRegistry in '..\..\..\global\delphi\standards\Keyman.System.Standards.LangTagsRegistry.pas', Keyman.Developer.System.Project.UrlRenderer in '..\..\..\developer\TIKE\project\Keyman.Developer.System.Project.UrlRenderer.pas', - KeymanPaths in '..\..\..\global\delphi\general\KeymanPaths.pas'; + KeymanPaths in '..\..\..\global\delphi\general\KeymanPaths.pas', + Keyman.Developer.System.ValidateKpsFile in '..\..\..\global\delphi\general\Keyman.Developer.System.ValidateKpsFile.pas'; var runner : ITestRunner; diff --git a/windows/src/test/unit-tests/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj b/windows/src/test/unit-tests/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj index 2638afd863..79c60633df 100644 --- a/windows/src/test/unit-tests/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj +++ b/windows/src/test/unit-tests/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj @@ -181,6 +181,7 @@ + Cfg_2 Base