diff --git a/.gitignore b/.gitignore index b1e97c9773caba4563ab39fc5d19b99590873e71..832df984e1b9515f380cbba30bdfc266efd5dc03 100644 --- a/.gitignore +++ b/.gitignore @@ -226,6 +226,10 @@ # /test/wm_unichar/ +# VS Code +.vscode + + .DS_Store *.kpj.user diff --git a/README.md b/README.md index 95b45c405f3687c99889770468531b0e870c3e8f..3f8cd7ab4d240150acff53e0900bbaaf08c94dd3 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,12 @@ Open Source | Platform | Master | Nightly | Beta | Stable | | -------- | :----: | :-----: | :--: | :----: | -| Windows | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_Build)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanDesktop_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanDesktop_Beta),branch:(default:false)/statusIcon)| ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanDesktop_Stable),branch:(default:false)/statusIcon) | -| macOS | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanMac_Master)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanMac_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanMac_Beta),branch:(default:false)/statusIcon)| ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanMac_Stable),branch:(default:false)/statusIcon) | -| Web | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keymanweb_Build)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keymanweb_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keymanweb_Beta),branch:(default:false)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keymanweb_Stable),branch:(default:false)/statusIcon)| -| Android | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanAndroid_Build)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanAndroid_NightlyAlpha)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanAndroid_Beta),branch:(default:false)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanAndroid_Stable),branch:(default:false)/statusIcon) | -| iOS | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_iOS_Master)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_iOS_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_iOS_Beta),branch:(default:false)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_iOS_Stable),branch:(default:false)/statusIcon) | +| Windows | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_Build)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanDesktop_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanDesktop_Beta)/statusIcon)| ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanDesktop_Stable),branch:(default:false)/statusIcon) | +| macOS | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanMac_Master)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanMac_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanMac_Beta)/statusIcon)| ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanMac_Stable),branch:(default:false)/statusIcon) | +| Web | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keymanweb_Build)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keymanweb_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keymanweb_Beta)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keymanweb_Stable),branch:(default:false)/statusIcon)| +| Android | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanAndroid_Build)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanAndroid_NightlyAlpha)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanAndroid_Beta)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanAndroid_Stable),branch:(default:false)/statusIcon) | +| iOS | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_iOS_Master)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_iOS_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_iOS_Beta)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:Keyman_iOS_Stable),branch:(default:false)/statusIcon) | +| Linux | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanLinux_Master)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanLinux_Nightly)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanLinux_Beta)/statusIcon) | ![Build Status](https://build.palaso.org/app/rest/builds/buildType:(id:KeymanLinux_Stable),branch:(default:false)/statusIcon) | [Keyman](https://keyman.com) makes it possible for you to type in over 1,000 languages on Windows, iPhone, iPad, Android tablets and phones, and even instantly in your web browser. @@ -19,6 +20,7 @@ Open Source ## Desktop Platforms ## [Windows](./windows/src/README.md) [macOS](./mac/README.md) +[Linux](./linux/README.md) ## Web & Mobile Platforms ## [Web](./web/README.md) @@ -37,4 +39,7 @@ Each platform maintains multiple types of releases: Copyright (c) 2018 SIL International. All rights reserved. -Licensed under the [MIT](./windows/src/LICENSE.md) License. \ No newline at end of file +Licensed under the [MIT](./windows/src/LICENSE.md) License. + +[Keyman for Linux](./linux) is licensed under the [MIT](./windows/src/LICENSE.md) License apart from [ibus-kmfl](./linux/ibus-kmfl) which is licensed under the GNU General Public License as published by the Free Software Foundation; either [version 2](./linux/ibus-kmfl/COPYING) of the License, or (at your option) any later version. + diff --git a/VERSION.md b/VERSION.md new file mode 100644 index 0000000000000000000000000000000000000000..584e765672cff969e592c58f7266e41b60f57ab5 --- /dev/null +++ b/VERSION.md @@ -0,0 +1 @@ +Please see resources/VERSION.md for the current target build version. diff --git a/android/KMAPro/build.gradle b/android/KMAPro/build.gradle index c8afa7e178ac50fd853f3d16b9984e12bc8db634..ed4c88d466f464c8770278a1130ccb1456b151c7 100644 --- a/android/KMAPro/build.gradle +++ b/android/KMAPro/build.gradle @@ -8,7 +8,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.2' classpath 'com.github.triplet.gradle:play-publisher:1.2.0' classpath 'com.google.gms:google-services:3.1.2' classpath 'io.fabric.tools:gradle:1.25.1' diff --git a/android/KMAPro/gradle/wrapper/gradle-wrapper.properties b/android/KMAPro/gradle/wrapper/gradle-wrapper.properties index 7cff8b29df2e1729dc72260b5718c710517242e0..eefda80448ec0fc98388959b61ccb38e468f92f2 100644 --- a/android/KMAPro/gradle/wrapper/gradle-wrapper.properties +++ b/android/KMAPro/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Oct 30 09:32:59 ICT 2017 +#Sat Apr 14 11:26:38 ICT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/android/KMAPro/kMAPro/build.gradle b/android/KMAPro/kMAPro/build.gradle index e7e789fc110e76b7ab3512757c543164af9b6f12..82fee5b51e1c9acaceb73c28417d7002ec7eed5d 100644 --- a/android/KMAPro/kMAPro/build.gradle +++ b/android/KMAPro/kMAPro/build.gradle @@ -3,8 +3,7 @@ apply plugin: 'com.github.triplet.play' apply plugin: 'io.fabric' android { - compileSdkVersion 25 - buildToolsVersion "26.0.2" + compileSdkVersion 27 playAccountConfigs { defaultAccountConfig { if (project.hasProperty("keys_json_file")) { @@ -15,7 +14,7 @@ android { defaultConfig { applicationId "com.tavultesoft.kmapro" minSdkVersion 15 - targetSdkVersion 25 + targetSdkVersion 27 //dumpProperties(project.ext) // Use this to dump all external properties for debugging TeamCity integration @@ -33,8 +32,9 @@ android { // Because TeamCity does not bubble build.counter into system properties... versionName "${project.ext['build.number']}" } else { + String majorMinorVersion = file('$projectDir/../../../../resources/VERSION.md').text.trim() versionCode 100 - versionName '10.0.0.0' + versionName majorMinorVersion + '.0.0' } playAccountConfig = playAccountConfigs.defaultAccountConfig @@ -99,14 +99,18 @@ repositories { } } -dependencies { - api(name: 'keyman-engine', ext: 'aar') +ext { + currentFirebaseVersion = "15.0.2" +} +dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:support-v4:25.4.0' - implementation 'com.google.firebase:firebase-core:11.8.0' - implementation 'com.google.firebase:firebase-crash:11.8.0' - implementation('com.crashlytics.sdk.android:crashlytics:2.9.0@aar') { + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' + api(name: 'keyman-engine', ext: 'aar') + implementation "com.google.firebase:firebase-core:$currentFirebaseVersion" + implementation "com.google.firebase:firebase-crash:$currentFirebaseVersion" + implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') { transitive = true } } diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml index a2b3148af4d4c944db123285034a2547655f2788..30498ed524457d32ceb88055d9e0c2ece5726acb 100644 --- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml +++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml @@ -6,11 +6,13 @@ + @@ -39,34 +41,114 @@ /> + android:theme="@style/AppTheme.BrandedLaunch" > + - + + + + + + - + + - - + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + - - + + + - - + + + + + + + + @@ -105,8 +187,9 @@ android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize" android:label="@string/app_name" android:launchMode="singleTask" - android:theme="@android:style/Theme.Holo.Light.Dialog" > + android:theme="@style/Theme.AppCompat.Light.Dialog" > +
-

- Getting Started -

- - -
-
-

Phone

-
-
-

Tablet

-
+

+ Getting Started +

+ +
+
+

Phone

+
+
+

Tablet

-
-

- Keyman for Android lets you type in over 600 languages on Android mobiles and tablets. - To get started, here are some helpful hints: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Keyboard Keys
(Keyboard Present)
Install or select another language/keyboard
Hide the keyboard
Backspace
Return
Shift key. Long press this key to access the CTRL, ALT and CTRL ALT keys - (which can access additional key layers) -
Keys with a small dot in the top right corner indicate a long press key. Access - further functionality by long pressing the key -
Toolbar Icons
Share your text via Mail, Text, Facebook or Twitter
Open the Keyman Browser to use the web in your language
Open the menu for additional options
Adjust the font size
Delete all current text
Load this help page
Open the initial setup screen
-

+
+
+

+ Keyman for Android lets you type in over 600 languages on Android mobiles and tablets. + To get started, here are some helpful hints: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Keyboard Keys
(Keyboard Present)
Install or select another language/keyboard
Hide the keyboard
Backspace
Return
Shift key. Long press this key to access the CTRL, ALT and CTRL ALT keys + (which can access additional key layers) +
Keys with a small dot in the top right corner indicate a long press key. Access + further functionality by long pressing the key +
Toolbar Icons
Share your text via Mail, Text, or Twitter
Open the Keyman Browser to use the web in your language
Open the menu for additional options
Adjust the font size
Delete all current text
Load this help page
Open the initial setup screen
-

Switching between Keyboards

-

- To switch keyboards, follow these steps: -

- Step 1) -
- With the keyboard visible, touch the globe key: -
- -
- This will bring up a list of all currently installed languages (the default is English - EuroLatin2). If you have already downloaded additional languages, they will appear here. - Simply select them and the keyboard will re-appear with the new language. -

- If you haven't installed any languages as yet, touch the + - button in the top right corner of your screen. -

- Step 2) -
- A list of all available languages will appear. Scroll through this list until you find - the language you want to install. Some languages may have multiple keyboards - for - example we have 10+ different Tamil keyboards. -
- -

- Select the language and keyboard you want to install - a popup will ask for confirmation, - hit the Download button. -
- -

- Step 3) -
- Once the download is complete, the new keyboard will be selected. Touch the Return button on the top left corner of the popup and the new - keyboard will become active. -
- -

+

Switching between Keyboards

+

+ To switch keyboards, follow these steps:

- -

Hotkey

-

- To switch between languages while using an external keyboard (i.e. a bluetooth keyboard), a quick and easy way is to - use a hotkey combination. Press and hold 'Ctrl' and then press 'Tab'. - You should see a list of languages/keyboards. Tap on any available language of your choice. -

- -

Accessing Keyboard Help

-

- Step 1) Open the keyboard list by touching the globe key. -

-

+ Step 1) +
+ With the keyboard visible, touch the globe key: +
+ +
+ This will bring up a list of all currently installed languages (the default is English + EuroLatin (SIL)). If you have already downloaded additional languages, they will appear here. + Simply select them and the keyboard will re-appear with the new language. +

+ If you haven't installed any languages as yet, touch the + + button in the bottom right corner of your screen. +
+ +

+ Step 2) +
+ A list of all available languages will appear. Scroll through this list until you find + the language you want to install. Some languages may have multiple keyboards - for + example we have 10+ different Tamil keyboards. +
+ +

+ Select the language and keyboard you want to install - a popup will ask for confirmation, + hit the Download button. +
+ +

+ Step 3) +
+ Once the download is complete, the new keyboard will be selected. Touch the + button on the top left corner of the popup and the new keyboard will become active. +
+ +

+ Alternate Step for System Keyboard)
-

- Step 2) Touch the info icon for your keyboard. -

-

+ The Keyman system keyboard has an additional CLOSE KEYMAN button at the bottom left of + the "Keyboards" menu. Pushing this button will close Keyman and switch to the next non-Keyman system keyboard.
+ +
+

-

Installing System Wide Keyboards

-

- Step 1) Open the Keyman app and select the menu for additional options. - The screenshots below are of a device running Android 7.1 Nougat. -

-

-
-

- Step 2) Select 'Get Started' and then select 'Enable Keyman as a system-wide keyboard'. -

-

-
-

- This will open an Android settings screen for 'Available virtual keyboard'. -

-

-
-

- Step 3) Enable 'Keyman' as an available virtual keyboard. - This will bring up two confirmation dialogs. -

-

-
-

-
-

- Select 'OK' on both. Now when the on-screen keyboard appears for apps other than Keyman, - you'll see a keyboard icon at the bottom right. -

-

-
-

- Click the keyboard icon whenever you want to change the current system keyboard. -

-

+

Removing a Keyboard

+

+ To uninstall a keyboard, follow these steps: +

+ Step 1) +
+ With the keyboard visible, touch the globe key: +
+ +

+ Step 2) +
+ A list of all available languages will appear. Scroll through this list until you find + the keyboard you want to uninstall +
+ +

+ Step 3) +
+ Long press the keyboard and release to get a prompt to delete the keyboard. +
+

+ Step 4) + Press "Delete" to uninstall the keyboard. You'll see a notification when the keyboard is deleted. +
+ +

-

Installing Custom Keyboards

-

- Step 1) From a Chrome browser, click the link to your keyboard package .kmp file. This example is for GFF Amharic 7. -

-

-

- Step 2) If the browser prompts to use "Keyman" to open the file, click on "Keyman" and proceed to Step 3. - Otherwise, wait for the browser to finish downloading the .kmp file. - When the browser has finished downloading the KMP file, you will need to browse to the KMP file and select it. - Select the Menu, then "Downloads", and select the downloaded KMP file. -

-

-

- Step 3) On Android 6.0 (Marshmallow) and higher, mobile apps need to request permissions to access storage. Keyman - for Android needs access to read storage for installing the KMP file. At the dialog, select "ALLOW". Once authorized, - Keyman for Android won't need to ask for storage permission again, unless the user revokes or uninstalls the app. -

-

-

- Step 4) Keyman for Android will parse the metadata in the package. If the keyboard package includes a "welcome.htm" - file, this will be displayed at the confirmation to install the keyboard package. -

-

-

- If "welcome.htm" is not included, a generic page with the package ID and package version will be shown. Click the - left "Install" button to install the entire keyboard package. -

-

- Step 5) All the keyboards in the package are installed as a group. In this example, the package only has the - "GFF Amharic 7" keyboard, so it becomes the active keyboard. -

-

-

- Once the keyboard has been downloaded, you should be able to use it as normal. -
- To learn how to create a custom installable keyboard, - click here. -

+

Hotkey

+

+ To switch between languages while using an external keyboard (i.e. a bluetooth keyboard), a quick and easy way is to + use a hotkey combination. Press and hold 'Ctrl' and then press 'Tab'. + You should see a list of languages/keyboards. Tap on any available language of your choice. +

-

Granting storage permission

-

- If Keyman for Android is permanently denied storage access, attempts to install custom keyboards will fail with the - notification "Storage permission request was denied". Perform these steps to grant Keyman for Android access to storage -

-

- Step 1) Go to Android Settings. -
- Step 2) Depending on your device, click "Apps", "Apps & notifications", or "App permissions" and grant Keyman - storage permission. The screenshot below is from Android 7.1 Nougat. -

-

+

Accessing Keyboard Help

+

+ Step 1) Open the keyboard list by touching the globe key. +

+

+
+

+ Step 2) Touch the info icon for your keyboard. +

+

+
-

Use the Keyman Browser

-

- Step 1) Click the Keyman Browser button in the Keyman app -
- Step 2) Enter the URL of a website to visit into the address bar, for example google.com. - Keyman Browser will load the page and detect your language if it is present, and reformat it to show your - language instead of square boxes. -
- Step 3) Use the bookmark button to save the current page for browsing later. -
- Step 4) Use the Globe icon to swap between languages. -

+

Installing System Wide Keyboards

+

+ Step 1) Open the Keyman app and select the menu for additional options. + The screenshots below are of a device running Android 8.1 Oreo. +

+

+
+

+ Step 2) Select 'Get Started' and then select 'Enable Keyman as system-wide keyboard'. +

+

+
+

+ This will open an Android settings screen for 'Available virtual keyboard'. +

+

+
+

+ Step 3) Enable 'Keyman' as an available virtual keyboard. + This will bring up two confirmation dialogs. +

+

+
+

+
+

+ Select 'OK' on both. Now when the on-screen keyboard appears for apps other than Keyman, + you'll see a keyboard icon at the bottom right. +

+

+
+

+ Click the keyboard icon whenever you want to change the current system keyboard. +

+

+

-

Integrate Keyman with your App or Website

-

- If you are interested in learning how Keyman can be integrated into your own app or - website, visit keyman.com/engine -

+

Installing Custom Keyboards

+

+ Step 1) Click the link to your custom keyboard package file. The link in this example is for Khmer Angkor keyboard. +

+

+

+ Step 2) Browse to the KMP file. Once the KMP file is on your device, you will need to browse to the KMP file and select it. + If using the Chrome browser, select the Menu, then "Downloads", and select the downloaded KMP file. +

+

+

+ Otherwise, use the "Downloads" or file browser app available on your Android device to select the downloaded KMP file. + If your default file browser doesn't prompt for Keyman to open the package file, you may need to use a different file + browser from the Google Play store. +

+

+

+ Selecting the KMP file should bring you to Step 3) +

+

+ Step 3) On Android 6.0 (Marshmallow) and higher, mobile apps need to request permissions to access storage. Keyman + for Android needs access to read storage for installing the KMP file. At the dialog, select "ALLOW". Once authorized, + Keyman for Android won't need to ask for storage permission again, unless the user revokes or uninstalls the app. +

+

+

+ Older versions of Android grant Storage permissions at app installation time, so those users can skip this step. +

+

+ Step 4) Keyman for Android will parse the metadata in the package. If the keyboard package includes a "welcome.htm" + file, this will be displayed at the confirmation to install the keyboard package. +

+

+

+ If "welcome.htm" is not included, a generic page with the package ID and package version will be shown. Click the + left "Install" button to install the entire keyboard package. +

+

+ Step 5) All the keyboards in the package are installed as a group. In this example, the package only has the + "Khmer Angkor" keyboard, so it becomes the active keyboard. +

+

+

+ Once the keyboard has been downloaded, you should be able to use it as normal. +
+ To learn how to create a custom installable keyboard, + click here. +

-

Further Help

-

- For more information on Keyman, visit keyman.com -

- For more information on Keyman for Android, visit keyman.com/android -

-
+

Granting storage permission

+

+ If Keyman for Android is permanently denied storage access, attempts to install custom keyboards will fail with the + notification "Storage permission request was denied". Perform these steps to grant Keyman for Android access to storage +

+

+ Step 1) Go to Android Settings. +
+ Step 2) Depending on your device, click "Apps", "Apps & notifications", or "App permissions" and grant Keyman + storage permission. The screenshot below is from Android 8.1 Oreo. +

+

+ +

Use the Keyman Browser

+

+ Step 1) Click the Keyman Browser button in the Keyman app +
+ Step 2) Enter the URL of a website to visit into the address bar, for example google.com. + Keyman Browser will load the page and detect your language if it is present, and reformat it to show your + language instead of square boxes. +
+ Step 3) Use the bookmark button to save the current page for browsing later. +
+ Step 4) Use the Globe icon to swap between languages. +

-
-

- Keyman for Android lets you type in over 600 languages on Android mobiles and tablets. - To get started, here are some helpful hints: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Keyboard Keys
(Keyboard Present)
Install or select another language/keyboard
Hide the keyboard
Backspace
Return
Shift key. Long press this key to access the CTRL, ALT and CTRL ALT keys - (which can access additional key layers) -
Keys with a small dot in the top right corner indicate a long press key. Access - further functionality by long pressing the key -
Toolbar Icons
Share your text via Mail, Text, Facebook or Twitter
Open the Keyman Browser to use the web in your language
Adjust the font size
Delete all current text
Link to this info page
Open the initial setup screen
-

+

Integrate Keyman with your App or Website

+

+ If you are interested in learning how Keyman can be integrated into your own app or + website, visit keyman.com/engine +

-

Switching between Keyboards

-

- To switch keyboards, follow these steps: -

- Step 1) -
- With the keyboard visible, touch the globe key: -
- -
- This will bring up a list of all currently installed languages (the default is English EuroLatin2). - If you have already downloaded additional languages, they will appear here. - Simply select them and the keyboard will re-appear with the new language. -

- If you haven't installed any languages as yet, touch the + - button in the top right corner of your screen. -

- Step 2) -
- A list of all available languages will appear. - Scroll through this list until you find the language you want to install. - Some languages may have multiple keyboards - for example we have 10+ different Tamil keyboards. -
- -

- Select the language and keyboard you want to install - a popup will ask for confirmation, hit the Download button. -
- -

- Step 3) -
- Once the download is complete, the new keyboard will be selected. Touch the Return button on the top left corner of the popup and the new - keyboard will become active. -
- -

+

Further Help

+

+ For more information on Keyman, visit keyman.com

+ For more information on Keyman for Android, visit keyman.com/android +

-

Hotkey

-

- To switch between languages while using an external keyboard (i.e. a bluetooth keyboard), a quick and easy way is to - use a hotkey combination. Press and hold 'Ctrl' and then press 'Tab'. - You should see a list of languages/keyboards. Tap on any available language of your choice. -

+
+ +
+

+ Keyman for Android lets you type in over 600 languages on Android mobiles and tablets. + To get started, here are some helpful hints: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Keyboard Keys
(Keyboard Present)
Install or select another language/keyboard
Hide the keyboard
Backspace
Return
Shift key. Long press this key to access the CTRL, ALT and CTRL ALT keys + (which can access additional key layers) +
Keys with a small dot in the top right corner indicate a long press key. Access + further functionality by long pressing the key +
Toolbar Icons
Share your text via Mail, Text, or Twitter
Open the Keyman Browser to use the web in your language
Adjust the font size
Delete all current text
Load this help page
Open the initial setup screen
-

Accessing Keyboard Help

-

- Step 1) Open the keyboard list by touching the globe key. -

-

+

Switching between Keyboards

+

+ To switch keyboards, follow these steps: +

+ Step 1) +
+ With the keyboard visible, touch the globe key: +
+
-

- Step 2) Touch the info icon for your keyboard. -

-

+ This will bring up a list of all currently installed languages (the default is English + EuroLatin (SIL)). If you have already downloaded additional languages, they will appear here. + Simply select them and the keyboard will re-appear with the new language. +

+ If you haven't installed any languages as yet, touch the + + button in the bottom right corner of your screen. +
+ +

+ Step 2)
+ A list of all available languages will appear. Scroll through this list until you find + the language you want to install. Some languages may have multiple keyboards - for + example we have 10+ different Tamil keyboards. +
+ +

+ Select the language and keyboard you want to install - a popup will ask for confirmation, hit the Download button. +
+ +

+ Step 3) +
+ Once the download is complete, the new keyboard will be selected. Touch the + button on the top left corner of the popup and the new keyboard will become active. +
+ +

+ Alternate Step for System Keyboard) +
+ The Keyman system keyboard has an additional CLOSE KEYMAN button at the bottom left of + the "Keyboards" menu. Pushing this button will close Keyman and switch to the next non-Keyman system keyboard. +
+ +
+

-

Installing System Wide Keyboards

-

- Step 1) Open the Keyman app. The screenshots below are of a device running Android 7.1 Nougat. -

-

-
-

- Step 2) Select 'Get Started' and then select 'Enable Keyman as a system-wide keyboard'. -

-

-
-

- This will open an Android settings screen for 'Available virtual keyboard'. -

-

-
-

- Step 3) Enable 'Keyman' as an available virtual keyboard. This will bring up two confirmation dialogs. -

-

-
-

-
-

- Select 'OK' on both. Now when the on-screen keyboard appears for apps other than Keyman, you'll see a keyboard - icon at the bottom right. -

-

-
-

- Click the keyboard icon whenever you want to change the current system keyboard. -

+

Removing a Keyboard

+

+ To uninstall a keyboard, follow these steps:

+ Step 1) +
+ With the keyboard visible, touch the globe key: +
+ +

+ Step 2) +
+ A list of all available languages will appear. Scroll through this list until you find + the keyboard you want to uninstall +
+ +

+ Step 3) +
+ Long press the keyboard and release to get a prompt to delete the keyboard. +
+ +

+ Step 4) + Press "Delete" to uninstall the keyboard. You'll see a notification when the keyboard is deleted. +
+ +

-

Installing Custom Keyboards

-

- Step 1) From a Chrome browser, click the link to your keyboard package .kmp file. This example is for GFF Amharic 7. -

-

-

- Step 2) If the browser prompts to use "Keyman" to open the file, click on "Keyman" and proceed to Step 3. - Otherwise, wait for the browser to finish downloading the .kmp file. - When the browser has finished downloading the KMP file, you will need to browse to the KMP file and select it. - Select the Menu, then "Downloads", and select the downloaded KMP file. -

-

-

- Step 3) On Android 6.0 (Marshmallow) and higher, mobile apps need to request permissions to access storage. Keyman - for Android needs access to read storage for installing the KMP file. At the dialog, select "ALLOW". Once authorized, - Keyman for Android won't need to ask for storage permission again, unless the user revokes or uninstalls the app. -

-

-

- Step 4) Keyman for Android will parse the metadata in the package. If the keyboard package includes a "welcome.htm" - file, this will be displayed at the confirmation to install the keyboard package. -

-

-

- If "welcome.htm" is not included, a generic page with the package ID and package version will be shown. Click the - left "Install" button to install the entire keyboard package. -

-

- Step 5) All the keyboards in the package are installed as a group. In this example, the package only has the - "GFF Amharic 7" keyboard, so it becomes the active keyboard. -

-

-

- Once the keyboard has been downloaded, you should be able to use it as normal. -
- To learn how to create a custom installable keyboard, - click here. -

+

Hotkey

+

+ To switch between languages while using an external keyboard (i.e. a bluetooth keyboard), a quick and easy way is to + use a hotkey combination. Press and hold 'Ctrl' and then press 'Tab'. + You should see a list of languages/keyboards. Tap on any available language of your choice. +

-

Granting storage permission

-

- If Keyman for Android is permanently denied storage access, attempts to install custom keyboards will fail with the - notification "Storage permission request was denied". Perform these steps to grant Keyman for Android access to storage -

-

- Step 1) Go to Android Settings. -
- Step 2) Depending on your device, click "Apps", "Apps & notifications", or "App permissions" and grant Keyman - storage permission. The screenshot below is from Android 7.1 Nougat. -

-

+

Accessing Keyboard Help

+

+ Step 1) Open the keyboard list by touching the globe key. +

+

+
+

+ Step 2) Touch the info icon for your keyboard. +

+

+
-

Use the Keyman Browser

-

- Step 1) Click the Keyman Browser button in the Keyman app -
- Step 2) Enter the URL of a website to visit into the address bar, for example google.com. - Keyman Browser will load the page and detect your language if it is present, and reformat it to show your - language instead of square boxes. -
- Step 3) Use the bookmark button to save the current page for browsing later. -
- Step 4) Use the Globe icon to swap between languages. -

+

Installing System Wide Keyboards

+

+ Step 1) Open the Keyman app. The screenshots below are of a device running Android 8.1 Oreo. +

+

+
+

+ Step 2) Select 'Get Started' and then select 'Enable Keyman as system-wide keyboard'. +

+

+
+

+ This will open an Android settings screen for 'Available virtual keyboard'. +

+

+
+

+ Step 3) Enable 'Keyman' as an available virtual keyboard. + This will bring up two confirmation dialogs. +

+

+
+

+
+

+ Select 'OK' on both. Now when the on-screen keyboard appears for apps other than Keyman, you'll see a keyboard + icon at the bottom right. +

+

+
+

+ Click the keyboard icon whenever you want to change the current system keyboard. +

+

+

-

Integrate Keyman with your App or Website

-

- If you are interested in learning how Keyman can be integrated into your own app or website, visit - keyman.com/engine -

+

Installing Custom Keyboards

+

+ Step 1) Click the link to your custom keyboard package file. The link in this example is for Khmer Angkor keyboard. +

+

+

+ Step 2) Browse to the KMP file. Once the KMP file is on your device, you will need to browse to the KMP file and select it. + If using the Chrome browser, select the Menu, then "Downloads", and select the downloaded KMP file. +

+

+

+ Otherwise, use the "Downloads" or file browser app available on your Android device to select the downloaded KMP file. + If your default file browser doesn't prompt for Keyman to open the package file, you may need to use a different file + browser from the Google Play store. +

+

+

+ Selecting the KMP file should bring you to Step 3) +

+

+ Step 3) On Android 6.0 (Marshmallow) and higher, mobile apps need to request permissions to access storage. Keyman + for Android needs access to read storage for installing the KMP file. At the dialog, select "ALLOW". Once authorized, + Keyman for Android won't need to ask for storage permission again, unless the user revokes or uninstalls the app. +

+

+

+ Older versions of Android grant Storage permissions at app installation time, so those users can skip this step. +

+

+ Step 4) Keyman for Android will parse the metadata in the package. If the keyboard package includes a "welcome.htm" + file, this will be displayed at the confirmation to install the keyboard package. +

+

+

+ If "welcome.htm" is not included, a generic page with the package ID and package version will be shown. Click the + left "Install" button to install the entire keyboard package. +

+

+ Step 5) All the keyboards in the package are installed as a group. In this example, the package only has the + "Khmer Angkor" keyboard, so it becomes the active keyboard. +

+

+

+ Once the keyboard has been downloaded, you should be able to use it as normal. +
+ To learn how to create a custom installable keyboard, + click here. +

+ +

Granting storage permission

+

+ If Keyman for Android is permanently denied storage access, attempts to install custom keyboards will fail with the + notification "Storage permission request was denied". Perform these steps to grant Keyman for Android access to storage +

+

+ Step 1) Go to Android Settings. +
+ Step 2) Depending on your device, click "Apps", "Apps & notifications", or "App permissions" and grant Keyman + storage permission. The screenshot below is from Android 8.1 Oreo. +

+

-

Further Help

-

- For more information on Keyman, visit keyman.com -

- For more information on Keyman for Android, visit keyman.com/android -

+

Use the Keyman Browser

+

+ Step 1) Click the Keyman Browser button in the Keyman app +
+ Step 2) Enter the URL of a website to visit into the address bar, for example google.com. + Keyman Browser will load the page and detect your language if it is present, and reformat it to show your + language instead of square boxes. +
+ Step 3) Use the bookmark button to save the current page for browsing later. +
+ Step 4) Use the Globe icon to swap between languages. +

+ +

Integrate Keyman with your App or Website

+

+ If you are interested in learning how Keyman can be integrated into your own app or + website, visit keyman.com/engine +

+ +

Further Help

+

+ For more information on Keyman, visit keyman.com +

+ For more information on Keyman for Android, visit keyman.com/android +

diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index eec7a8a53ac7ad32d757901e407668d6dfe2a07a..b82072d9593a01027fe3e36726145700ad14cb95 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -14,6 +14,7 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.inputmethodservice.InputMethodService; +import android.text.InputType; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -71,7 +72,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven */ @Override public View onCreateInputView() { - //Log.i("SystemKeyboard", "onCreateInputView"); if (inputView == null) { inputView = KMManager.createInputView(this); } @@ -105,7 +105,13 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven KMManager.onStartInput(attribute, restarting); KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); - //Log.i("SystemKeyboard", "onStartInput"); + // Select numeric layer if applicable + int inputType = attribute.inputType; + if (((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_NUMBER) || + ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_PHONE)) { + KMManager.setNumericLayer(KeyboardType.KEYBOARD_TYPE_SYSTEM); + } + InputConnection ic = getCurrentInputConnection(); if (ic != null) { ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); @@ -123,13 +129,11 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { super.onStartInputView(attribute, restarting); - //Log.i("SystemKeyboard", "onStartInputView"); } @Override public void onUpdateExtractingVisibility(EditorInfo ei) { super.onUpdateExtractingVisibility(ei); - //Log.i("SystemKeyboard", "onUpdateExtractingVisibility"); } @Override diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/BookmarksActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/BookmarksActivity.java index 0e9114cdf2c4426bdf3d4fbb34ddcefd59471120..d6e7c0d40599854727dd32beca5510901510856d 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/BookmarksActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/BookmarksActivity.java @@ -23,7 +23,7 @@ import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.EditText; -import android.widget.ImageButton; +import android.support.design.widget.FloatingActionButton; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.PopupMenu; @@ -32,14 +32,14 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemLongClickListener; import android.annotation.SuppressLint; -import android.app.ActionBar; -import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.widget.Toolbar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -public class BookmarksActivity extends Activity { +public class BookmarksActivity extends AppCompatActivity { private static ListView listView; private static ArrayList> list = null; @@ -57,17 +57,17 @@ public class BookmarksActivity extends Activity { final Context context = this; final String title = getIntent().getStringExtra(titleKey); final String url = getIntent().getStringExtra(urlKey); - final ActionBar actionBar = getActionBar(); - actionBar.setLogo(null); - actionBar.setDisplayShowHomeEnabled(false); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setBackgroundDrawable(MainActivity.getActionBarDrawable(this)); - final ViewGroup bookmarksTitleLayout = (ViewGroup) getLayoutInflater().inflate( - R.layout.bookmarks_title_layout, - null); - actionBar.setCustomView(bookmarksTitleLayout); + setContentView(R.layout.bookmarks_list_layout); + + final Toolbar toolbar = (Toolbar) findViewById(R.id.bookmarks_toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setLogo(null); + getSupportActionBar().setDisplayShowHomeEnabled(false); + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setDisplayShowCustomEnabled(true); + getSupportActionBar().setBackgroundDrawable(MainActivity.getActionBarDrawable(this)); + listView = (ListView) findViewById(R.id.listView); list = getBookmarksList(); String[] from = new String[]{titleKey, urlKey, iconKey}; @@ -112,14 +112,14 @@ public class BookmarksActivity extends Activity { TextView emptyText = (TextView) findViewById(android.R.id.empty); listView.setEmptyView(emptyText); - ImageButton addButton = (ImageButton) findViewById(R.id.addButton); + FloatingActionButton addButton = (FloatingActionButton) findViewById(R.id.addButton); addButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - dialogBuilder.setTitle("Add Bookmark"); + dialogBuilder.setTitle(getString(R.string.add_bookmark)); dialogBuilder.setView(getLayoutInflater().inflate(R.layout.add_bookmark_dialog_layout, null)); - dialogBuilder.setPositiveButton("Add", new DialogInterface.OnClickListener() { + dialogBuilder.setPositiveButton(getString(R.string.label_add), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { EditText titleField = (EditText) mDialog.findViewById(R.id.title); EditText urlField = (EditText) mDialog.findViewById(R.id.url); @@ -154,7 +154,7 @@ public class BookmarksActivity extends Activity { } }); - dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + dialogBuilder.setNegativeButton(getString(R.string.label_cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Cancel } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java index 4df7ba79dc05dfa65bae7067175caeb933d5b35b..e1edd481b290b6c027296f78be16b7fc8247afb6 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/GetStartedActivity.java @@ -25,12 +25,12 @@ import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageButton; import android.widget.ListView; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -public class GetStartedActivity extends Activity { +public class GetStartedActivity extends AppCompatActivity { private static ListView listView = null; private static ArrayList> list = null; @@ -42,17 +42,10 @@ public class GetStartedActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); final Context context = this; - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - try { - int titleContainerId = (Integer) Class.forName("com.android.internal.R$id").getField("title_container").get(null); - ((ViewGroup) getWindow().findViewById(titleContainerId)).removeAllViews(); - } catch (Exception e) { - Log.e("GetStartedActivity", e.getMessage()); - } - - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.get_started_title_layout); setContentView(R.layout.get_started_list_layout); + listView = (ListView) findViewById(R.id.listView); final ImageButton closeButton = (ImageButton) findViewById(R.id.close_button); @@ -80,28 +73,29 @@ public class GetStartedActivity extends Activity { HashMap hashMap = new HashMap(); hashMap.put(iconKey, "0"); - hashMap.put(textKey, "Add a keyboard for your language"); + hashMap.put(textKey, getString(R.string.add_a_keyboard)); hashMap.put(isEnabledKey, "true"); list.add(hashMap); hashMap = new HashMap(); hashMap.put(iconKey, "0"); - hashMap.put(textKey, "Enable Keyman as system-wide keyboard"); + hashMap.put(textKey, getString(R.string.enable_system_keyboard)); hashMap.put(isEnabledKey, "true"); list.add(hashMap); hashMap = new HashMap(); hashMap.put(iconKey, "0"); - hashMap.put(textKey, "Set Keyman as default keyboard"); + hashMap.put(textKey, getString(R.string.set_keyman_as_default)); hashMap.put(isEnabledKey, "false"); list.add(hashMap); hashMap = new HashMap(); - hashMap.put(iconKey, String.valueOf(R.drawable.ic_light_action_info)); - hashMap.put(textKey, "More info"); + hashMap.put(iconKey, "0"); + hashMap.put(textKey, getString(R.string.more_info)); hashMap.put(isEnabledKey, "true"); list.add(hashMap); + String[] from = new String[]{iconKey, textKey}; int[] to = new int[]{R.id.left_icon, R.id.text}; listAdapter = new KMListAdapter(context, list, R.layout.get_started_row_layout, from, to); @@ -140,30 +134,36 @@ public class GetStartedActivity extends Activity { public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { - String checkbox_off = String.valueOf(android.R.drawable.checkbox_off_background); + // Enumerated steps are replaced with checkboxes when the user completes a step + String one = String.valueOf(R.drawable.ic_looks_one); + String two = String.valueOf(R.drawable.ic_looks_two); + String three = String.valueOf(R.drawable.ic_looks_three); String checkbox_on = String.valueOf(android.R.drawable.checkbox_on_background); + String info = String.valueOf(R.drawable.ic_info_outline); ArrayList> kbList = KMManager.getKeyboardsList(this); if (kbList != null && kbList.size() > 1) { list.get(0).put(iconKey, checkbox_on); } else { - list.get(0).put(iconKey, checkbox_off); + list.get(0).put(iconKey, one); } if (isEnabledAsSystemKB(this)) { list.get(1).put(iconKey, checkbox_on); list.get(2).put(isEnabledKey, "true"); } else { - list.get(1).put(iconKey, checkbox_off); + list.get(1).put(iconKey, two); list.get(2).put(isEnabledKey, "false"); } if (isDefaultKB(this)) { list.get(2).put(iconKey, checkbox_on); } else { - list.get(2).put(iconKey, checkbox_off); + list.get(2).put(iconKey, three); } + list.get(3).put(iconKey, info); + String[] from = new String[]{iconKey, textKey}; int[] to = new int[]{R.id.left_icon, R.id.text}; listAdapter = new KMListAdapter(this, list, R.layout.get_started_row_layout, from, to); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/InfoActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/InfoActivity.java index 9945b8e4ee4384d8ae60f0f9d0f71948f8db44e0..11aa056735a6ca1b24025d41762af4b2991e6f97 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/InfoActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/InfoActivity.java @@ -9,24 +9,26 @@ import java.util.HashMap; import com.tavultesoft.kmea.KMManager; -import android.net.Uri; +import android.content.Context; +import android.graphics.Bitmap; import android.os.Bundle; import android.view.Gravity; import android.view.View; -import android.webkit.WebSettings.LayoutAlgorithm; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.TextView; import android.annotation.SuppressLint; -import android.app.ActionBar; -import android.app.Activity; -import android.content.Intent; +import android.support.v7.widget.Toolbar; +import android.support.v7.app.AppCompatActivity; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; -public class InfoActivity extends Activity { +public class InfoActivity extends AppCompatActivity { - private final String kmBaseUrl = "http://keyman.com/android/app/"; + private WebView webView; + private final String kmBaseUrl = "https://keyman.com/android/app/"; private String kmUrl = ""; private final String htmlPath = "file:///android_asset/info/info.html"; @@ -34,31 +36,35 @@ public class InfoActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - //getWindow().requestFeature(Window.FEATURE_PROGRESS); + final Context context = this; + + setContentView(R.layout.activity_info); + + Toolbar toolbar = findViewById(R.id.info_toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setTitle(null); + getSupportActionBar().setDisplayUseLogoEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setLogo(R.drawable.keyman_logo); + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setDisplayShowCustomEnabled(true); - final ActionBar actionBar = getActionBar(); - actionBar.setLogo(R.drawable.keyman_logo); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setBackgroundDrawable(MainActivity.getActionBarDrawable(this)); TextView version = new TextView(this); - version.setWidth((int) getResources().getDimension(R.dimen.version_label_width)); - version.setTextSize(getResources().getDimension(R.dimen.version_label_textsize)); + version.setWidth((int) getResources().getDimension(R.dimen.label_width)); + version.setTextSize(getResources().getDimension(R.dimen.titlebar_label_textsize)); version.setGravity(Gravity.CENTER); String ver = ""; PackageInfo pInfo; try { pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); - ver = String.format("Version: %s", pInfo.versionName); + ver = String.format("%s: %s", getString(R.string.title_version), pInfo.versionName); } catch (NameNotFoundException e) { // Could not get version number } - version.setText(ver); - actionBar.setCustomView(version); + getSupportActionBar().setCustomView(version); - setContentView(R.layout.activity_info); String currentKbID = KMManager.KMDefault_KeyboardID; HashMap curKbInfo = KMManager.getCurrentKeyboardInfo(this); @@ -83,46 +89,44 @@ public class InfoActivity extends Activity { installedKbs = currentKbID; kmUrl = String.format("%s?active=%s&installed=%s", kmBaseUrl, currentKbID, installedKbs); - WebView webView = (WebView) findViewById(R.id.webView); - webView.getSettings().setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); + webView = (WebView) findViewById(R.id.infoWebView); + webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setUseWideViewPort(true); webView.getSettings().setLoadWithOverviewMode(true); webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - /* - final Activity activity = this; + webView.setWebChromeClient(new WebChromeClient() { - public void onProgressChanged(WebView view, int progress) { - activity.setProgress(progress * 1000); - } }); - */ - webView.setWebViewClient(new WebViewClient() { @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - // Handle the error } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url.equals(kmUrl) || url.equals(htmlPath)) { + if (url != null && !url.toLowerCase().equals("about:blank")) { view.loadUrl(url); - } else { - Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(i); - } + } return true; } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + } + + @Override + public void onPageFinished(WebView view, String url) { + } }); - if (KMManager.hasConnection(this)) { + if (KMManager.hasConnection(context)) { // Load app info page from server webView.loadUrl(kmUrl); } else { - webView.loadUrl(htmlPath); // Load app info page from assets + webView.loadUrl(htmlPath); } } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index a5b128cf330d13e2291f34a03043dc3694f9f490..b7af39f8c6f339de6085ae5ecf0fefd8fc340c87 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -39,9 +39,9 @@ import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.app.Activity; -import android.app.ActionBar; -import android.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.support.v7.app.AlertDialog; import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; @@ -65,6 +65,7 @@ import android.support.v4.app.ActivityCompat; import android.text.Html; import android.util.Log; import android.util.TypedValue; +import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -75,7 +76,7 @@ import android.view.WindowManager; import android.widget.SeekBar; import android.widget.Toast; -public class MainActivity extends Activity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener, +public class MainActivity extends AppCompatActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener, ActivityCompat.OnRequestPermissionsResultCallback { // Fields used for installing kmp packages @@ -93,7 +94,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O private static final String userTextSizeKey = "UserTextSize"; protected static final String dontShowGetStartedKey = "DontShowGetStarted"; protected static final String didCheckUserDataKey = "DidCheckUserData"; + private Toolbar toolbar; private Menu menu; + DownloadResultReceiver resultReceiver; private ProgressDialog progressDialog; @@ -130,12 +133,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O @Override protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); resultReceiver = new DownloadResultReceiver(new Handler()); - final ActionBar actionBar = getActionBar(); - actionBar.setLogo(R.drawable.keyman_logo); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setBackgroundDrawable(getActionBarDrawable(this)); mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); @@ -145,6 +145,16 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP); setContentView(R.layout.activity_main); + + toolbar = (Toolbar) findViewById(R.id.titlebar); + setSupportActionBar(toolbar); + getSupportActionBar().setTitle(null); + getSupportActionBar().setDisplayUseLogoEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setLogo(R.drawable.keyman_logo); + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); + textView = (KMTextView) findViewById(R.id.kmTextView); SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); textView.setText(prefs.getString(userTextKey, "")); @@ -152,9 +162,6 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O textView.setTextSize((float) textSize); textView.setSelection(textView.getText().length()); - - - boolean didCheckUserData = prefs.getBoolean(MainActivity.didCheckUserDataKey, false); if (!didCheckUserData && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)) { try { @@ -192,7 +199,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O File newFile = new File(getDir("userdata", Context.MODE_PRIVATE), filename); copyFile(inputStream, newFile); inputStream.close(); - } else if ((filename.endsWith(".js") || filename.endsWith(".ttf") || filename.endsWith(".otf"))) { + } else if (FileUtils.hasJavaScriptExtension(filename) || FileUtils.hasFontExtension(filename)) { File packagesDir = new File(getDir("data", Context.MODE_PRIVATE) + File.separator + KMManager.KMDefault_AssetPackages); if (!packagesDir.exists()) { @@ -264,8 +271,6 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O case "file": checkStoragePermission(data); break; - // Intending to deprecate keyman:// protocol in https://github.com/keymanapp/keyman/issues/538 - case "keyman" : case "http" : case "https" : Intent downloadIntent; @@ -283,7 +288,8 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O filename = url.substring(index); } - if (url.endsWith(".kmp")) { + // Only handle ad-hoc kmp packages + if (FileUtils.hasKeyboardPackageExtension(url)) { try { // Download the KMP to app cache downloadIntent = new Intent(MainActivity.this, DownloadIntentService.class); @@ -292,7 +298,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O downloadIntent.putExtra("receiver", resultReceiver); progressDialog = new ProgressDialog(MainActivity.this); - progressDialog.setMessage("Downloading keyboard package\n" + filename + "..."); + String ellipsisStr = "\u2026"; + progressDialog.setMessage(String.format("%s\n%s%s", + getString(R.string.downloading_keyboard_package), filename, ellipsisStr)); progressDialog.setCancelable(false); progressDialog.show(); @@ -305,23 +313,14 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O break; } } else { - // Legacy ad-hoc keyboard (JSON) distribution - downloadIntent = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class); - Bundle bundle = new Bundle(); - bundle.putString(KMKeyboardDownloaderActivity.ARG_KEYBOARD, - data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_Keyboard)); - bundle.putString(KMKeyboardDownloaderActivity.ARG_LANGUAGE, - data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_Language)); - bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, isCustom); - bundle.putString(KMKeyboardDownloaderActivity.ARG_URL, url); - bundle.putString(KMKeyboardDownloaderActivity.ARG_FILENAME, filename); - downloadIntent.putExtras(bundle); - startActivity(downloadIntent); + String message = "Download failed. Not a .kmp keyboard package."; + Toast.makeText(getApplicationContext(), message, + Toast.LENGTH_SHORT).show(); } } break; default : - Log.d(TAG, "Unrecognized protocol " + data.getScheme()); + Log.e(TAG, "Unrecognized protocol " + data.getScheme()); } } intent.setData(null); @@ -352,7 +351,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - getActionBar().setBackgroundDrawable(getActionBarDrawable(this)); + getSupportActionBar().setBackgroundDrawable(getActionBarDrawable(this)); resizeTextView(textView.isKeyboardVisible()); invalidateOptionsMenu(); } @@ -512,9 +511,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O final View textSizeController = inflater.inflate(R.layout.text_size_controller, null); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this); dialogBuilder.setIcon(R.drawable.ic_light_action_textsize); - dialogBuilder.setTitle(String.format("Text Size: %d", textSize)); + dialogBuilder.setTitle(String.format("%s: %d", getString(R.string.action_text_size), textSize)); dialogBuilder.setView(textSizeController); - dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + dialogBuilder.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Done @@ -543,7 +542,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { textSize = progress + minTextSize; textView.setTextSize((float) textSize); - dialog.setTitle(String.format("Text Size: %d", textSize)); + dialog.setTitle(String.format("%s: %d", getString(R.string.action_text_size), textSize)); } }); @@ -571,16 +570,16 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O private void showClearTextDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this); dialogBuilder.setIcon(R.drawable.ic_light_action_trash); - dialogBuilder.setTitle("Clear Text"); - dialogBuilder.setMessage("\nAll text will be cleared\n"); - dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + dialogBuilder.setTitle(getString(R.string.action_clear_text)); + dialogBuilder.setMessage(String.format("\n%s\n", getString(R.string.all_text_will_be_cleared))); + dialogBuilder.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { textView.setText(""); } }); - dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + dialogBuilder.setNegativeButton(getString(R.string.label_cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Cancel @@ -614,15 +613,12 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PERMISSION_REQUEST_STORAGE) { // Request for storage permission - Log.d(TAG, "Received response for Storage permission request"); if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission has been granted. Resume task needing this permission - Log.d(TAG, "Permission: " + permissions[0] + " was " + grantResults[0]); useLocalKMP(data); } else { // Permission request denied String message = "Storage permission request was denied. Unable to install keyboard package"; - Log.d(TAG, message); Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); } @@ -634,16 +630,13 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Read external storage permission granted"); useLocalKMP(data); } else { - Log.d(TAG, "Read external storage permission denied"); // Permission is missing and must be requested requestStoragePermission(); } } else { // Permission automatically granted on older Android versions - Log.d(TAG, "Read external storage permission granted"); useLocalKMP(data); } } @@ -674,7 +667,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O InputStream inputFile = null; Bundle bundle = new Bundle(); try { - boolean fileEndsWithKMP = false; + boolean isKMP = false; switch (data.getScheme().toLowerCase()) { case "content": // DownloadManager passes a path "/document/number" so we need to extract the .kmp filename @@ -682,7 +675,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O cursor.moveToFirst(); int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); filename = cursor.getString(nameIndex); - fileEndsWithKMP = filename.endsWith(".kmp"); + isKMP = FileUtils.hasKeyboardPackageExtension(filename); cacheKMPFilename = filename; inputFile = getContentResolver().openInputStream(data); break; @@ -690,20 +683,19 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O case "file": File kmpFile = new File(data.getPath()); filename = kmpFile.getName(); - fileEndsWithKMP = data.toString().endsWith(".kmp"); + isKMP = FileUtils.hasKeyboardPackageExtension(data.toString()); cacheKMPFilename = kmpFile.getName(); inputFile = new FileInputStream(kmpFile); break; } - if (fileEndsWithKMP) { + if (isKMP) { // Copy KMP to app cache cacheKMPFile = new File(MainActivity.this.getCacheDir().toString(), cacheKMPFilename); if (cacheKMPFile.exists()) { cacheKMPFile.delete(); } - Log.d(TAG, "Copying " + filename + " to app cache"); FileUtils.copy(inputFile, new FileOutputStream(cacheKMPFile)); } else { String noKeyboardsInstalledMessage = " is not a valid keyboard package file.\nNo keyboards were installed."; 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 dc35ae42eb45324f30bb32ddb4e6c24f5f3c5dfd..b60afe44a488a96700e0bc26529a009bda7a2728 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 @@ -1,9 +1,9 @@ package com.tavultesoft.kmapro; import android.annotation.SuppressLint; -import android.app.ActionBar; -import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.widget.Toolbar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -32,8 +32,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -public class PackageActivity extends Activity { +public class PackageActivity extends AppCompatActivity { + private Toolbar toolbar; private WebView webView; private AlertDialog alertDialog; private File kmpFile; @@ -45,6 +46,8 @@ public class PackageActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_package_installer); + final Context context = this; Bundle bundle = getIntent().getExtras(); if (bundle != null) { @@ -53,43 +56,38 @@ public class PackageActivity extends Activity { final String pkgId = PackageProcessor.getPackageID(kmpFile); String pkgVersion = PackageProcessor.getPackageVersion(kmpFile, false); - if (pkgVersion == null || pkgVersion.isEmpty()) { - String message = "Invalid package version in " + kmpFile.getName(); - showErrorDialog(context, pkgId, message); - } String pkgName = PackageProcessor.getPackageName(kmpFile, false); try { tempPackagePath = PackageProcessor.unzipKMP(kmpFile); } catch (Exception e) { - String message = "Failed to extract\n" + kmpFile.getAbsolutePath(); + String message = String.format("%s\n%s", + getString(R.string.failed_to_extract), kmpFile.getAbsolutePath()); showErrorDialog(context, pkgId, message); } - final ActionBar actionBar = getActionBar(); - actionBar.setLogo(R.drawable.keyman_logo); - actionBar.setDisplayShowHomeEnabled(false); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setBackgroundDrawable(MainActivity.getActionBarDrawable(this)); + toolbar = (Toolbar) findViewById(R.id.titlebar); + setSupportActionBar(toolbar); + getSupportActionBar().setTitle(null); + getSupportActionBar().setDisplayUseLogoEnabled(false); + getSupportActionBar().setDisplayShowHomeEnabled(false); + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setDisplayShowCustomEnabled(true); + getSupportActionBar().setBackgroundDrawable(MainActivity.getActionBarDrawable(this)); + TextView packageActivityTitle = new TextView(this); packageActivityTitle.setWidth((int) getResources().getDimension(R.dimen.package_label_width)); - packageActivityTitle.setTextSize(getResources().getDimension(R.dimen.package_label_textsize)); + packageActivityTitle.setTextSize(getResources().getDimension(R.dimen.titlebar_label_textsize)); packageActivityTitle.setGravity(Gravity.CENTER); - String titleStr = "Install Keyboard Package"; - if (pkgVersion != null) { - titleStr += " " + pkgVersion; - } + String titleStr = String.format("%s %s", getString(R.string.install_keyboard_package), pkgVersion); packageActivityTitle.setText(titleStr); - actionBar.setCustomView(packageActivityTitle); - - setContentView(R.layout.activity_package_installer); + getSupportActionBar().setCustomView(packageActivityTitle); final Button installButton = (Button) findViewById(R.id.installButton); final Button cancelButton = (Button) findViewById(R.id.cancelButton); - webView = (WebView) findViewById(R.id.webView); + webView = (WebView) findViewById(R.id.packageWebView); webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setUseWideViewPort(true); @@ -110,7 +108,7 @@ public class PackageActivity extends Activity { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (!url.toLowerCase().equals("about:blank")) + if (url != null && !url.toLowerCase().equals("about:blank")) view.loadUrl(url); return true; @@ -141,7 +139,8 @@ public class PackageActivity extends Activity { webView.loadUrl("file:///" + files[0].getAbsolutePath()); } else { // No welcome.htm so display minimal package information - String keyboardString = (pkgName.toLowerCase().endsWith("keyboard")) ? "" : " Keyboard "; + String keyboardString = (pkgName != null && pkgName.toLowerCase().endsWith("keyboard")) ? "" : + String.format(" %s", getString(R.string.title_keyboard)); String htmlString = String.format( "

The %s%s Package

", pkgName, keyboardString); @@ -163,12 +162,12 @@ public class PackageActivity extends Activity { } cleanup(); } else { - showErrorDialog(context, pkgId, "No new touch-optimized keyboards to install"); + showErrorDialog(context, pkgId, getString(R.string.no_new_touch_keyboards_to_install)); } } catch (Exception e) { Log.e("PackageActivity", "Error " + e); - showErrorDialog(context, pkgId, "No valid touch-optimized keyboards to install"); + showErrorDialog(context, pkgId, getString(R.string.no_valid_touch_keyboards_to_install)); } } }); @@ -218,11 +217,12 @@ public class PackageActivity extends Activity { private void showErrorDialog(Context context, String pkgId, String message) { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); - alertDialogBuilder.setTitle("Package " + pkgId + " failed to install"); + alertDialogBuilder.setTitle(String.format("%s %s %s", + getString(R.string.title_package), pkgId, getString(R.string.title_failed_to_install))); alertDialogBuilder .setMessage(message) .setCancelable(false) - .setNeutralButton("Close",new DialogInterface.OnClickListener() { + .setPositiveButton(getString(R.string.label_close),new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog,int id) { if (dialog != null) { dialog.dismiss(); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..68378bf4fc8915e79bc01ee85649e1cc41a46137 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SplashScreenActivity.java @@ -0,0 +1,49 @@ +package com.tavultesoft.kmapro; + +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.app.Activity; +import android.content.Intent; +import android.widget.TextView; + +import java.util.Calendar; + +public class SplashScreenActivity extends Activity { + + // For now, disable this Activity so app is not delayed showing version/copyright text + private static int SPLASH_TIME_OUT = 0; // msec + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_splash_screen); + + TextView version = (TextView)findViewById(R.id.splash_version); + String ver = ""; + PackageInfo pInfo; + try { + pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + ver = String.format("%s: %s", version.getText(), pInfo.versionName); + } catch (PackageManager.NameNotFoundException e) { + // Could not get version number + } + version.setText(ver); + + TextView copyright = (TextView)findViewById(R.id.splash_copyright); + int year = Calendar.getInstance().get(Calendar.YEAR); + String date = String.format("%s%s", copyright.getText(), year); + copyright.setText(date); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + Intent intent = new Intent(getApplicationContext(), + MainActivity.class); + startActivity(intent); + finish(); + } + }, SPLASH_TIME_OUT); + } +} diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/WebBrowserActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/WebBrowserActivity.java index bb9a8c0ef4451a6e8118fd7b9f4aab7c9bf0dafb..c2367096544b78f3198ca9f44f75dcabe23b571f 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/WebBrowserActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/WebBrowserActivity.java @@ -38,10 +38,10 @@ import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; -import android.app.ActionBar; -import android.app.Activity; +import android.support.v7.widget.Toolbar; +import android.support.v7.app.AppCompatActivity; -public class WebBrowserActivity extends Activity { +public class WebBrowserActivity extends AppCompatActivity { private WebView webView; private EditText addressField; @@ -58,21 +58,21 @@ public class WebBrowserActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final Context context = this; - final ActionBar actionBar = getActionBar(); - actionBar.setLogo(null); - actionBar.setDisplayShowHomeEnabled(false); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setBackgroundDrawable(MainActivity.getActionBarDrawable(this)); - final ViewGroup webBarLayout = (ViewGroup) getLayoutInflater().inflate( - R.layout.web_browser_bar_layout, - null); - actionBar.setCustomView(webBarLayout); + setContentView(R.layout.activity_web_browser); - webView = (WebView) findViewById(R.id.webView); + + final Toolbar toolbar = findViewById(R.id.web_browser_toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setTitle(null); + getSupportActionBar().setDisplayUseLogoEnabled(false); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setDisplayShowCustomEnabled(true); + getSupportActionBar().setBackgroundDrawable(MainActivity.getActionBarDrawable(this)); + + webView = (WebView) findViewById(R.id.browserWebView); addressField = (EditText) findViewById(R.id.address_field); clearButton = (ImageButton) findViewById(R.id.clear_button); stopButton = (ImageButton) findViewById(R.id.stop_button); @@ -332,8 +332,6 @@ public class WebBrowserActivity extends Activity { protected void onResume() { super.onResume(); if (webView != null) { - webView.resumeTimers(); - if (didFinishLoading) { String fontFilename = KMManager.getKeyboardTextFontFilename(); if (!loadedFont.equals(fontFilename)) { @@ -346,9 +344,6 @@ public class WebBrowserActivity extends Activity { @Override protected void onPause() { super.onPause(); - if (webView != null) { - webView.pauseTimers(); - } } @Override diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_action_no1.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_action_no1.png deleted file mode 100644 index 68ae16f353b86bf63d4c8836f6836045f160227c..0000000000000000000000000000000000000000 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_action_no1.png and /dev/null differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_action_no2.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_action_no2.png deleted file mode 100644 index ed177fbe4d89ae61b0800741821235fa35177965..0000000000000000000000000000000000000000 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_action_no2.png and /dev/null differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_info.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_info_outline.png similarity index 100% rename from android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_light_action_info.png rename to android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_info_outline.png diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_launcher_round.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..7dd8b598eae7b296a5c26813e5d227b0fe86cc98 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_launcher_round.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_one.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_one.png new file mode 100644 index 0000000000000000000000000000000000000000..d663fa386c039b1b7a27a3566bd89fe6715c99f0 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_one.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_three.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_three.png new file mode 100644 index 0000000000000000000000000000000000000000..120c3d13ef2c3525238c83007d30b6060aed9cef Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_three.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_two.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_two.png new file mode 100644 index 0000000000000000000000000000000000000000..45ace5d9d9b4e6444d3602edea7f7ed4fcf4317f Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/ic_looks_two.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_logo_splash.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_logo_splash.9.png new file mode 100644 index 0000000000000000000000000000000000000000..06b1e9094f90781f3bfd7aa8c403dc1c8e0d4ba1 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/keyman_logo_splash.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_blue_2017_1.9.png new file mode 100644 index 0000000000000000000000000000000000000000..ddc290534715e10ef1481cac1f36f9f0729f3e0c Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_blue_2017_1.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_action_no1.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_action_no1.png deleted file mode 100644 index d7b8777350824c7c41e0deb542d5e4490a640f26..0000000000000000000000000000000000000000 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_action_no1.png and /dev/null differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_action_no2.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_action_no2.png deleted file mode 100644 index effbedcd2dd688172b6c265c5dd3f1aa4bc99462..0000000000000000000000000000000000000000 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_action_no2.png and /dev/null differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_info_outline.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_info_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..e0c9fe0eb8457013fbfa6e2efea61fca70bb9303 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_info_outline.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_launcher_round.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..ab69e103370560ef297b5aed865b7b7970aba259 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_launcher_round.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_one.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_one.png new file mode 100644 index 0000000000000000000000000000000000000000..ace590b70787b1bbd379fb2258518195433e3da3 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_one.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_three.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_three.png new file mode 100644 index 0000000000000000000000000000000000000000..a74632c838cde7a6ecfae1e44f02c52b5234081b Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_three.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_two.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_two.png new file mode 100644 index 0000000000000000000000000000000000000000..9a7699405216fd3b8d2e9fa0821c99e8ea475a4b Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_looks_two.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/keyman_logo_splash.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/keyman_logo_splash.9.png new file mode 100644 index 0000000000000000000000000000000000000000..8090f451285f4c870e338dbc256b5c2af71e3ad3 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/keyman_logo_splash.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_blue_2017_1.9.png new file mode 100644 index 0000000000000000000000000000000000000000..0e720d69a11607138e36317e5d48c8a8182413a5 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_blue_2017_1.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_action_no1.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_action_no1.png deleted file mode 100644 index 4e11377f23f910d34d110a42dcac728220d545da..0000000000000000000000000000000000000000 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_action_no1.png and /dev/null differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_action_no2.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_action_no2.png deleted file mode 100644 index cfd9cbbd684078825a4496614db77f5d10c1fb22..0000000000000000000000000000000000000000 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_action_no2.png and /dev/null differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_light_action_info.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_info_outline.png similarity index 100% rename from android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_light_action_info.png rename to android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_info_outline.png diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_launcher_round.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..444310ccedebe14f64fdd0481b02f127728d7060 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_launcher_round.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_one.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_one.png new file mode 100644 index 0000000000000000000000000000000000000000..26cfe0c1fa8ac04a712fbb0d476dd433c4407a1f Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_one.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_three.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_three.png new file mode 100644 index 0000000000000000000000000000000000000000..c347a537f1d106378772a5f4eef27fe3dbcbb244 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_three.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_two.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_two.png new file mode 100644 index 0000000000000000000000000000000000000000..445b73c9032a53574891f8965564e61e4858877c Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/ic_looks_two.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/keyman_logo_splash.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/keyman_logo_splash.9.png new file mode 100644 index 0000000000000000000000000000000000000000..8801addd47360d73e3056c38b1246ca61ca13dab Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/keyman_logo_splash.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_blue_2017_1.9.png new file mode 100644 index 0000000000000000000000000000000000000000..17f25f6cce6a1b32d97459678768d278a12ddd59 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_blue_2017_1.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_action_no1.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_action_no1.png deleted file mode 100644 index c9697613440f3cc68139c90e492d88d8a1223fef..0000000000000000000000000000000000000000 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_action_no1.png and /dev/null differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_action_no2.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_action_no2.png deleted file mode 100644 index e8ea447fe65bd1e716aae172456743063d10edd9..0000000000000000000000000000000000000000 Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_action_no2.png and /dev/null differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_info_outline.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_info_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..3847a9fe74774d3593dd694cff48ccca13cdec5a Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_info_outline.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_launcher_round.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..e3a2e6b8ad8dd42e123f56ee7270e07d6a38ff19 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_launcher_round.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_one.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_one.png new file mode 100644 index 0000000000000000000000000000000000000000..13d737392fcef5b4ffb2619bb6b8b1027edf5787 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_one.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_three.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_three.png new file mode 100644 index 0000000000000000000000000000000000000000..023ff6c9ca8bf1a207923eeca03f8c0e3c43ef74 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_three.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_two.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_two.png new file mode 100644 index 0000000000000000000000000000000000000000..4c322087c6b7fc5f974b03914174b274af2dd9e1 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_looks_two.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/keyman_logo_splash.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/keyman_logo_splash.9.png new file mode 100644 index 0000000000000000000000000000000000000000..600ff95544b368644dc7d76df3ae3f82294a474d Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/keyman_logo_splash.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_blue_2017_1.9.png new file mode 100644 index 0000000000000000000000000000000000000000..014572d691c16f5a4756eb40360e299522d1e084 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_blue_2017_1.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_info_outline.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_info_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..f41f4010a622518310c844a6b3ba9258455ff881 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_info_outline.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_launcher_round.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..d7503dec54ffbdba442c2b6c8e75d6fcb603bd1f Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_launcher_round.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_one.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_one.png new file mode 100644 index 0000000000000000000000000000000000000000..d00b34db47b9efa6cd292e678d8ff85287e10527 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_one.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_three.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_three.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8644b42153382b011d6dcb1e3ecd84f264f36a Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_three.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_two.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_two.png new file mode 100644 index 0000000000000000000000000000000000000000..756dcf96d1e55223729ab36a8e9b4809b4178106 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/ic_looks_two.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/keyman_logo_splash.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/keyman_logo_splash.9.png new file mode 100644 index 0000000000000000000000000000000000000000..381fe09d35a79ae9c340048835dd26143046a480 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/keyman_logo_splash.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_blue_2017_1.9.png new file mode 100644 index 0000000000000000000000000000000000000000..04366bd151adf19c387494e57f18ada3bddf0e44 Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_blue_2017_1.9.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml new file mode 100644 index 0000000000000000000000000000000000000000..cb6b2cbabfe03c20e725bebca184c42ad5997c6b --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/textview_bg.xml b/android/KMAPro/kMAPro/src/main/res/drawable/textview_bg.xml index 6ef8a3adafd62f256aacfbc8ecbc474f0345af6b..29cf5488e717ca5801ecc868b38b250321eb7777 100644 --- a/android/KMAPro/kMAPro/src/main/res/drawable/textview_bg.xml +++ b/android/KMAPro/kMAPro/src/main/res/drawable/textview_bg.xml @@ -2,6 +2,5 @@ - - + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml index 8750492213412fa23d666b249d45cb5266034d3d..2a2b2aaf42a64f7f6c910f6db06a4260003a3bcf 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml @@ -1,15 +1,24 @@ - + android:orientation="vertical" + tools:context=".InfoActivity" > + + + android:layout_height="match_parent" /> - + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml index a9779e37c68aea6129035418df3cf21a57bd3360..adffd02f59c84127146499e2f834cfa5801dd254 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml @@ -3,15 +3,20 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" + android:layout_above="@+id/KMKeyboard" + android:orientation="vertical" android:background="@color/yellow_background" - tools:context=".MainActivity" > + tools:context=".MainActivity"> + + - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_package_installer.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_package_installer.xml index efeea07e0aa4757ba432e18bd3c66612210e1c46..00c6cc58370a3160ed9cbbae7475e738740b9757 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_package_installer.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_package_installer.xml @@ -1,15 +1,18 @@ + + android:elevation="1dp" + android:singleLine="true" + android:layout_marginStart="10dp" android:layout_marginEnd="15dp" /> + + - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_splash_screen.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_splash_screen.xml new file mode 100644 index 0000000000000000000000000000000000000000..ef441602782bb397d15dfd41bb79aa25658c63a1 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_splash_screen.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_web_browser.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_web_browser.xml index 61a4ef87a1b27d801cfae616df4130bb99e8ffe6..58cd3261468c748158e8fffe8ed13230cf20f8b8 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_web_browser.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_web_browser.xml @@ -3,14 +3,18 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".WebBrowserActivity" > + tools:context=".WebBrowserActivity" + android:orientation="vertical"> + + - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_list_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_list_layout.xml index 69d3e511ea538ad073ebf347715c1478b344c8e8..927338347e8fc12e67ec49c0cad8f39eab172639 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_list_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_list_layout.xml @@ -1,42 +1,43 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> + + + - - - - - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_row_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_row_layout.xml index fad98b8e0b45d1e9d6b84892154813f02daa6754..a5a8d30937b0142ee28057d73e86625d4d8e3d96 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_row_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_row_layout.xml @@ -45,4 +45,4 @@ android:maxLines="1" /> - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_title_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_title_layout.xml deleted file mode 100644 index 623aabd579bd638db4a544b54e48cdd8aa10a0e9..0000000000000000000000000000000000000000 --- a/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_title_layout.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_toolbar.xml b/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_toolbar.xml new file mode 100644 index 0000000000000000000000000000000000000000..1bcd618e62b4adfeb26662a5c4470b68ba4ed09c --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/bookmarks_toolbar.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/get_started_list_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/get_started_list_layout.xml index 82248a3a84eb1c0e9e9b4ff6800ecf93e776ed6e..29273782651a17f85e4a6cd08a163bec4776c253 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/get_started_list_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/get_started_list_layout.xml @@ -1,8 +1,12 @@ - + + + - + android:layout_height="wrap_content" > + - + android:background="@color/keyman_orange" /> + - + - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/get_started_row_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/get_started_row_layout.xml index 429f2e17f196f0f7d430a1c127d05737bd4087a8..c42a307754f92a768690e1cc8f077dd00505c5b8 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/get_started_row_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/get_started_row_layout.xml @@ -8,7 +8,8 @@ style="?android:attr/actionButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" + android:layout_marginLeft="@dimen/checkbox_margin" + android:layout_marginStart="@dimen/checkbox_margin" android:layout_centerVertical="true" android:contentDescription="@string/image_view" /> @@ -17,8 +18,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/left_icon" + android:layout_toEndOf="@id/left_icon" android:layout_centerVertical="true" android:textSize="14sp" android:textStyle="normal" /> - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/get_started_title_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/get_started_title_layout.xml deleted file mode 100644 index 463865c5b6ba1192ed8281f57e42f4b7022d171f..0000000000000000000000000000000000000000 --- a/android/KMAPro/kMAPro/src/main/res/layout/get_started_title_layout.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/layout/get_started_toolbar.xml b/android/KMAPro/kMAPro/src/main/res/layout/get_started_toolbar.xml new file mode 100644 index 0000000000000000000000000000000000000000..3ac8a694f119562015c019406f65cfeb718b7089 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/get_started_toolbar.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/titlebar.xml b/android/KMAPro/kMAPro/src/main/res/layout/titlebar.xml new file mode 100644 index 0000000000000000000000000000000000000000..1c6bcf0dcd4802bc7b96c1ac57209ed2e00b3b46 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/layout/titlebar.xml @@ -0,0 +1,8 @@ + + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml index 11ee93869e6ae2895a4f66498926bf7fc6474b1e..0b0a6e99509029e4357f9989d2b3d11c2dd590cd 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml @@ -1,76 +1,86 @@ - + android:layout_height="?attr/actionBarSize" + android:id="@+id/web_browser_toolbar"> - + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:layout_margin="3dp" + android:padding="4dp" + android:layout_alignParentLeft="true" + android:layout_above="@+id/progressBar" + android:weightSum="2" + android:background="@drawable/address_field_bg" + android:orientation="horizontal"> - - - - - + - - - - - \ No newline at end of file + + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/menu-land/main.xml b/android/KMAPro/kMAPro/src/main/res/menu-land/main.xml index 56629387547521fca73cafc5a8e1d769e9acea47..5a077c99f4e10b9792eb63517aa797bb6a9177c2 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu-land/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu-land/main.xml @@ -1,39 +1,42 @@ - + - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/menu-sw600dp/main.xml b/android/KMAPro/kMAPro/src/main/res/menu-sw600dp/main.xml index ffd1337b7098758a50841dc30b5b2342ea3c80f5..7263dda25322c50e759d1bcd78faa8733ddede69 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu-sw600dp/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu-sw600dp/main.xml @@ -1,39 +1,42 @@ - - + + - \ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/res/menu/main.xml b/android/KMAPro/kMAPro/src/main/res/menu/main.xml index 5c31051fb41ce04d3c988785ba2d709455e26628..ebde4a41748c227aa34215d0754390b5306b6c87 100644 --- a/android/KMAPro/kMAPro/src/main/res/menu/main.xml +++ b/android/KMAPro/kMAPro/src/main/res/menu/main.xml @@ -1,52 +1,51 @@ - - - + + - + + - + - - + app:showAsAction="never" + android:icon="@drawable/ic_light_action_textsize"/> + - + - \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/values-sw600dp/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values-sw600dp/dimens.xml index c430be8cf2974d5b03b60cc50fd77fbce1e5019b..e466f62acf7d1150962673e92652be438d67aa32 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-sw600dp/dimens.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-sw600dp/dimens.xml @@ -4,7 +4,11 @@ Customize dimensions originally defined in res/values/dimens.xml (such as screen margins) for sw600dp devices (e.g. 7" tablets) here. --> - 100dp - 6.75sp - + 400dp + 6.75sp + 196dp + 18dp + 12dp + 10dp + diff --git a/android/KMAPro/kMAPro/src/main/res/values-sw720dp-land/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values-sw720dp-land/dimens.xml index 2805f187b351345043cb9b8bef9655df91d9b14b..5d1e6e39ec19d0e7d6acf30160ddacffd5578b16 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-sw720dp-land/dimens.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-sw720dp-land/dimens.xml @@ -4,6 +4,11 @@ Customize dimensions originally defined in res/values/dimens.xml (such as screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here. --> + 750dp 128dp - + 196dp + 8dp + 15dp + 10dp + diff --git a/android/KMAPro/kMAPro/src/main/res/values-sw720dp/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values-sw720dp/dimens.xml index 80c671aae058b8269c822902b5daffee9b50165b..2436c7dd7b132a43196207ef9b7e825121c4fbde 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-sw720dp/dimens.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-sw720dp/dimens.xml @@ -4,7 +4,12 @@ Customize dimensions originally defined in res/values/dimens.xml (such as screen margins) for sw720dp devices (e.g. 10" tablets) here. --> - 140dp - 18sp - + 500dp + 18sp + 650dp + 196dp + 8dp + 15dp + 10dp + diff --git a/android/KMAPro/kMAPro/src/main/res/values-v11/styles.xml b/android/KMAPro/kMAPro/src/main/res/values-v11/styles.xml deleted file mode 100644 index 3c02242ad044be9b8c7c09e7c90c5d427763fe97..0000000000000000000000000000000000000000 --- a/android/KMAPro/kMAPro/src/main/res/values-v11/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/android/KMAPro/kMAPro/src/main/res/values-v14/styles.xml b/android/KMAPro/kMAPro/src/main/res/values-v14/styles.xml deleted file mode 100644 index 44d609b982f7e75561bec976f0e6ea437cc7a891..0000000000000000000000000000000000000000 --- a/android/KMAPro/kMAPro/src/main/res/values-v14/styles.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - diff --git a/android/KMAPro/kMAPro/src/main/res/values-v21/styles.xml b/android/KMAPro/kMAPro/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..ee37eff14a0a083fcdfc9e1ffdb37898037203d6 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-v21/styles.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/values/colors.xml b/android/KMAPro/kMAPro/src/main/res/values/colors.xml index c080cc393f0534bdd08ad10a5ebc8cc4ac36575b..2415183a565a26a64c17dc01844a57b3ecc7ba75 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/colors.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/colors.xml @@ -1,7 +1,29 @@ + + #D2782E + #C9872F + #C06033 + #D44B2C + #F68924 + #CC3846 + #F69169 + #773017 + #C06033 + #00735E + #0AB8A0 + #79C3DA + #000000 + #6D6C6F + #929396 + #C9CACA + #DADDDC + + @color/analagous_5 + @color/analagous_6 + @color/complementary_6 + #FFFFFFCF - #FFFF7132 - #FFC82236 - #FF4CB8D1 - \ No newline at end of file + @color/complementary_1 + + diff --git a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml index 62e3b11e864190c4b0823bd0d1b612f76c170836..6bd0f2fa5344ca96b63a5ad151a1738d01e6f392 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml @@ -1,12 +1,17 @@ + 200dp + 10dp + 20sp 16dp 16dp - 100dp - 4.5sp + 200dp + 6sp 250dp - 6sp - 6dp + 8dp + 8dp + 15dp + 10dp diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml index 59f297e67e7b556e0163c5bdff634ef31cc7eb04..2025fc0a9b53a3cbb14ed6eb254e14be8e1e5f9d 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml @@ -2,32 +2,52 @@ Keyman - Settings + Share Web Browser Text Size More Clear Text Info + Version + + Start typing here… + + + Copyright © 1992- + + Text size up Text size down - Top bar line - Start typing here… - Share - + + All text will be cleared + + + + Get Started + Add a keyboard for your language + Enable Keyman as system-wide keyboard + Set Keyman as default keyboard + More info Don\'t show again Search or type URL Bookmarks No Bookmarks + Add Bookmark Title Url - Cancel - Install + Package + failed to install + Downloading keyboard package + Failed to extract + Install Keyboard Package + No new touch-optimized keyboards to install + No valid touch-optimized keyboards to install diff --git a/android/KMAPro/kMAPro/src/main/res/values/styles.xml b/android/KMAPro/kMAPro/src/main/res/values/styles.xml index 6ce89c7ba4394d8cab27953d41456ce234ca37c3..6bdacca3b779c5e0f5f10a7889307c69eafb4944 100644 --- a/android/KMAPro/kMAPro/src/main/res/values/styles.xml +++ b/android/KMAPro/kMAPro/src/main/res/values/styles.xml @@ -1,20 +1,35 @@ - + + + + + - - diff --git a/android/KMAPro/kMAPro/src/main/res/xml/method.xml b/android/KMAPro/kMAPro/src/main/res/xml/method.xml index d246624c472fa9b0d6a83082e29dc94ef2e91c07..4ec32c1ac2ed78db0523c4e44be934d1f7659b6a 100644 --- a/android/KMAPro/kMAPro/src/main/res/xml/method.xml +++ b/android/KMAPro/kMAPro/src/main/res/xml/method.xml @@ -20,4 +20,6 @@ - + diff --git a/android/KMEA/.idea/.name b/android/KMEA/.idea/.name deleted file mode 100644 index 0ba0900ef5bae6a4ea4d27e0622efde56d78ed55..0000000000000000000000000000000000000000 --- a/android/KMEA/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -KMEA \ No newline at end of file diff --git a/android/KMEA/.idea/compiler.xml b/android/KMEA/.idea/compiler.xml deleted file mode 100644 index 96cc43efa6a0885098044e976cd780bb42c68a70..0000000000000000000000000000000000000000 --- a/android/KMEA/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/KMEA/.idea/copyright/profiles_settings.xml b/android/KMEA/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3377d40335424fd605124d4761390218bb..0000000000000000000000000000000000000000 --- a/android/KMEA/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/android/KMEA/.idea/encodings.xml b/android/KMEA/.idea/encodings.xml deleted file mode 100644 index 97626ba45445dc9f3afa66e6a149914dc39e3df6..0000000000000000000000000000000000000000 --- a/android/KMEA/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/KMEA/AndroidManifest.xml b/android/KMEA/AndroidManifest.xml deleted file mode 100644 index 1b90e07ab3f263435a0d6a3f749c50db8ad6df5a..0000000000000000000000000000000000000000 --- a/android/KMEA/AndroidManifest.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/KMEA/app/build.gradle b/android/KMEA/app/build.gradle index 8d0e3602678a7c355398f9d87a810a77b85da391..c9be2f4fdeb36e23aaf6b0667e3713bbb829c3f7 100644 --- a/android/KMEA/app/build.gradle +++ b/android/KMEA/app/build.gradle @@ -1,19 +1,20 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 25 - buildToolsVersion "26.0.2" + compileSdkVersion 27 + buildToolsVersion "27.0.3" defaultConfig { minSdkVersion 15 - targetSdkVersion 25 + targetSdkVersion 27 if (project.hasProperty("build.number")) { versionCode project.ext['build_counter'] as Integer // Because TeamCity does not bubble build.counter into system properties... versionName "${project.ext['build.number']}" } else { + String majorMinorVersion = file('$projectDir/../../../../resources/VERSION.md').text.trim() versionCode 100 - versionName '10.0.0.0' + versionName majorMinorVersion + '.0.0' } } @@ -42,20 +43,21 @@ android { } ext { - currentFirebaseVersion = "11.8.0" + currentFirebaseVersion = "15.0.2" } dependencies { - implementation 'com.android.support:support-v4:25.4.0' + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' implementation 'commons-io:commons-io:2.6' implementation 'org.apache.commons:commons-text:1.2' testImplementation 'junit:junit:4.12' - testImplementation "org.robolectric:robolectric:3.5.1" + testImplementation "org.robolectric:robolectric:3.8" /* We only want these Firebase Crashlytics dependencies for KMEA */ implementation "com.google.firebase:firebase-analytics:$currentFirebaseVersion" implementation "com.google.firebase:firebase-crash:$currentFirebaseVersion" - implementation('com.crashlytics.sdk.android:crashlytics:2.9.0@aar') { + implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') { transitive = true; } } diff --git a/android/KMEA/app/src/main/AndroidManifest.xml b/android/KMEA/app/src/main/AndroidManifest.xml index 8ab92d53e90dfaf3e2a68061fd2b44833cb868c2..7963fc03d6b47839a30e4de999458a1999fb8bac 100644 --- a/android/KMEA/app/src/main/AndroidManifest.xml +++ b/android/KMEA/app/src/main/AndroidManifest.xml @@ -3,36 +3,38 @@ + - + + android:theme="@style/Theme.AppCompat.Light.Dialog" > + android:theme="@style/Theme.AppCompat.Light.Dialog" > + android:theme="@style/Theme.AppCompat.Light.Dialog" > + android:theme="@style/Theme.AppCompat.Light.Dialog" > + android:theme="@style/Theme.AppCompat.Light.Dialog"> diff --git a/android/KMEA/app/src/main/assets/cloud/european2-1.6.js b/android/KMEA/app/src/main/assets/cloud/european2-1.6.js deleted file mode 100644 index bc658b528e8faa66f21502f25f29eec0409071f7..0000000000000000000000000000000000000000 --- a/android/KMEA/app/src/main/assets/cloud/european2-1.6.js +++ /dev/null @@ -1,110 +0,0 @@ -KeymanWeb.KR(new Keyboard_european2());function Keyboard_european2(){this.KI="Keyboard_european2";this.KN="EuroLatin2 Keyboard";this.KV=null;this.KH='

More help

Letters
Symbols
Accent
Marks
Type`#\'"^%&*~-_:@.|,
Result `  ̏ ´ ˝ ˆ ˇ  ̑ ˘ ˜ ˉ  ̱ ¨ ˚ ˙ . ¸
Other
Letters
Type = thencegjnostyz\'
Result ɔ ə ɂ ɲ ŋ ɵ ß þ ȝ ʒ
Ɔ Ə Ɂ Ɲ Ŋ Ɵ Þ Ȝ Ʒ

Type \\ thendegknos
Result ð ɛ ǥ ĸ ʼn ø ſ
Ð Ɛ Ǥ Ø
Digraphs Typea\\ed\\zf\\if\\li\\jl\\jn\\jo\\eo\\i
Result æ dž ij lj nj œ ƣ
Æ Dž DŽ IJ Lj LJ Nj NJ Œ Ƣ
Punctuation
and Symbols
Type\\?\\!<<>>\\f\\m\\P\\S\\T\\t\\|\\c\\p\\rt\\m
Result¿¡«»ªº§¦©®

Type\\:\\\'\\,\\_\\M\\N\\-\\~\\...\\.\\>
Result¨´¸¯­¬·
Numbers
and Fractions
Type\\+\\x\\/\\*\\%\\u
Result±×÷°µ

Typen\\o\\1\\2\\3\\4\\5\\6\\7\\8\\9\\0
Result¹²³

Type1//21//32//31//43//41//52//53//54//51//65//61//83//85//87//8
Result½¼¾
Currency
Symbols
Type $ thencefhlnNpPrRwy
Result¢£¥

Type $ thenCdDFgGkKt
Result¤
';this.KM=0;this.KBVER="1.6";this.KVKL={"tablet":{"font":"Tahoma","layer":[{"id":"default","row":[{"id":1,"key":[{"id":"K_Q","text":"q","sk":[{"id":"U_A78C"}]},{"id":"K_W","text":"w","sk":[{"id":"U_1E81"},{"id":"U_1E83"},{"id":"U_0175"},{"id":"U_1E85"},{"id":"U_1E98"},{"id":"U_1E87"},{"id":"U_1E89"}]},{"id":"K_E","text":"e","sk":[{"text":"\u00E8","id":"U_00E8"},{"text":"\u0205","id":"U_0205"},{"text":"\u00E9","id":"U_00E9"},{"text":"\u00EA","id":"U_00EA"},{"text":"\u011B","id":"U_011B"},{"text":"\u0207","id":"U_0207"},{"text":"\u0115","id":"U_0115"},{"text":"\u1EBD","id":"U_1EBD"},{"text":"\u0113","id":"U_0113"},{"text":"\u00EB","id":"U_00EB"},{"text":"\u0117","id":"U_0117"},{"text":"\u1EB9","id":"U_1EB9"},{"text":"\u0119","id":"U_0119"},{"text":"\u0259","id":"U_0259"},{"text":"\u025B","id":"U_025B"}]},{"id":"K_R","text":"r","sk":[{"id":"U_0211"},{"id":"U_0155"},{"id":"U_0159"},{"id":"U_0213"},{"id":"U_024D"},{"id":"U_1E5F"},{"id":"U_1E59"},{"id":"U_1E5B"},{"id":"U_0157"}]},{"id":"K_T","text":"t","sk":[{"text":"\u0165","id":"U_0165"},{"text":"\u0167","id":"U_0167"},{"text":"\u1E6F","id":"U_1E6F"},{"text":"\u1E97","id":"U_1E97"},{"text":"\u1E6B","id":"U_1E6B"},{"text":"\u1E6D","id":"U_1E6D"},{"text":"\u0163","id":"U_0163"},{"text":"\u021B","id":"U_021B"},{"text":"\u00FE","id":"U_00FE"}]},{"id":"K_Y","text":"y","sk":[{"id":"U_1EF3"},{"id":"U_00FD"},{"id":"U_0177"},{"id":"U_1EF9"},{"id":"U_0233"},{"id":"U_00FF"},{"id":"U_1E99"},{"id":"U_1E8F"},{"id":"U_1EF5"}]},{"id":"K_U","text":"u","sk":[{"id":"U_00F9"},{"id":"U_0215"},{"id":"U_00FA"},{"id":"U_0171"},{"id":"U_00FB"},{"id":"U_01D4"},{"id":"U_0217"},{"id":"U_016D"},{"id":"U_0169"},{"id":"U_016B"},{"id":"U_00FC"},{"id":"U_016F"},{"id":"U_1EE5"},{"id":"U_0173"}]},{"id":"K_I","text":"i","sk":[{"id":"U_00EC"},{"id":"U_0209"},{"id":"U_00ED"},{"id":"U_00EE"},{"id":"U_01D0"},{"id":"U_020B"},{"id":"U_012D"},{"id":"U_0129"},{"id":"U_012B"},{"id":"U_00EF"},{"id":"U_0131"},{"id":"U_1ECB"},{"id":"U_012F"},{"id":"U_0272"},{"id":"U_0133"}]},{"id":"K_O","text":"o","sk":[{"id":"U_00F2"},{"id":"U_020D"},{"id":"U_00F3"},{"id":"U_0151"},{"id":"U_00F4"},{"id":"U_01D2"},{"id":"U_020F"},{"id":"U_014F"},{"id":"U_00F5"},{"id":"U_014D"},{"id":"U_00F6"},{"id":"U_022F"},{"id":"U_1ECD"},{"id":"U_01EB"},{"id":"U_0275"},{"id":"U_00F8"},{"id":"U_0254"},{"id":"U_0153"},{"id":"U_01A3"}]},{"id":"K_P","text":"p","sk":[{"id":"U_1E55"},{"id":"U_0070"},{"id":"U_1E57"}]}]},{"id":2,"key":[{"id":"K_A","text":"a","pad":"40","sk":[{"id":"U_00E0"},{"id":"U_0201"},{"id":"U_00E1"},{"id":"U_00E2"},{"id":"U_01CE"},{"id":"U_0203"},{"id":"U_0103"},{"id":"U_00E3"},{"id":"U_0101"},{"id":"U_00E4"},{"id":"U_00E5"},{"id":"U_0227"},{"id":"U_1EA1"},{"id":"U_0105"},{"id":"U_00E6"}]},{"id":"K_S","text":"s","sk":[{"text":"\u015B","id":"U_015B"},{"text":"\u015D","id":"U_015D"},{"text":"\u0161","id":"U_0161"},{"text":"\u1E61","id":"U_1E61"},{"text":"\u1E63","id":"U_1E63"},{"text":"\u015F","id":"U_015F"},{"text":"\u0219","id":"U_0219"},{"text":"\u00DF","id":"U_00DF"},{"text":"\u017F","id":"U_017F"}]},{"id":"K_D","text":"d","sk":[{"id":"U_010F"},{"id":"U_0111"},{"id":"U_1E0F"},{"id":"U_1E0B"},{"id":"U_1E0D"},{"id":"U_1E11"},{"id":"U_00F0"},{"id":"U_01C6"}]},{"id":"K_F","text":"f","sk":[{"id":"U_1E1F"},{"id":"U_FB01"},{"id":"U_FB02"}]},{"id":"K_G","text":"g","sk":[{"id":"U_01F5"},{"id":"U_011D"},{"id":"U_01E7"},{"id":"U_011F"},{"id":"U_0067"},{"id":"U_1E21"},{"id":"U_0121"},{"id":"U_0123"},{"id":"U_0242"},{"id":"U_01E5"}]},{"id":"K_H","text":"h","sk":[{"id":"U_0125"},{"id":"U_021F"},{"id":"U_1E2B"},{"id":"U_0127"},{"id":"U_1E96"},{"id":"U_1E27"},{"id":"U_1E23"},{"id":"U_1E25"},{"id":"U_1E29"}]},{"id":"K_J","text":"j","sk":[{"id":"U_0135"},{"id":"U_01F0"},{"id":"U_0249"},{"id":"U_0237"}]},{"id":"K_K","text":"k","sk":[{"id":"U_1E31"},{"id":"U_01E9"},{"id":"U_1E35"},{"id":"U_1E33"},{"id":"U_0137"},{"id":"U_0138"}]},{"id":"K_L","text":"l","sk":[{"id":"U_013A"},{"id":"U_013E"},{"id":"U_026B"},{"id":"U_0142"},{"id":"U_1E3B"},{"id":"U_0140"},{"id":"U_1E37"},{"id":"U_013C"},{"id":"U_01C9"}]},{"id":"T_new_20","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_SHIFT","text":"*Shift*","width":"100","sp":"1","nextlayer":"shift"},{"id":"K_Z","text":"z","sk":[{"id":"U_017A"},{"id":"U_1E91"},{"id":"U_017E"},{"id":"U_01B6"},{"id":"U_1E95"},{"id":"U_017C"},{"id":"U_1E93"},{"id":"U_2C6C"},{"id":"U_0292"},{"id":"U_01EF"}]},{"id":"K_X","text":"x","sk":[{"id":"U_1E8D"},{"id":"U_1E8B"}]},{"id":"K_C","text":"c","sk":[{"id":"U_0107"},{"id":"U_0109"},{"id":"U_010D"},{"id":"U_010B"},{"id":"U_00E7"}]},{"id":"K_V","text":"v","sk":[{"id":"U_1E7D"},{"id":"U_1E7F"}]},{"id":"K_B","text":"b","sk":[{"id":"U_0180"},{"id":"U_1E07"},{"id":"U_1E03"},{"id":"U_1E05"}]},{"id":"K_N","text":"n","sk":[{"id":"U_014B"},{"id":"U_01F9"},{"id":"U_0144"},{"id":"U_0148"},{"id":"U_00F1"},{"id":"U_1E49"},{"id":"U_1E45"},{"id":"U_1E47"},{"id":"U_0146"},{"id":"U_0149"},{"id":"U_01CC"}]},{"id":"K_M","text":"m","sk":[{"id":"U_1E3F"},{"id":"U_1E41"},{"id":"U_1E43"}]},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"id":"U_0021"},{"id":"U_003F"},{"id":"U_0027"},{"id":"U_0022"},{"id":"U_005C"},{"id":"U_003A"},{"id":"U_003B"}]},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_NUMERALS","text":"*123*","width":"150","sp":"1","nextlayer":"numeric"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_34","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_36","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]},{"id":"shift","row":[{"id":1,"key":[{"id":"K_Q","text":"Q","sk":[{"id":"U_A78B"}]},{"id":"K_W","text":"W","sk":[{"id":"U_1E80"},{"id":"U_1E82"},{"id":"U_0174"},{"id":"U_1E84"},{"id":"U_1E86"},{"id":"U_1E88"}]},{"id":"K_E","text":"E","sk":[{"id":"U_00C8"},{"id":"U_0204"},{"id":"U_00C9"},{"id":"U_00CA"},{"id":"U_011A"},{"id":"U_0206"},{"id":"U_0114"},{"id":"U_1EBC"},{"id":"U_0112"},{"id":"U_00CB"},{"id":"U_0116"},{"id":"U_1EB8"},{"id":"U_0118"},{"id":"U_018F"},{"id":"U_0190"}]},{"id":"K_R","text":"R","sk":[{"id":"U_0210"},{"id":"U_0154"},{"id":"U_0158"},{"id":"U_0212"},{"id":"U_024C"},{"id":"U_1E5E"},{"id":"U_1E58"},{"id":"U_1E5A"},{"id":"U_0156"}]},{"id":"K_T","text":"T","sk":[{"text":"\u0164","id":"U_0164"},{"text":"\u0166","id":"U_0166"},{"text":"\u1E6E","id":"U_1E6E"},{"text":"\u1E6A","id":"U_1E6A"},{"text":"\u1E6C","id":"U_1E6C"},{"text":"\u0162","id":"U_0162"},{"text":"\u021A","id":"U_021A"},{"text":"\u00DE","id":"U_00DE"}]},{"id":"K_Y","text":"Y","sk":[{"id":"U_1EF2"},{"id":"U_00DD"},{"id":"U_0176"},{"id":"U_1EF8"},{"id":"U_0232"},{"id":"U_0178"},{"id":"U_1E8E"},{"id":"U_1EF4"}]},{"id":"K_U","text":"U","sk":[{"id":"U_00D9"},{"id":"U_0214"},{"id":"U_00DA"},{"id":"U_0170"},{"id":"U_00DB"},{"id":"U_01D3"},{"id":"U_0216"},{"id":"U_016C"},{"id":"U_0168"},{"id":"U_016A"},{"id":"U_00DC"},{"id":"U_016E"},{"id":"U_1EE4"},{"id":"U_0172"}]},{"id":"K_I","text":"I","sk":[{"id":"U_00CC"},{"id":"U_0208"},{"id":"U_00CD"},{"id":"U_00CE"},{"id":"U_01CF"},{"id":"U_020A"},{"id":"U_012C"},{"id":"U_0128"},{"id":"U_012A"},{"id":"U_00CF"},{"id":"U_0130"},{"id":"U_1ECA"},{"id":"U_012E"},{"id":"U_019D"}]},{"id":"K_O","text":"O","sk":[{"id":"U_00D2"},{"id":"U_020C"},{"id":"U_00D3"},{"id":"U_0150"},{"id":"U_00D4"},{"id":"U_01D1"},{"id":"U_020E"},{"id":"U_014E"},{"id":"U_00D5"},{"id":"U_014C"},{"id":"U_00D6"},{"id":"U_022E"},{"id":"U_1ECC"},{"id":"U_01EA"},{"id":"U_019F"},{"id":"U_00D8"},{"id":"U_0186"},{"id":"U_0152"},{"id":"U_01A2"}]},{"id":"K_P","text":"P","sk":[{"id":"U_1E54"},{"id":"U_0050"},{"id":"U_1E56"}]}]},{"id":2,"key":[{"id":"K_A","text":"A","pad":"40","sk":[{"id":"U_00C0"},{"id":"U_0200"},{"id":"U_00C1"},{"id":"U_00C2"},{"id":"U_01CD"},{"id":"U_0202"},{"id":"U_0102"},{"id":"U_00C3"},{"id":"U_0100"},{"id":"U_00C4"},{"id":"U_00C5"},{"id":"U_0226"},{"id":"U_1EA0"},{"id":"U_0104"},{"id":"U_00C6"}]},{"id":"K_S","text":"S","sk":[{"text":"\u015A","id":"U_015A"},{"text":"\u015C","id":"U_015C"},{"text":"\u0160","id":"U_0160"},{"text":"\u1E60","id":"U_1E60"},{"text":"\u1E62","id":"U_1E62"},{"text":"\u015E","id":"U_015E"},{"text":"\u0218","id":"U_0218"},{"text":"\u1E9E","id":"U_1E9E"}]},{"id":"K_D","text":"D","sk":[{"id":"U_010E"},{"id":"U_0110"},{"id":"U_1E0E"},{"id":"U_1E0A"},{"id":"U_1E0C"},{"id":"U_1E10"},{"id":"U_00D0"},{"id":"U_01C5"},{"id":"U_01C4"}]},{"id":"K_F","text":"F","sk":[{"id":"U_1E1E"}]},{"id":"K_G","text":"G","sk":[{"id":"U_01F4"},{"id":"U_011C"},{"id":"U_01E6"},{"id":"U_011E"},{"id":"U_0047"},{"id":"U_1E20"},{"id":"U_0120"},{"id":"U_0122"},{"id":"U_0241"},{"id":"U_01E4"}]},{"id":"K_H","text":"H","sk":[{"id":"U_0124"},{"id":"U_021E"},{"id":"U_1E2A"},{"id":"U_0126"},{"id":"U_1E26"},{"id":"U_1E22"},{"id":"U_1E24"},{"id":"U_1E28"}]},{"id":"K_J","text":"J","sk":[{"id":"U_0134"},{"id":"U_0248"}]},{"id":"K_K","text":"K","sk":[{"id":"U_1E30"},{"id":"U_01E8"},{"id":"U_1E34"},{"id":"U_1E32"},{"id":"U_0136"}]},{"id":"K_L","text":"L","sk":[{"id":"U_0139"},{"id":"U_013D"},{"id":"U_2C62"},{"id":"U_0141"},{"id":"U_1E3A"},{"id":"U_013F"},{"id":"U_1E36"},{"id":"U_013B"},{"id":"U_01C8"},{"id":"U_01C7"}]},{"id":"T_new_60","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_SHIFT","text":"*Shift*","width":"100","sp":"2","nextlayer":"default"},{"id":"K_Z","text":"Z","sk":[{"id":"U_021C"},{"id":"U_0179"},{"id":"U_1E90"},{"id":"U_017D"},{"id":"U_01B5"},{"id":"U_1E94"},{"id":"U_017B"},{"id":"U_1E92"},{"id":"U_2C6B"},{"id":"U_01B7"},{"id":"U_01EE"}]},{"id":"K_X","text":"X","sk":[{"id":"U_1E8C"},{"id":"U_1E8A"}]},{"id":"K_C","text":"C","sk":[{"id":"U_0106"},{"id":"U_0108"},{"id":"U_010C"},{"id":"U_010A"},{"id":"U_00C7"}]},{"id":"K_V","text":"V","sk":[{"id":"U_1E7C"},{"id":"U_1E7E"}]},{"id":"K_B","text":"B","sk":[{"id":"U_0243"},{"id":"U_1E06"},{"id":"U_1E02"},{"id":"U_1E04"}]},{"id":"K_N","text":"N","sk":[{"id":"U_014A"},{"id":"U_01F8"},{"id":"U_0143"},{"id":"U_0147"},{"id":"U_00D1"},{"id":"U_1E48"},{"id":"U_1E44"},{"id":"U_1E46"},{"id":"U_0145"},{"id":"U_01CB"},{"id":"U_01CA"}]},{"id":"K_M","text":"M","sk":[{"id":"U_1E3E"},{"id":"U_1E40"},{"id":"U_1E42"}]},{"id":"K_PERIOD","text":".","layer":"default","sk":[{"text":",","id":"K_COMMA"},{"text":"!","id":"U_0021","layer":"default"},{"text":"?","id":"U_003F","layer":"default"},{"text":"'","id":"U_0027","layer":"default"},{"text":"\"","id":"U_0022","layer":"default"},{"text":"\\","id":"U_005C","layer":"default"},{"text":":","id":"U_003A","layer":"default"},{"text":";","id":"U_003B","layer":"default"}]},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_NUMERALS","text":"*123*","width":"150","sp":"1","nextlayer":"numeric"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_74","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_76","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]},{"id":"numeric","row":[{"id":1,"key":[{"id":"K_1","text":"1","sp":"0","sk":[{"id":"U_00B9"},{"id":"U_00BD"},{"id":"U_2153"},{"id":"U_00BC"},{"id":"U_2155"},{"id":"U_2159"},{"id":"U_215B"}]},{"id":"K_2","text":"2","sp":"0","sk":[{"id":"U_00B2"},{"id":"U_2154"},{"id":"U_2156"}]},{"id":"K_3","text":"3","sp":"0","sk":[{"id":"U_00B3"},{"id":"U_00BE"},{"id":"U_2157"},{"id":"U_215C"}]},{"id":"K_4","text":"4","sp":"0","sk":[{"id":"U_2074"},{"id":"U_2158"}]},{"id":"K_5","text":"5","sp":"0","sk":[{"id":"U_2075"},{"id":"U_215A"},{"id":"U_215D"}]},{"id":"K_6","text":"6","sp":"0","sk":[{"id":"U_2076"}]},{"id":"K_7","text":"7","sp":"0","sk":[{"id":"U_2077"},{"id":"U_215E"}]},{"id":"K_8","text":"8","sp":"0","sk":[{"id":"U_2078"}]},{"id":"K_9","text":"9","sp":"0","sk":[{"id":"U_2079"}]},{"id":"K_0","text":"0","sp":"0","sk":[{"id":"U_2070"}]}]},{"id":2,"key":[{"id":"U_0024","text":"$","pad":"40"},{"id":"U_0040","text":"@"},{"id":"U_0023","text":"#"},{"id":"U_0025","text":"%","sk":[{"id":"U_2030"},{"id":"U_2031"}]},{"id":"U_0026","text":"&"},{"id":"U_005F","text":"_"},{"id":"U_003D","text":"="},{"id":"U_007C","text":"|"},{"id":"U_002F","text":"\/","sk":[{"text":"\\","id":"U_005C"}]},{"id":"T_new_164","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_LOWER","text":"*abc*","width":"100","sp":"1","nextlayer":"default"},{"id":"U_005B","text":"[","sk":[{"id":"U_007B"},{"id":"U_00AB"},{"id":"U_003C"}]},{"id":"U_0028","text":"("},{"id":"U_0029","text":")"},{"id":"U_005D","text":"]","sk":[{"id":"U_007D"},{"id":"U_00BB"},{"id":"U_003E"}]},{"id":"U_002B","text":"+"},{"id":"U_002D","text":"-"},{"id":"U_002A","text":"*"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"id":"U_0021"},{"id":"U_003F"},{"id":"U_0027"},{"id":"U_0022"},{"id":"U_005C"},{"id":"U_003A"},{"id":"U_003B"}]},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_CURRENCIES","text":"*Currency*","width":"150","sp":"1","nextlayer":"currency"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_178","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_180","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]},{"id":"symbol","row":[{"id":1,"key":[{"id":"U_0060","text":"`"},{"id":"U_007E","text":"~"},{"id":"U_005E","text":"^"},{"id":"U_00A8","text":"\u00A8"},{"id":"U_00B4","text":"\u00B4"},{"id":"U_00B8","text":"\u00B8"},{"id":"U_00AF","text":"\u00AF"},{"id":"U_00BF","text":"\u00BF"},{"id":"U_00A1","text":"\u00A1"},{"id":"U_00AC","text":"\u00AC"}]},{"id":2,"key":[{"id":"U_00AA","text":"\u00AA","pad":"40"},{"id":"U_00BA","text":"\u00BA"},{"id":"U_00B6","text":"\u00B6"},{"id":"U_00A7","text":"\u00A7"},{"id":"U_2021","text":"\u2021"},{"id":"U_2020","text":"\u2020"},{"id":"U_00A6","text":"\u00A6"},{"id":"U_2122","text":"\u2122"},{"id":"U_2116","text":"\u2116"},{"id":"T_new_336","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_LOWER","text":"*abc*","width":"100","sp":"1","nextlayer":"default"},{"id":"U_00A9","text":"\u00A9"},{"id":"U_2117","text":"\u2117"},{"id":"U_00AE","text":"\u00AE"},{"id":"U_2014","text":"\u2014"},{"id":"U_2013","text":"\u2013"},{"id":"U_2022","text":"\u2022"},{"id":"U_00B7","text":"\u00B7"},{"id":"U_2026","text":"\u2026","sk":[{"id":"U_00B1"},{"id":"U_00D7"},{"id":"U_00F7"},{"id":"U_00B5"},{"id":"U_00B0"},{"id":"U_2031"}]},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_CURRENCIES","text":"*Currency*","width":"150","sp":"1","nextlayer":"numeric"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_350","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_352","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]},{"id":"currency","row":[{"id":1,"key":[{"id":"U_00A2","text":"\u00A2","nextlayer":"numeric"},{"id":"U_20AC","text":"\u20AC","nextlayer":"numeric"},{"id":"U_20A4","text":"\u20A4","nextlayer":"numeric"},{"id":"U_00A3","text":"\u00A3","nextlayer":"numeric"},{"id":"U_20AA","text":"\u20AA","nextlayer":"numeric"},{"id":"U_20A8","text":"\u20A8","nextlayer":"numeric"},{"id":"U_20B9","text":"\u20B9","nextlayer":"numeric"},{"id":"U_20A9","text":"\u20A9","nextlayer":"numeric"},{"id":"U_00A5","text":"\u00A5","nextlayer":"numeric"},{"id":"U_00A4","text":"\u00A4","nextlayer":"numeric"}]},{"id":2,"key":[{"id":"U_20AB","text":"\u20AB","pad":"40"},{"id":"U_20AF","text":"\u20AF"},{"id":"U_20B1","text":"\u20B1"},{"id":"U_20A3","text":"\u20A3"},{"id":"U_20B0","text":"\u20B0"},{"id":"U_20B2","text":"\u20B2"},{"id":"U_20B4","text":"\u20B4"},{"id":"U_20A1","text":"\u20A1"},{"id":"U_20AD","text":"\u20AD"},{"id":"T_new_2717","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_LOWER","text":"*abc*","width":"100","sp":"1","nextlayer":"default"},{"id":"U_20A6","text":"\u20A6"},{"id":"U_20A7","text":"\u20A7"},{"id":"U_20AE","text":"\u20AE"},{"id":"U_0BF9","text":"\u0BF9"},{"id":"U_09F3","text":"\u09F3"},{"id":"U_0E3F","text":"\u0E3F"},{"id":"T_new_2725","text":"","sp":"9"},{"id":"T_new_2726","text":"","sp":"9"},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_SYMBOLS","text":"*Symbol*","width":"150","sp":"1","nextlayer":"symbol"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_2731","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_2733","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]}]}};this.s15="`#'\"^%&*~-_:@.|,=\\$";this.s16="àèìǹòùẁỳÀÈÌǸÒÙẀỲ";this.s17="aeinouwyAEINOUWY";this.s18="ȁȅȉȍȑȕȀȄȈȌȐȔ";this.s19="aeioruAEIORU";this.s20="áćéǵíḱĺḿńóṕŕśúẃýźÁĆÉǴÍḰĹḾŃÓṔŔŚÚẂÝŹ";this.s21="acegiklmnoprsuwyzACEGIKLMNOPRSUWYZ";this.s22="őűŐŰ";this.s23="ouOU";this.s24="âĉêĝĥîĵôŝûŵŷẑÂĈÊĜĤÎĴÔŜÛŴŶẐ";this.s25="aceghijosuwyzACEGHIJOSUWYZ";this.s26="ǎǍčČďĎěĚǧǦȟȞǐǏǰǩǨľĽňŇǒǑřŘšŠťŤǔǓǯǮžŽ";this.s27="aAcCdDeEgGhHiIjkKlLnNoOrRsStTuUxXzZ";this.s28="ȂȃȆȇȊȋȎȏȒȓȖȗ";this.s29="AaEeIiOoRrUu";this.s30="ăĂĕĔğĞḫḪĭĬŏŎŭŬ";this.s31="aAeEgGhHiIoOuU";this.s32="ãÃẽẼĩĨɫⱢñÑõÕũŨṽṼỹỸ";this.s33="aAeEiIlLnNoOuUvVyY";this.s34="ĀāɃƀĐđĒēḠḡĦħĪīɈɉŁłŌōɌɍŦŧŪūȲȳƵƶ";this.s35="AaBbDdEeGgHhIiJjLlOoRrTtUuYyZz";this.s36="ḆḇḎḏẖḴḵḺḻṈṉṞṟṮṯẔẕ";this.s37="BbDdhKkLlNnRrTtZz";this.s38="äëḧïöẗüẅẍÿÄËḦÏÖÜẄẌŸ";this.s39="aehiotuwxyAEHIOUWXY";this.s40="åÅůŮẘẙșȘțȚ";this.s41="aAuUwysStT";this.s42="ȧȦḃḂċĊḋḊėĖḟḞġĠḣḢıİȷŀĿṁṀṅṄȯȮṗṖṙṘṡṠṫṪẇẆẋẊẏẎżŻ";this.s43="aAbBcCdDeEfFgGhHiIjlLmMnNoOpPrRsStTwWxXyYzZ";this.s44="ẠạḄḅḌḍẸẹḤḥỊịḲḳḶḷṂṃṆṇỌọṚṛṢṣṬṭỤụṾṿẈẉỴỵẒẓ";this.s45="AaBbDdEeHhIiKkLlMmNnOoRrSsTtUuVvWwYyZz";this.s46="ąĄçÇḑḐęĘģĢḩḨįĮķĶļĻņŅǫǪŗŖşŞţŢųŲⱬⱫ";this.s47="aAcCdDeEgGhHiIkKlLnNoOrRsStTuUzZ";this.s48="ƆɔƏəɁɂƝɲŊŋƟɵẞßÞþȜȝƷʒ";this.s49="CcEeGgJjNnOoSsTtYyZz";this.s50="©ÐðƐɛªǤǥĸ—º–ʼnØø¶℗®§ſ‡†µ×¡¦¨¬­¯°±´·¸¿÷‰•¹²³⁴⁵⁶⁷⁸⁹⁰";this.s51="cDdEefGgkMmNnOoPprSsTtux!|:~-_*+'.,?/%>1234567890";this.s52="¤¢₯₫€€₱₣₲₰₴₡₭₤₦₪₧£₹₨₮₩¥";this.s53="CcDdEefFGghkKlNnPpRrtwy";this.s56="tablet";this.s57="phone";this.KVER="9.0.487.0";this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16400,49)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¡");}else if(k.KKM(e,16400,222)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ꞌ");}else if(k.KKM(e,16400,222)&&k.KCM(1,t,"\"",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"\"");}else if(k.KKM(e,16400,222)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,222)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,222)) {r=m=1;k.KO(0,t,"\"");k.KDO(-1,t,0);}else if(k.KKM(e,16400,51)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"#");}else if(k.KKM(e,16400,51)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,51)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,51)) {r=m=1;k.KO(0,t,"#");k.KDO(-1,t,0);}else if(k.KKM(e,16400,52)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"$");}else if(k.KKM(e,16400,52)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,52)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,52)) {r=m=1;k.KO(0,t,"$");k.KDO(-1,t,0);}else if(k.KKM(e,16400,53)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"‰");}else if(k.KKM(e,16400,53)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"%");}else if(k.KKM(e,16400,53)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,53)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,53)) {r=m=1;k.KO(0,t,"%");k.KDO(-1,t,0);}else if(k.KKM(e,16400,55)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"&");}else if(k.KKM(e,16400,55)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,55)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,55)) {r=m=1;k.KO(0,t,"&");k.KDO(-1,t,0);}else if(k.KKM(e,16384,222)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ꞌ");}else if(k.KKM(e,16384,222)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"´");}else if(k.KKM(e,16384,222)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"'");}else if(k.KKM(e,16384,222)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,222)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,222)) {r=m=1;k.KO(0,t,"'");k.KDO(-1,t,0);}else if(k.KKM(e,16400,56)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"°");}else if(k.KKM(e,16400,56)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"*");}else if(k.KKM(e,16400,56)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,56)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,56)) {r=m=1;k.KO(0,t,"*");k.KDO(-1,t,0);}else if(k.KKM(e,16400,187)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"±");}else if(k.KKM(e,16384,188)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¸");}else if(k.KKM(e,16384,188)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,",");}else if(k.KKM(e,16384,188)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,188)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,188)) {r=m=1;k.KO(0,t,",");k.KDO(-1,t,0);}else if(k.KKM(e,16384,189)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"­");}else if(k.KKM(e,16384,189)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"-");}else if(k.KKM(e,16384,189)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,189)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,189)) {r=m=1;k.KO(0,t,"-");k.KDO(-1,t,0);}else if(k.KKM(e,16384,190)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"·");}else if(k.KKM(e,16384,190)&&k.KCM(2,t,"·.",2)) {r=m=1;k.KO(2,t,"…");}else if(k.KKM(e,16384,190)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,".");}else if(k.KKM(e,16384,190)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,190)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,190)) {r=m=1;k.KO(0,t,".");k.KDO(-1,t,0);}else if(k.KKM(e,16384,191)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"÷");}else if(k.KKM(e,16384,48)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"⁰");}else if(k.KKM(e,16384,49)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¹");}else if(k.KKM(e,16384,50)&&k.KCM(3,t,"1//",3)) {r=m=1;k.KO(3,t,"½");}else if(k.KKM(e,16384,50)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"²");}else if(k.KKM(e,16384,50)&&k.KCM(2,t,"¹/",2)) {r=m=1;k.KO(2,t,"½");}else if(k.KKM(e,16384,51)&&k.KCM(3,t,"1//",3)) {r=m=1;k.KO(3,t,"⅓");}else if(k.KKM(e,16384,51)&&k.KCM(3,t,"2//",3)) {r=m=1;k.KO(3,t,"⅔");}else if(k.KKM(e,16384,51)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"³");}else if(k.KKM(e,16384,51)&&k.KCM(2,t,"¹/",2)) {r=m=1;k.KO(2,t,"⅓");}else if(k.KKM(e,16384,51)&&k.KCM(2,t,"²/",2)) {r=m=1;k.KO(2,t,"⅔");}else if(k.KKM(e,16384,52)&&k.KCM(3,t,"1//",3)) {r=m=1;k.KO(3,t,"¼");}else if(k.KKM(e,16384,52)&&k.KCM(3,t,"3//",3)) {r=m=1;k.KO(3,t,"¾");}else if(k.KKM(e,16384,52)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"⁴");}else if(k.KKM(e,16384,52)&&k.KCM(2,t,"¹/",2)) {r=m=1;k.KO(2,t,"¼");}else if(k.KKM(e,16384,52)&&k.KCM(2,t,"³/",2)) {r=m=1;k.KO(2,t,"¾");}else if(k.KKM(e,16384,53)&&k.KCM(3,t,"1//",3)) {r=m=1;k.KO(3,t,"⅕");}else if(k.KKM(e,16384,53)&&k.KCM(3,t,"2//",3)) {r=m=1;k.KO(3,t,"⅖");}else if(k.KKM(e,16384,53)&&k.KCM(3,t,"3//",3)) {r=m=1;k.KO(3,t,"⅗");}else if(k.KKM(e,16384,53)&&k.KCM(3,t,"4//",3)) {r=m=1;k.KO(3,t,"⅘");}else if(k.KKM(e,16384,53)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"⁵");}else if(k.KKM(e,16384,54)&&k.KCM(3,t,"1//",3)) {r=m=1;k.KO(3,t,"⅙");}else if(k.KKM(e,16384,54)&&k.KCM(3,t,"5//",3)) {r=m=1;k.KO(3,t,"⅚");}else if(k.KKM(e,16384,54)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"⁶");}else if(k.KKM(e,16384,55)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"⁷");}else if(k.KKM(e,16384,56)&&k.KCM(3,t,"1//",3)) {r=m=1;k.KO(3,t,"⅛");}else if(k.KKM(e,16384,56)&&k.KCM(3,t,"3//",3)) {r=m=1;k.KO(3,t,"⅜");}else if(k.KKM(e,16384,56)&&k.KCM(3,t,"5//",3)) {r=m=1;k.KO(3,t,"⅝");}else if(k.KKM(e,16384,56)&&k.KCM(3,t,"7//",3)) {r=m=1;k.KO(3,t,"⅞");}else if(k.KKM(e,16384,56)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"⁸");}else if(k.KKM(e,16384,56)&&k.KCM(2,t,"¹/",2)) {r=m=1;k.KO(2,t,"⅛");}else if(k.KKM(e,16384,56)&&k.KCM(2,t,"³/",2)) {r=m=1;k.KO(2,t,"⅜");}else if(k.KKM(e,16384,56)&&k.KCM(2,t,"⁵/",2)) {r=m=1;k.KO(2,t,"⅝");}else if(k.KKM(e,16384,56)&&k.KCM(2,t,"⁷/",2)) {r=m=1;k.KO(2,t,"⅞");}else if(k.KKM(e,16384,57)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"⁹");}else if(k.KKM(e,16400,186)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¨");}else if(k.KKM(e,16400,186)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,":");}else if(k.KKM(e,16400,186)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,186)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,186)) {r=m=1;k.KO(0,t,":");k.KDO(-1,t,0);}else if(k.KKM(e,16400,188)&&k.KCM(1,t,"<",1)&&k.KDM(0,t,1)) {r=m=1;k.KO(1,t,"<<");k.KDO(-1,t,1);}else if(k.KKM(e,16400,188)&&k.KCM(1,t,"<",1)) {r=m=1;k.KO(1,t,"«");}else if(k.KKM(e,16400,188)&&k.KCM(1,t,"«",1)) {r=m=1;k.KO(1,t,"<<");k.KDO(-1,t,1);}else if(k.KKM(e,16384,187)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"=");}else if(k.KKM(e,16384,187)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,187)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,187)) {r=m=1;k.KO(0,t,"=");k.KDO(-1,t,0);}else if(k.KKM(e,16400,190)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"•");}else if(k.KKM(e,16400,190)&&k.KCM(1,t,">",1)&&k.KDM(0,t,1)) {r=m=1;k.KO(1,t,">>");k.KDO(-1,t,1);}else if(k.KKM(e,16400,190)&&k.KCM(1,t,">",1)) {r=m=1;k.KO(1,t,"»");}else if(k.KKM(e,16400,190)&&k.KCM(1,t,"»",1)) {r=m=1;k.KO(1,t,">>");k.KDO(-1,t,1);}else if(k.KKM(e,16400,191)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¿");}else if(k.KKM(e,16400,50)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"@");}else if(k.KKM(e,16400,50)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,50)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,50)) {r=m=1;k.KO(0,t,"@");k.KDO(-1,t,0);}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"À");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȁ");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Á");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Â");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǎ");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȃ");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ă");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ã");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ā");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ä");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Å");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȧ");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ạ");}else if(k.KKM(e,16400,65)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ą");}else if(k.KKM(e,16400,66)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ƀ");}else if(k.KKM(e,16400,66)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḇ");}else if(k.KKM(e,16400,66)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḃ");}else if(k.KKM(e,16400,66)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḅ");}else if(k.KKM(e,16400,67)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ć");}else if(k.KKM(e,16400,67)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ĉ");}else if(k.KKM(e,16400,67)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Č");}else if(k.KKM(e,16400,67)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ċ");}else if(k.KKM(e,16400,67)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ç");}else if(k.KKM(e,16400,67)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ɔ");}else if(k.KKM(e,16400,67)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¤");}else if(k.KKM(e,16400,68)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ď");}else if(k.KKM(e,16400,68)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Đ");}else if(k.KKM(e,16400,68)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḏ");}else if(k.KKM(e,16400,68)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḋ");}else if(k.KKM(e,16400,68)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḍ");}else if(k.KKM(e,16400,68)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḑ");}else if(k.KKM(e,16400,68)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ð");}else if(k.KKM(e,16400,68)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₯");}else if(k.KKM(e,16400,69)&&k.KCM(2,t,"A\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"Æ");}else if(k.KKM(e,16400,69)&&k.KCM(2,t,"O\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"Œ");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"È");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȅ");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"É");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ê");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ě");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȇ");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ĕ");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẽ");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ē");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ë");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ė");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẹ");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ę");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ə");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ɛ");}else if(k.KKM(e,16400,69)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"€");}else if(k.KKM(e,16400,70)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḟ");}else if(k.KKM(e,16400,70)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₣");}else if(k.KKM(e,16400,71)&&k.KCM(2,t,"N\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"Ŋ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"G̃");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǵ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ĝ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǧ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ğ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḡ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ġ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ģ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ɂ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǥ");}else if(k.KKM(e,16400,71)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₲");}else if(k.KKM(e,16400,72)&&k.KCM(2,t,"T\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"Þ");}else if(k.KKM(e,16400,72)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ĥ");}else if(k.KKM(e,16400,72)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȟ");}else if(k.KKM(e,16400,72)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḫ");}else if(k.KKM(e,16400,72)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ħ");}else if(k.KKM(e,16400,72)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḧ");}else if(k.KKM(e,16400,72)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḣ");}else if(k.KKM(e,16400,72)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḥ");}else if(k.KKM(e,16400,72)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḩ");}else if(k.KKM(e,16400,73)&&k.KCM(2,t,"O\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"Ƣ");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ì");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȉ");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Í");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Î");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǐ");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȋ");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ĭ");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ĩ");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ī");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ï");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"İ");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ị");}else if(k.KKM(e,16400,73)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Į");}else if(k.KKM(e,16400,74)&&k.KCM(2,t,"I\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"IJ");}else if(k.KKM(e,16400,74)&&k.KCM(2,t,"L\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"LJ");}else if(k.KKM(e,16400,74)&&k.KCM(2,t,"N\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"NJ");}else if(k.KKM(e,16400,74)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ĵ");}else if(k.KKM(e,16400,74)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ɉ");}else if(k.KKM(e,16400,74)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ɲ");}else if(k.KKM(e,16400,75)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḱ");}else if(k.KKM(e,16400,75)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǩ");}else if(k.KKM(e,16400,75)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḵ");}else if(k.KKM(e,16400,75)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḳ");}else if(k.KKM(e,16400,75)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ķ");}else if(k.KKM(e,16400,75)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₭");}else if(k.KKM(e,16400,76)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ĺ");}else if(k.KKM(e,16400,76)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ľ");}else if(k.KKM(e,16400,76)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ɫ");}else if(k.KKM(e,16400,76)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ł");}else if(k.KKM(e,16400,76)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḻ");}else if(k.KKM(e,16400,76)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŀ");}else if(k.KKM(e,16400,76)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḷ");}else if(k.KKM(e,16400,76)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ļ");}else if(k.KKM(e,16400,77)&&k.KCM(2,t,"T\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"™");}else if(k.KKM(e,16400,77)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ḿ");}else if(k.KKM(e,16400,77)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṁ");}else if(k.KKM(e,16400,77)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṃ");}else if(k.KKM(e,16400,77)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"—");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǹ");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ń");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ň");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ñ");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṉ");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṅ");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṇ");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ņ");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŋ");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"–");}else if(k.KKM(e,16400,78)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₦");}else if(k.KKM(e,16400,79)&&k.KCM(2,t,"N\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"№");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ò");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȍ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ó");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"\"",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ő");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ô");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǒ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȏ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŏ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Õ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ō");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ö");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȯ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ọ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǫ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ɵ");}else if(k.KKM(e,16400,79)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ø");}else if(k.KKM(e,16400,80)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"P̈");}else if(k.KKM(e,16400,80)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṕ");}else if(k.KKM(e,16400,80)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṗ");}else if(k.KKM(e,16400,80)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¶");}else if(k.KKM(e,16400,80)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₧");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȑ");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŕ");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ř");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȓ");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ɍ");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṟ");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṙ");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṛ");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŗ");}else if(k.KKM(e,16400,82)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₹");}else if(k.KKM(e,16400,83)&&k.KCM(2,t,"S\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"ẞ");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ś");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŝ");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Š");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ș");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṡ");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṣ");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ş");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẞ");}else if(k.KKM(e,16400,83)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"§");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ť");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŧ");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṯ");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ț");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṫ");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṭ");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ţ");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Þ");}else if(k.KKM(e,16400,84)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"‡");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ù");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȕ");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ú");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"\"",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ű");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Û");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǔ");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȗ");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŭ");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ũ");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ū");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ü");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ů");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ụ");}else if(k.KKM(e,16400,85)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ų");}else if(k.KKM(e,16400,86)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṽ");}else if(k.KKM(e,16400,86)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ṿ");}else if(k.KKM(e,16400,87)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẁ");}else if(k.KKM(e,16400,87)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẃ");}else if(k.KKM(e,16400,87)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŵ");}else if(k.KKM(e,16400,87)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẅ");}else if(k.KKM(e,16400,87)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẇ");}else if(k.KKM(e,16400,87)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẉ");}else if(k.KKM(e,16400,88)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ǯ");}else if(k.KKM(e,16400,88)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẍ");}else if(k.KKM(e,16400,88)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẋ");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ỳ");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ý");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ŷ");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ỹ");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȳ");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ÿ");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẏ");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ỵ");}else if(k.KKM(e,16400,89)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ȝ");}else if(k.KKM(e,16400,90)&&k.KCM(2,t,"D\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"DŽ");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ź");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẑ");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ž");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ƶ");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẕ");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ż");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ẓ");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ⱬ");}else if(k.KKM(e,16400,90)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"Ʒ");}else if(k.KKM(e,16384,220)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"\\");}else if(k.KKM(e,16384,220)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,220)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,220)) {r=m=1;k.KO(0,t,"\\");k.KDO(-1,t,0);}else if(k.KKM(e,16400,54)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"^");}else if(k.KKM(e,16400,54)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,54)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,54)) {r=m=1;k.KO(0,t,"^");k.KDO(-1,t,0);}else if(k.KKM(e,16400,189)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¯");}else if(k.KKM(e,16400,189)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"_");}else if(k.KKM(e,16400,189)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,189)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,189)) {r=m=1;k.KO(0,t,"_");k.KDO(-1,t,0);}else if(k.KKM(e,16384,192)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"`");}else if(k.KKM(e,16384,192)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,192)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16384,192)) {r=m=1;k.KO(0,t,"`");k.KDO(-1,t,0);}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"à");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȁ");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"á");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"â");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǎ");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȃ");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ă");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ã");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ā");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ä");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"å");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȧ");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ạ");}else if(k.KKM(e,16384,65)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ą");}else if(k.KKM(e,16384,66)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ƀ");}else if(k.KKM(e,16384,66)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḇ");}else if(k.KKM(e,16384,66)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḃ");}else if(k.KKM(e,16384,66)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḅ");}else if(k.KKM(e,16384,67)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ć");}else if(k.KKM(e,16384,67)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĉ");}else if(k.KKM(e,16384,67)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"č");}else if(k.KKM(e,16384,67)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ċ");}else if(k.KKM(e,16384,67)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ç");}else if(k.KKM(e,16384,67)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ɔ");}else if(k.KKM(e,16384,67)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"©");}else if(k.KKM(e,16384,67)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¢");}else if(k.KKM(e,16384,68)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ď");}else if(k.KKM(e,16384,68)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"đ");}else if(k.KKM(e,16384,68)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḏ");}else if(k.KKM(e,16384,68)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḋ");}else if(k.KKM(e,16384,68)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḍ");}else if(k.KKM(e,16384,68)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḑ");}else if(k.KKM(e,16384,68)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ð");}else if(k.KKM(e,16384,68)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₫");}else if(k.KKM(e,16384,69)&&k.KCM(2,t,"a\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"æ");}else if(k.KKM(e,16384,69)&&k.KCM(2,t,"o\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"œ");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"è");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȅ");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"é");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ê");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ě");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȇ");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĕ");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẽ");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ē");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ë");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ė");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẹ");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ę");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ə");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ɛ");}else if(k.KKM(e,16384,69)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"€");}else if(k.KKM(e,16384,70)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḟ");}else if(k.KKM(e,16384,70)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ª");}else if(k.KKM(e,16384,70)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₱");}else if(k.KKM(e,16384,71)&&k.KCM(2,t,"n\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"ŋ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"g̃");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǵ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĝ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǧ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ğ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḡ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ġ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ģ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ɂ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǥ");}else if(k.KKM(e,16384,71)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₰");}else if(k.KKM(e,16384,72)&&k.KCM(2,t,"t\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"þ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĥ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȟ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḫ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ħ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẖ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḧ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḣ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḥ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḩ");}else if(k.KKM(e,16384,72)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₴");}else if(k.KKM(e,16384,73)&&k.KCM(2,t,"f\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"fi");}else if(k.KKM(e,16384,73)&&k.KCM(2,t,"o\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"ƣ");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ì");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȉ");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"í");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"î");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǐ");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȋ");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĭ");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĩ");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ī");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ï");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ı");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ị");}else if(k.KKM(e,16384,73)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"į");}else if(k.KKM(e,16384,74)&&k.KCM(2,t,"i\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"ij");}else if(k.KKM(e,16384,74)&&k.KCM(2,t,"L\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"Lj");}else if(k.KKM(e,16384,74)&&k.KCM(2,t,"l\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"lj");}else if(k.KKM(e,16384,74)&&k.KCM(2,t,"N\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"Nj");}else if(k.KKM(e,16384,74)&&k.KCM(2,t,"n\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"nj");}else if(k.KKM(e,16384,74)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĵ");}else if(k.KKM(e,16384,74)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǰ");}else if(k.KKM(e,16384,74)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ɉ");}else if(k.KKM(e,16384,74)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȷ");}else if(k.KKM(e,16384,74)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ɲ");}else if(k.KKM(e,16384,75)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḱ");}else if(k.KKM(e,16384,75)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǩ");}else if(k.KKM(e,16384,75)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḵ");}else if(k.KKM(e,16384,75)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḳ");}else if(k.KKM(e,16384,75)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ķ");}else if(k.KKM(e,16384,75)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĸ");}else if(k.KKM(e,16384,75)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₡");}else if(k.KKM(e,16384,76)&&k.KCM(2,t,"f\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"fl");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ĺ");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ľ");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ɫ");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ł");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḻ");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŀ");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḷ");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ļ");}else if(k.KKM(e,16384,76)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₤");}else if(k.KKM(e,16384,77)&&k.KCM(2,t,"t\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"™");}else if(k.KKM(e,16384,77)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ḿ");}else if(k.KKM(e,16384,77)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṁ");}else if(k.KKM(e,16384,77)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṃ");}else if(k.KKM(e,16384,77)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"º");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǹ");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ń");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ň");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ñ");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṉ");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṅ");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṇ");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ņ");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŋ");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ʼn");}else if(k.KKM(e,16384,78)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₪");}else if(k.KKM(e,16384,79)&&k.KCM(2,t,"n\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"№");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ò");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȍ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ó");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"\"",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ő");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ô");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǒ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȏ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŏ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"õ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ō");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ö");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȯ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ọ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǫ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ɵ");}else if(k.KKM(e,16384,79)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ø");}else if(k.KKM(e,16384,80)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"p̈");}else if(k.KKM(e,16384,80)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṕ");}else if(k.KKM(e,16384,80)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṗ");}else if(k.KKM(e,16384,80)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"℗");}else if(k.KKM(e,16384,80)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"£");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȑ");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŕ");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ř");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȓ");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ɍ");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṟ");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṙ");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṛ");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŗ");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"®");}else if(k.KKM(e,16384,82)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₨");}else if(k.KKM(e,16384,83)&&k.KCM(2,t,"s\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"ß");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ś");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŝ");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"š");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ș");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṡ");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṣ");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ş");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ß");}else if(k.KKM(e,16384,83)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ſ");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ť");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŧ");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṯ");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẗ");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ț");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṫ");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṭ");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ţ");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"þ");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"†");}else if(k.KKM(e,16384,84)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₮");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ù");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"#",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȕ");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ú");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"\"",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ű");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"û");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǔ");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"&",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȗ");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"*",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŭ");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ũ");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ū");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ü");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ů");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ụ");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ų");}else if(k.KKM(e,16384,85)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"µ");}else if(k.KKM(e,16384,86)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṽ");}else if(k.KKM(e,16384,86)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ṿ");}else if(k.KKM(e,16384,87)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẁ");}else if(k.KKM(e,16384,87)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẃ");}else if(k.KKM(e,16384,87)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŵ");}else if(k.KKM(e,16384,87)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẅ");}else if(k.KKM(e,16384,87)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẘ");}else if(k.KKM(e,16384,87)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẇ");}else if(k.KKM(e,16384,87)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẉ");}else if(k.KKM(e,16384,87)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"₩");}else if(k.KKM(e,16384,88)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ǯ");}else if(k.KKM(e,16384,88)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẍ");}else if(k.KKM(e,16384,88)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẋ");}else if(k.KKM(e,16384,88)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"×");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"`",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ỳ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ý");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ŷ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ỹ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȳ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,":",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ÿ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"@",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẙ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẏ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ỵ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ȝ");}else if(k.KKM(e,16384,89)&&k.KCM(1,t,"$",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¥");}else if(k.KKM(e,16384,90)&&k.KCM(2,t,"D\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"Dž");}else if(k.KKM(e,16384,90)&&k.KCM(2,t,"d\\",2)&&k.KDM(0,t,0)) {r=m=1;k.KO(2,t,"dž");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,"'",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ź");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,"^",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẑ");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,"%",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ž");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,"-",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ƶ");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,"_",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẕ");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,".",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ż");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ẓ");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,",",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ⱬ");}else if(k.KKM(e,16384,90)&&k.KCM(1,t,"=",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"ʒ");}else if(k.KKM(e,16400,220)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¦");}else if(k.KKM(e,16400,220)&&k.KCM(1,t,"|",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"|");}else if(k.KKM(e,16400,220)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,220)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,220)) {r=m=1;k.KO(0,t,"|");k.KDO(-1,t,0);}else if(k.KKM(e,16400,192)&&k.KCM(1,t,"\\",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"¬");}else if(k.KKM(e,16400,192)&&k.KCM(1,t,"~",1)&&k.KDM(0,t,0)) {r=m=1;k.KO(1,t,"~");}else if(k.KKM(e,16400,192)&&k.KIFS(31,this.s56,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,192)&&k.KIFS(31,this.s57,t)) {r=m=1;r=this.g1(t,e);}else if(k.KKM(e,16400,192)) {r=m=1;k.KO(0,t,"~");k.KDO(-1,t,0);}return r;};this.g1=function(t,e) {var k=KeymanWeb,r=0,m=0;return r;};this.KHF=function(e) { - var h = document.getElementsByTagName('head'); - var stext = - "#keyboard_europeanlatin_help { text-align: left; padding: 0; background: #f5e3de; font-size: 8pt; font-family: Tahoma; cursor: default; padding: 4px; }"+ - "#keyboard_europeanlatin_help table { margin-top: 0; }"+ - "#keyboard_europeanlatin_help tr.out td { padding: 4px; border: solid 1px #ad4a28; font-size: 10pt; font-family: Cambria, Tahoma; font-weight: bold; text-align: center; background: white; }"+ - "#keyboard_europeanlatin_help th { padding: 2px 2px; font-size: 8pt; font-family: Tahoma; }"+ - "#keyboard_europeanlatin_help span.elkey { font-weight: normal; background: #e0e0e0; border: outset 1px; padding: 2px 6px; }"+ - "#keyboard_europeanlatin_help th.prompt { text-align: left; white-space: nowrap; }"+ - "#keyboard_europeanlatin_help tr.key th.prompt { background: white; border: none; font-weight: normal; text-align: right; }"+ - "#keyboard_europeanlatin_help tr.key th.title { background: white; border: none; font-weight: bold; vertical-align: middle; text-align: left; padding: 1px; }"+ - "#keyboard_europeanlatin_help tr.key th { min-width: 14px; margin: 2px; font-weight: normal; text-align: center; background: #e0e0e0; border: outset 1px; padding: 2px 2px; }"+ - "#keyboard_europeanlatin_help tr.out th.prompt { background: white; border: none; font-weight: normal; text-align: right; }"+ - "#keyboard_europeanlatin_help tr.gap th { background: white; border: none; height: 7px; }"+ - "#keyboard_europeanlatin_help p { font-weight: bold; }"+ - "#keyboard_eurolatin_tabsheets { color: black; border: solid 1px #ad4a28; margin: 0 4px 4px 4px; background: white; z-index: 101; }"+ - ".keyboard_eurolatin_tabsheet_selected { display: block; padding: 6px 6px 0 6px; }"+ - ".keyboard_eurolatin_tabsheet { display: none; }"+ - "#keyboard_eurolatin_tabs { margin: 4px 4px 0 4px; z-index: 100; }"+ - ".keyboard_eurolatin_tab { background: white; color: #ad4a28; border: solid 1px #ad4a28; display: inline; padding: 1px 4px 0px 4px; height: 16px; }"+ - ".keyboard_eurolatin_tab_selected { background: white; color: #ad4a28; border: solid 1px #ad4a28; display: inline; padding: 1px 4px; border-bottom: none; margin-top: -1px; height: 18px;}"; - - var style = document.createElement("style"); - style.setAttribute("type", "text/css"); - if(style.styleSheet){// IE - style.styleSheet.cssText = stext; - } else {// w3c - var cssText = document.createTextNode(stext); - style.appendChild(cssText); - } - if(h && h.length > 0) - h[0].appendChild(style); - - e=document.getElementById('keyboard_europeanlatin_help'); - if (typeof e.onselectstart!="undefined") //IE route - e.onselectstart=function(){return false;}; - else - { - e.style.MozUserSelect="none"; - e.style.KhtmlUserSelect="none"; - e.style.UserSelect="none"; - } - var ch = e.getElementsByTagName('td'); - - for(var i = 0; i < ch.length; i++) - { - if(ch[i].attributes['o']) - { - var sxv = ch[i].attributes['x'].value, so = ch[i].attributes['o'].value, sc = ch[i].attributes['c'].value; - if(ch[i].attributes['p']) var sp = ch[i].attributes['p'].value; else var sp = ''; - // ch[i].title = 'The following combinations are available: '+so; - var ce = function(tag){return document.createElement(tag);} - var sx = '', sy = '', sz = (sp == '' ? '' : sy); - var e2 = ce('div'); - for(var j = 0; j < sc.length; j++) - { - var chvo = so.charAt(j), chvc = sc.charAt(j), chvp = sp.charAt(j); - /* Special case for characters p umlaut, g tilde because they aren't precomposed */ - if(sxv == ':' && chvo == 'p') { chvo = 'p̈'; chvp = 'P̈'; } - if(sxv == '~' && chvo == 'g') { chvo = 'g̃'; chvp = 'G̃'; } - sx += ''; - sy += ''; - if(sp != '') sz += ''; - } - e2.innerHTML = sx+''+sy+''+sz+(sz == '' ? '' : '')+'
'+sxv+chvc+''+chvo+''+chvp+'
'; - - var es = e2.style; - es.background = '#f3ffdf'; - es.border = 'solid 1px #ad4a28'; - es.padding = '2px'; - es.display = 'none'; - es.position = 'absolute'; - es.left = (ch[i].offsetLeft+4)+'px'; - es.top = '24px'; - e2.onclick = function() { KeymanWeb.KT(); window.event.cancelBubble=true; return false; } - - ch[i].kmw_subitems = e2; - ch[i].appendChild(e2); - ch[i].onmouseover = function() { this.style.background = KeymanWeb_KeyHoverColor; this.kmw_subitems.style.left=this.offsetLeft+4+'px'; this.kmw_subitems.style.top=(this.offsetParent.offsetTop+this.offsetTop+this.offsetHeight-2)+'px'; this.kmw_subitems.style.display = 'block'; return false; } - ch[i].onmouseout = function() { this.style.background = ''; this.kmw_subitems.style.display = 'none'; return false; } - } - else - { - ch[i].onmouseover = function() { this.style.background = KeymanWeb_KeyHoverColor; return false; } - ch[i].onmouseout = function() { this.style.background = ''; return false; } - } - - ch[i].onclick = function(e) { with(this) { if(attributes['x']) KeymanWeb.KT(attributes['x'].value,0); else if(innerHTML != ' ') KeymanWeb.KT(innerHTML); else KeymanWeb.KT(''); } if(!e)e=window.event;if(e) e.cancelBubble=true;} - ch[i].onmousedown = function(e) { KeymanWeb.KSF(); if(!e)e=window.event;if(e) e.cancelBubble=true; } - } - - e.onclick=function() { KeymanWeb.KT(''); return false; } - e.onmousedown=function() { KeymanWeb.KSF(); return false; } - - e = document.getElementById('keyboard_eurolatin_tabs'); - e2 = document.getElementById('keyboard_eurolatin_tabsheets'); - var ch = [document.getElementById('keyboard_eurolatin_letters_tab'), document.getElementById('keyboard_eurolatin_symbols_tab')]; - var ch2 = [document.getElementById('keyboard_eurolatin_letters_tabsheet'), document.getElementById('keyboard_eurolatin_symbols_tabsheet')]; - for(var i = 0; i < ch.length; i++) - ch[i].onclick = (function(i) { return function(e) - { - for(var j = 0; j < ch.length; j++) - { - ch[j].className = "keyboard_eurolatin_tab" + (i == j ? "_selected" : ""); - ch2[j].className = "keyboard_eurolatin_tabsheet" + (i == j ? "_selected" : ""); - } - } - })(i); -} -} \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/cloud/sil_euro_latin-1.8.js b/android/KMEA/app/src/main/assets/cloud/sil_euro_latin-1.8.js new file mode 100644 index 0000000000000000000000000000000000000000..ea29ce348cfbb8407afeb8e62ede5b5166ec8a24 --- /dev/null +++ b/android/KMEA/app/src/main/assets/cloud/sil_euro_latin-1.8.js @@ -0,0 +1,110 @@ +if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_sil_euro_latin());}function Keyboard_sil_euro_latin(){this.KI="Keyboard_sil_euro_latin";this.KN="EuroLatin (SIL)";this.KMINVER="10.0";this.KV=null;this.KH='

More help

Letters
Symbols
Accent
Marks
Type`#\'"^%&*~-_:@.|,
Result `  ̏ ´ ˝ ˆ ˇ  ̑ ˘ ˜ ˉ  ̱ ¨ ˚ ˙ . ¸
Other
Letters
Type = thencegjnostyz\'
Result ɔ ə ɂ ɲ ŋ ɵ ß þ ȝ ʒ
Ɔ Ə Ɂ Ɲ Ŋ Ɵ Þ Ȝ Ʒ

Type \\ thendegknos
Result ð ɛ ǥ ĸ ʼn ø ſ
Ð Ɛ Ǥ Ø
Digraphs Typea\\ed\\zf\\if\\li\\jl\\jn\\jo\\eo\\i
Result æ dž ij lj nj œ ƣ
Æ Dž DŽ IJ Lj LJ Nj NJ Œ Ƣ
Punctuation
and Symbols
Type\\?\\!<<>>\\f\\m\\P\\S\\T\\t\\|\\c\\p\\rt\\m
Result¿¡«»ªº§¦©®

Type\\:\\\'\\,\\_\\M\\N\\-\\~\\...\\.\\>
Result¨´¸¯­¬·
Numbers
and Fractions
Type\\+\\x\\/\\*\\%\\u
Result±×÷°µ

Typen\\o\\1\\2\\3\\4\\5\\6\\7\\8\\9\\0
Result¹²³

Type1//21//32//31//43//41//52//53//54//51//65//61//83//85//87//8
Result½¼¾
Currency
Symbols
Type $ thencefhlnNpPrRwy
Result¢£¥

Type $ thenCdDFgGkKt
Result¤
';this.KM=0;this.KBVER="1.8";this.KMBM=0x0010;this.KVKD="T_G_Tilde T_P_Dieresis";this.KVKL={"tablet":{"font":"Tahoma","layer":[{"id":"default","row":[{"id":1,"key":[{"id":"K_Q","text":"q","sk":[{"id":"U_A78C"}]},{"id":"K_W","text":"w","sk":[{"id":"U_1E81"},{"id":"U_1E83"},{"id":"U_0175"},{"id":"U_1E85"},{"id":"U_1E98"},{"id":"U_1E87"},{"id":"U_1E89"}]},{"id":"K_E","text":"e","sk":[{"text":"\u00E8","id":"U_00E8"},{"text":"\u0205","id":"U_0205"},{"text":"\u00E9","id":"U_00E9"},{"text":"\u00EA","id":"U_00EA"},{"text":"\u011B","id":"U_011B"},{"text":"\u0207","id":"U_0207"},{"text":"\u0115","id":"U_0115"},{"text":"\u1EBD","id":"U_1EBD"},{"text":"\u0113","id":"U_0113"},{"text":"\u00EB","id":"U_00EB"},{"text":"\u0117","id":"U_0117"},{"text":"\u1EB9","id":"U_1EB9"},{"text":"\u0119","id":"U_0119"},{"text":"\u0259","id":"U_0259"},{"text":"\u025B","id":"U_025B"}]},{"id":"K_R","text":"r","sk":[{"id":"U_0211"},{"id":"U_0155"},{"id":"U_0159"},{"id":"U_0213"},{"id":"U_024D"},{"id":"U_1E5F"},{"id":"U_1E59"},{"id":"U_1E5B"},{"id":"U_0157"}]},{"id":"K_T","text":"t","sk":[{"text":"\u0165","id":"U_0165"},{"text":"\u0167","id":"U_0167"},{"text":"\u1E6F","id":"U_1E6F"},{"text":"\u1E97","id":"U_1E97"},{"text":"\u1E6B","id":"U_1E6B"},{"text":"\u1E6D","id":"U_1E6D"},{"text":"\u0163","id":"U_0163"},{"text":"\u021B","id":"U_021B"},{"text":"\u00FE","id":"U_00FE"}]},{"id":"K_Y","text":"y","sk":[{"id":"U_1EF3"},{"id":"U_00FD"},{"id":"U_0177"},{"id":"U_1EF9"},{"id":"U_0233"},{"id":"U_00FF"},{"id":"U_1E99"},{"id":"U_1E8F"},{"id":"U_1EF5"}]},{"id":"K_U","text":"u","sk":[{"id":"U_00F9"},{"id":"U_0215"},{"id":"U_00FA"},{"id":"U_0171"},{"id":"U_00FB"},{"id":"U_01D4"},{"id":"U_0217"},{"id":"U_016D"},{"id":"U_0169"},{"id":"U_016B"},{"id":"U_00FC"},{"id":"U_016F"},{"id":"U_1EE5"},{"id":"U_0173"}]},{"id":"K_I","text":"i","sk":[{"id":"U_00EC"},{"id":"U_0209"},{"id":"U_00ED"},{"id":"U_00EE"},{"id":"U_01D0"},{"id":"U_020B"},{"id":"U_012D"},{"id":"U_0129"},{"id":"U_012B"},{"id":"U_00EF"},{"id":"U_0131"},{"id":"U_1ECB"},{"id":"U_012F"},{"id":"U_0272"},{"id":"U_0133"}]},{"id":"K_O","text":"o","sk":[{"id":"U_00F2"},{"id":"U_020D"},{"id":"U_00F3"},{"id":"U_0151"},{"id":"U_00F4"},{"id":"U_01D2"},{"id":"U_020F"},{"id":"U_014F"},{"id":"U_00F5"},{"id":"U_014D"},{"id":"U_00F6"},{"id":"U_022F"},{"id":"U_1ECD"},{"id":"U_01EB"},{"id":"U_0275"},{"id":"U_00F8"},{"id":"U_0254"},{"id":"U_0153"},{"id":"U_01A3"}]},{"id":"K_P","text":"p","sk":[{"text":"\u1E55","id":"U_1E55"},{"text":"p\u0308","id":"T_P_Dieresis"},{"text":"\u1E57","id":"U_1E57"}]}]},{"id":2,"key":[{"id":"K_A","text":"a","pad":"40","sk":[{"id":"U_00E0"},{"id":"U_0201"},{"id":"U_00E1"},{"id":"U_00E2"},{"id":"U_01CE"},{"id":"U_0203"},{"id":"U_0103"},{"id":"U_00E3"},{"id":"U_0101"},{"id":"U_00E4"},{"id":"U_00E5"},{"id":"U_0227"},{"id":"U_1EA1"},{"id":"U_0105"},{"id":"U_00E6"}]},{"id":"K_S","text":"s","sk":[{"text":"\u015B","id":"U_015B"},{"text":"\u015D","id":"U_015D"},{"text":"\u0161","id":"U_0161"},{"text":"\u1E61","id":"U_1E61"},{"text":"\u1E63","id":"U_1E63"},{"text":"\u015F","id":"U_015F"},{"text":"\u0219","id":"U_0219"},{"text":"\u00DF","id":"U_00DF"},{"text":"\u017F","id":"U_017F"}]},{"id":"K_D","text":"d","sk":[{"id":"U_010F"},{"id":"U_0111"},{"id":"U_1E0F"},{"id":"U_1E0B"},{"id":"U_1E0D"},{"id":"U_1E11"},{"id":"U_00F0"},{"id":"U_01C6"}]},{"id":"K_F","text":"f","sk":[{"id":"U_1E1F"},{"id":"U_FB01"},{"id":"U_FB02"}]},{"id":"K_G","text":"g","sk":[{"text":"\u01F5","id":"U_01F5"},{"text":"\u011D","id":"U_011D"},{"text":"\u01E7","id":"U_01E7"},{"text":"\u011F","id":"U_011F"},{"text":"g\u0303","id":"T_G_Tilde"},{"text":"\u1E21","id":"U_1E21"},{"text":"\u0121","id":"U_0121"},{"text":"\u0123","id":"U_0123"},{"text":"\u0242","id":"U_0242"},{"text":"\u01E5","id":"U_01E5"}]},{"id":"K_H","text":"h","sk":[{"id":"U_0125"},{"id":"U_021F"},{"id":"U_1E2B"},{"id":"U_0127"},{"id":"U_1E96"},{"id":"U_1E27"},{"id":"U_1E23"},{"id":"U_1E25"},{"id":"U_1E29"}]},{"id":"K_J","text":"j","sk":[{"id":"U_0135"},{"id":"U_01F0"},{"id":"U_0249"},{"id":"U_0237"}]},{"id":"K_K","text":"k","sk":[{"id":"U_1E31"},{"id":"U_01E9"},{"id":"U_1E35"},{"id":"U_1E33"},{"id":"U_0137"},{"id":"U_0138"}]},{"id":"K_L","text":"l","sk":[{"id":"U_013A"},{"id":"U_013E"},{"id":"U_026B"},{"id":"U_0142"},{"id":"U_1E3B"},{"id":"U_0140"},{"id":"U_1E37"},{"id":"U_013C"},{"id":"U_01C9"}]},{"id":"T_new_20","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_SHIFT","text":"*Shift*","width":"100","sp":"1","nextlayer":"shift"},{"id":"K_Z","text":"z","sk":[{"id":"U_017A"},{"id":"U_1E91"},{"id":"U_017E"},{"id":"U_01B6"},{"id":"U_1E95"},{"id":"U_017C"},{"id":"U_1E93"},{"id":"U_2C6C"},{"id":"U_0292"},{"id":"U_01EF"}]},{"id":"K_X","text":"x","sk":[{"id":"U_1E8D"},{"id":"U_1E8B"}]},{"id":"K_C","text":"c","sk":[{"id":"U_0107"},{"id":"U_0109"},{"id":"U_010D"},{"id":"U_010B"},{"id":"U_00E7"}]},{"id":"K_V","text":"v","sk":[{"id":"U_1E7D"},{"id":"U_1E7F"}]},{"id":"K_B","text":"b","sk":[{"id":"U_0180"},{"id":"U_1E07"},{"id":"U_1E03"},{"id":"U_1E05"}]},{"id":"K_N","text":"n","sk":[{"id":"U_014B"},{"id":"U_01F9"},{"id":"U_0144"},{"id":"U_0148"},{"id":"U_00F1"},{"id":"U_1E49"},{"id":"U_1E45"},{"id":"U_1E47"},{"id":"U_0146"},{"id":"U_0149"},{"id":"U_01CC"}]},{"id":"K_M","text":"m","sk":[{"id":"U_1E3F"},{"id":"U_1E41"},{"id":"U_1E43"}]},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"id":"U_0021"},{"id":"U_003F"},{"id":"U_0027"},{"id":"U_0022"},{"id":"U_005C"},{"id":"U_003A"},{"id":"U_003B"}]},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_NUMERALS","text":"*123*","width":"150","sp":"1","nextlayer":"numeric"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_34","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_36","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]},{"id":"shift","row":[{"id":1,"key":[{"id":"K_Q","text":"Q","sk":[{"id":"U_A78B"}]},{"id":"K_W","text":"W","sk":[{"id":"U_1E80"},{"id":"U_1E82"},{"id":"U_0174"},{"id":"U_1E84"},{"id":"U_1E86"},{"id":"U_1E88"}]},{"id":"K_E","text":"E","sk":[{"id":"U_00C8"},{"id":"U_0204"},{"id":"U_00C9"},{"id":"U_00CA"},{"id":"U_011A"},{"id":"U_0206"},{"id":"U_0114"},{"id":"U_1EBC"},{"id":"U_0112"},{"id":"U_00CB"},{"id":"U_0116"},{"id":"U_1EB8"},{"id":"U_0118"},{"id":"U_018F"},{"id":"U_0190"}]},{"id":"K_R","text":"R","sk":[{"id":"U_0210"},{"id":"U_0154"},{"id":"U_0158"},{"id":"U_0212"},{"id":"U_024C"},{"id":"U_1E5E"},{"id":"U_1E58"},{"id":"U_1E5A"},{"id":"U_0156"}]},{"id":"K_T","text":"T","sk":[{"text":"\u0164","id":"U_0164"},{"text":"\u0166","id":"U_0166"},{"text":"\u1E6E","id":"U_1E6E"},{"text":"\u1E6A","id":"U_1E6A"},{"text":"\u1E6C","id":"U_1E6C"},{"text":"\u0162","id":"U_0162"},{"text":"\u021A","id":"U_021A"},{"text":"\u00DE","id":"U_00DE"}]},{"id":"K_Y","text":"Y","sk":[{"id":"U_1EF2"},{"id":"U_00DD"},{"id":"U_0176"},{"id":"U_1EF8"},{"id":"U_0232"},{"id":"U_0178"},{"id":"U_1E8E"},{"id":"U_1EF4"}]},{"id":"K_U","text":"U","sk":[{"id":"U_00D9"},{"id":"U_0214"},{"id":"U_00DA"},{"id":"U_0170"},{"id":"U_00DB"},{"id":"U_01D3"},{"id":"U_0216"},{"id":"U_016C"},{"id":"U_0168"},{"id":"U_016A"},{"id":"U_00DC"},{"id":"U_016E"},{"id":"U_1EE4"},{"id":"U_0172"}]},{"id":"K_I","text":"I","sk":[{"id":"U_00CC"},{"id":"U_0208"},{"id":"U_00CD"},{"id":"U_00CE"},{"id":"U_01CF"},{"id":"U_020A"},{"id":"U_012C"},{"id":"U_0128"},{"id":"U_012A"},{"id":"U_00CF"},{"id":"U_0130"},{"id":"U_1ECA"},{"id":"U_012E"},{"id":"U_019D"}]},{"id":"K_O","text":"O","sk":[{"id":"U_00D2"},{"id":"U_020C"},{"id":"U_00D3"},{"id":"U_0150"},{"id":"U_00D4"},{"id":"U_01D1"},{"id":"U_020E"},{"id":"U_014E"},{"id":"U_00D5"},{"id":"U_014C"},{"id":"U_00D6"},{"id":"U_022E"},{"id":"U_1ECC"},{"id":"U_01EA"},{"id":"U_019F"},{"id":"U_00D8"},{"id":"U_0186"},{"id":"U_0152"},{"id":"U_01A2"}]},{"id":"K_P","text":"P","sk":[{"text":"\u1E54","id":"U_1E54"},{"text":"P\u0308","id":"T_P_Dieresis"},{"text":"\u1E56","id":"U_1E56"}]}]},{"id":2,"key":[{"id":"K_A","text":"A","pad":"40","sk":[{"id":"U_00C0"},{"id":"U_0200"},{"id":"U_00C1"},{"id":"U_00C2"},{"id":"U_01CD"},{"id":"U_0202"},{"id":"U_0102"},{"id":"U_00C3"},{"id":"U_0100"},{"id":"U_00C4"},{"id":"U_00C5"},{"id":"U_0226"},{"id":"U_1EA0"},{"id":"U_0104"},{"id":"U_00C6"}]},{"id":"K_S","text":"S","sk":[{"text":"\u015A","id":"U_015A"},{"text":"\u015C","id":"U_015C"},{"text":"\u0160","id":"U_0160"},{"text":"\u1E60","id":"U_1E60"},{"text":"\u1E62","id":"U_1E62"},{"text":"\u015E","id":"U_015E"},{"text":"\u0218","id":"U_0218"},{"text":"\u1E9E","id":"U_1E9E"}]},{"id":"K_D","text":"D","sk":[{"id":"U_010E"},{"id":"U_0110"},{"id":"U_1E0E"},{"id":"U_1E0A"},{"id":"U_1E0C"},{"id":"U_1E10"},{"id":"U_00D0"},{"id":"U_01C5"},{"id":"U_01C4"}]},{"id":"K_F","text":"F","sk":[{"id":"U_1E1E"}]},{"id":"K_G","text":"G","sk":[{"text":"\u01F4","id":"U_01F4"},{"text":"\u011C","id":"U_011C"},{"text":"\u01E6","id":"U_01E6"},{"text":"\u011E","id":"U_011E"},{"text":"G\u0303","id":"T_G_Tilde"},{"text":"\u1E20","id":"U_1E20"},{"text":"\u0120","id":"U_0120"},{"text":"\u0122","id":"U_0122"},{"text":"\u0241","id":"U_0241"},{"text":"\u01E4","id":"U_01E4"}]},{"id":"K_H","text":"H","sk":[{"id":"U_0124"},{"id":"U_021E"},{"id":"U_1E2A"},{"id":"U_0126"},{"id":"U_1E26"},{"id":"U_1E22"},{"id":"U_1E24"},{"id":"U_1E28"}]},{"id":"K_J","text":"J","sk":[{"id":"U_0134"},{"id":"U_0248"}]},{"id":"K_K","text":"K","sk":[{"id":"U_1E30"},{"id":"U_01E8"},{"id":"U_1E34"},{"id":"U_1E32"},{"id":"U_0136"}]},{"id":"K_L","text":"L","sk":[{"id":"U_0139"},{"id":"U_013D"},{"id":"U_2C62"},{"id":"U_0141"},{"id":"U_1E3A"},{"id":"U_013F"},{"id":"U_1E36"},{"id":"U_013B"},{"id":"U_01C8"},{"id":"U_01C7"}]},{"id":"T_new_60","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_SHIFT","text":"*Shift*","width":"100","sp":"2","nextlayer":"default"},{"id":"K_Z","text":"Z","sk":[{"id":"U_021C"},{"id":"U_0179"},{"id":"U_1E90"},{"id":"U_017D"},{"id":"U_01B5"},{"id":"U_1E94"},{"id":"U_017B"},{"id":"U_1E92"},{"id":"U_2C6B"},{"id":"U_01B7"},{"id":"U_01EE"}]},{"id":"K_X","text":"X","sk":[{"id":"U_1E8C"},{"id":"U_1E8A"}]},{"id":"K_C","text":"C","sk":[{"id":"U_0106"},{"id":"U_0108"},{"id":"U_010C"},{"id":"U_010A"},{"id":"U_00C7"}]},{"id":"K_V","text":"V","sk":[{"id":"U_1E7C"},{"id":"U_1E7E"}]},{"id":"K_B","text":"B","sk":[{"id":"U_0243"},{"id":"U_1E06"},{"id":"U_1E02"},{"id":"U_1E04"}]},{"id":"K_N","text":"N","sk":[{"id":"U_014A"},{"id":"U_01F8"},{"id":"U_0143"},{"id":"U_0147"},{"id":"U_00D1"},{"id":"U_1E48"},{"id":"U_1E44"},{"id":"U_1E46"},{"id":"U_0145"},{"id":"U_01CB"},{"id":"U_01CA"}]},{"id":"K_M","text":"M","sk":[{"id":"U_1E3E"},{"id":"U_1E40"},{"id":"U_1E42"}]},{"id":"K_PERIOD","text":".","layer":"default","sk":[{"text":",","id":"K_COMMA"},{"text":"!","id":"U_0021","layer":"default"},{"text":"?","id":"U_003F","layer":"default"},{"text":"'","id":"U_0027","layer":"default"},{"text":"\"","id":"U_0022","layer":"default"},{"text":"\\","id":"U_005C","layer":"default"},{"text":":","id":"U_003A","layer":"default"},{"text":";","id":"U_003B","layer":"default"}]},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_NUMERALS","text":"*123*","width":"150","sp":"1","nextlayer":"numeric"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_74","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_76","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]},{"id":"numeric","row":[{"id":1,"key":[{"id":"K_1","text":"1","sp":"0","sk":[{"id":"U_00B9"},{"id":"U_00BD"},{"id":"U_2153"},{"id":"U_00BC"},{"id":"U_2155"},{"id":"U_2159"},{"id":"U_215B"}]},{"id":"K_2","text":"2","sp":"0","sk":[{"id":"U_00B2"},{"id":"U_2154"},{"id":"U_2156"}]},{"id":"K_3","text":"3","sp":"0","sk":[{"id":"U_00B3"},{"id":"U_00BE"},{"id":"U_2157"},{"id":"U_215C"}]},{"id":"K_4","text":"4","sp":"0","sk":[{"id":"U_2074"},{"id":"U_2158"}]},{"id":"K_5","text":"5","sp":"0","sk":[{"id":"U_2075"},{"id":"U_215A"},{"id":"U_215D"}]},{"id":"K_6","text":"6","sp":"0","sk":[{"id":"U_2076"}]},{"id":"K_7","text":"7","sp":"0","sk":[{"id":"U_2077"},{"id":"U_215E"}]},{"id":"K_8","text":"8","sp":"0","sk":[{"id":"U_2078"}]},{"id":"K_9","text":"9","sp":"0","sk":[{"id":"U_2079"}]},{"id":"K_0","text":"0","sp":"0","sk":[{"id":"U_2070"}]}]},{"id":2,"key":[{"id":"U_0024","text":"$","pad":"40"},{"id":"U_0040","text":"@"},{"id":"U_0023","text":"#"},{"id":"U_0025","text":"%","sk":[{"id":"U_2030"},{"id":"U_2031"}]},{"id":"U_0026","text":"&"},{"id":"U_005F","text":"_"},{"id":"U_003D","text":"="},{"id":"U_007C","text":"|"},{"id":"U_002F","text":"\/","sk":[{"text":"\\","id":"U_005C"}]},{"id":"T_new_164","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_LOWER","text":"*abc*","width":"100","sp":"1","nextlayer":"default"},{"id":"U_005B","text":"[","sk":[{"id":"U_007B"},{"id":"U_00AB"},{"id":"U_003C"}]},{"id":"U_0028","text":"("},{"id":"U_0029","text":")"},{"id":"U_005D","text":"]","sk":[{"id":"U_007D"},{"id":"U_00BB"},{"id":"U_003E"}]},{"id":"U_002B","text":"+"},{"id":"U_002D","text":"-"},{"id":"U_002A","text":"*"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"id":"U_0021"},{"id":"U_003F"},{"id":"U_0027"},{"id":"U_0022"},{"id":"U_005C"},{"id":"U_003A"},{"id":"U_003B"}]},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_CURRENCIES","text":"*Currency*","width":"150","sp":"1","nextlayer":"currency"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_178","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_180","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]},{"id":"symbol","row":[{"id":1,"key":[{"id":"U_0060","text":"`"},{"id":"U_007E","text":"~"},{"id":"U_005E","text":"^"},{"id":"U_00A8","text":"\u00A8"},{"id":"U_00B4","text":"\u00B4"},{"id":"U_00B8","text":"\u00B8"},{"id":"U_00AF","text":"\u00AF"},{"id":"U_00BF","text":"\u00BF"},{"id":"U_00A1","text":"\u00A1"},{"id":"U_00AC","text":"\u00AC"}]},{"id":2,"key":[{"id":"U_00AA","text":"\u00AA","pad":"40"},{"id":"U_00BA","text":"\u00BA"},{"id":"U_00B6","text":"\u00B6"},{"id":"U_00A7","text":"\u00A7"},{"id":"U_2021","text":"\u2021"},{"id":"U_2020","text":"\u2020"},{"id":"U_00A6","text":"\u00A6"},{"id":"U_2122","text":"\u2122"},{"id":"U_2116","text":"\u2116"},{"id":"T_new_336","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_LOWER","text":"*abc*","width":"100","sp":"1","nextlayer":"default"},{"id":"U_00A9","text":"\u00A9"},{"id":"U_2117","text":"\u2117"},{"id":"U_00AE","text":"\u00AE"},{"id":"U_2014","text":"\u2014"},{"id":"U_2013","text":"\u2013"},{"id":"U_2022","text":"\u2022"},{"id":"U_00B7","text":"\u00B7"},{"id":"U_2026","text":"\u2026","sk":[{"id":"U_00B1"},{"id":"U_00D7"},{"id":"U_00F7"},{"id":"U_00B5"},{"id":"U_00B0"},{"id":"U_2031"}]},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_CURRENCIES","text":"*Currency*","width":"150","sp":"1","nextlayer":"numeric"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_350","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_352","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]},{"id":"currency","row":[{"id":1,"key":[{"id":"U_00A2","text":"\u00A2","nextlayer":"numeric"},{"id":"U_20AC","text":"\u20AC","nextlayer":"numeric"},{"id":"U_20A4","text":"\u20A4","nextlayer":"numeric"},{"id":"U_00A3","text":"\u00A3","nextlayer":"numeric"},{"id":"U_20AA","text":"\u20AA","nextlayer":"numeric"},{"id":"U_20A8","text":"\u20A8","nextlayer":"numeric"},{"id":"U_20B9","text":"\u20B9","nextlayer":"numeric"},{"id":"U_20A9","text":"\u20A9","nextlayer":"numeric"},{"id":"U_00A5","text":"\u00A5","nextlayer":"numeric"},{"id":"U_00A4","text":"\u00A4","nextlayer":"numeric"}]},{"id":2,"key":[{"id":"U_20AB","text":"\u20AB","pad":"40"},{"id":"U_20AF","text":"\u20AF"},{"id":"U_20B1","text":"\u20B1"},{"id":"U_20A3","text":"\u20A3"},{"id":"U_20B0","text":"\u20B0"},{"id":"U_20B2","text":"\u20B2"},{"id":"U_20B4","text":"\u20B4"},{"id":"U_20A1","text":"\u20A1"},{"id":"U_20AD","text":"\u20AD"},{"id":"T_new_2717","text":"","width":"10","sp":"10"}]},{"id":3,"key":[{"id":"K_LOWER","text":"*abc*","width":"100","sp":"1","nextlayer":"default"},{"id":"U_20A6","text":"\u20A6"},{"id":"U_20A7","text":"\u20A7"},{"id":"U_20AE","text":"\u20AE"},{"id":"U_0BF9","text":"\u0BF9"},{"id":"U_09F3","text":"\u09F3"},{"id":"U_0E3F","text":"\u0E3F"},{"id":"T_new_2725","text":"","sp":"9"},{"id":"T_new_2726","text":"","sp":"9"},{"id":"K_BKSP","text":"*BkSp*","width":"100","sp":"1"}]},{"id":4,"key":[{"id":"K_SYMBOLS","text":"*Symbol*","width":"150","sp":"1","nextlayer":"symbol"},{"id":"K_LOPT","text":"*Menu*","width":"100","sp":"1"},{"id":"T_new_2731","text":"","width":"10","sp":"10"},{"id":"K_SPACE","text":"","width":"610","sp":"0"},{"id":"T_new_2733","text":"","width":"10","sp":"10"},{"id":"K_ENTER","text":"*Enter*","width":"140","sp":"1"}]}]}]}};this.s13=KeymanWeb.KLOAD(this.KI,"option_key","");this.s14="`#'\"^%&*~-_:@.|,=\\$";this.s15="àèìǹòùẁỳÀÈÌǸÒÙẀỲ";this.s16="aeinouwyAEINOUWY";this.s17="ȁȅȉȍȑȕȀȄȈȌȐȔ";this.s18="aeioruAEIORU";this.s19="áćéǵíḱĺḿńóṕŕśúẃýźÁĆÉǴÍḰĹḾŃÓṔŔŚÚẂÝŹ";this.s20="acegiklmnoprsuwyzACEGIKLMNOPRSUWYZ";this.s21="őűŐŰ";this.s22="ouOU";this.s23="âĉêĝĥîĵôŝûŵŷẑÂĈÊĜĤÎĴÔŜÛŴŶẐ";this.s24="aceghijosuwyzACEGHIJOSUWYZ";this.s25="ǎǍčČďĎěĚǧǦȟȞǐǏǰǩǨľĽňŇǒǑřŘšŠťŤǔǓǯǮžŽ";this.s26="aAcCdDeEgGhHiIjkKlLnNoOrRsStTuUxXzZ";this.s27="ȂȃȆȇȊȋȎȏȒȓȖȗ";this.s28="AaEeIiOoRrUu";this.s29="ăĂĕĔğĞḫḪĭĬŏŎŭŬ";this.s30="aAeEgGhHiIoOuU";this.s31="ãÃẽẼĩĨɫⱢñÑõÕũŨṽṼỹỸ";this.s32="aAeEiIlLnNoOuUvVyY";this.s33="ĀāɃƀĐđĒēḠḡĦħĪīɈɉŁłŌōɌɍŦŧŪūȲȳƵƶ";this.s34="AaBbDdEeGgHhIiJjLlOoRrTtUuYyZz";this.s35="ḆḇḎḏẖḴḵḺḻṈṉṞṟṮṯẔẕ";this.s36="BbDdhKkLlNnRrTtZz";this.s37="äëḧïöẗüẅẍÿÄËḦÏÖÜẄẌŸ";this.s38="aehiotuwxyAEHIOUWXY";this.s39="åÅůŮẘẙșȘțȚ";this.s40="aAuUwysStT";this.s41="ȧȦḃḂċĊḋḊėĖḟḞġĠḣḢıİȷŀĿṁṀṅṄȯȮṗṖṙṘṡṠṫṪẇẆẋẊẏẎżŻ";this.s42="aAbBcCdDeEfFgGhHiIjlLmMnNoOpPrRsStTwWxXyYzZ";this.s43="ẠạḄḅḌḍẸẹḤḥỊịḲḳḶḷṂṃṆṇỌọṚṛṢṣṬṭỤụṾṿẈẉỴỵẒẓ";this.s44="AaBbDdEeHhIiKkLlMmNnOoRrSsTtUuVvWwYyZz";this.s45="ąĄçÇḑḐęĘģĢḩḨįĮķĶļĻņŅǫǪŗŖşŞţŢųŲⱬⱫ";this.s46="aAcCdDeEgGhHiIkKlLnNoOrRsStTuUzZ";this.s47="ƆɔƏəɁɂƝɲŊŋƟɵẞßÞþȜȝƷʒ";this.s48="CcEeGgJjNnOoSsTtYyZz";this.s49="©ÐðƐɛªǤǥĸ—º–ʼnØø¶℗®§ſ‡†µ×¡¦¨¬­¯°±´·¸¿÷‰•¹²³⁴⁵⁶⁷⁸⁹⁰";this.s50="cDdEefGgkMmNnOoPprSsTtux!|:~-_*+'.,?/%>1234567890";this.s51="¤¢₯₫€€₱₣₲₰₴₡₭₤₦₪₧£₹₨₮₩¥";this.s52="CcDdEefFGghkKlNnPpRrtwy";this.s55="touch";this.s56="touch";this.s57="touch";this.s58="touch";this.s59="touch";this.s60="";this.s61="";this.s62="";this.KVER="10.0.1099.0";this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)&&k.KIFS(31,this.s56,t)) {r=m=1;k.KDC(0,t);k.KO(-1,t,"g̃");}else if(k.KKM(e,16400,256)&&k.KIFS(31,this.s57,t)) {r=m=1;k.KDC(0,t);k.KO(-1,t,"G̃");}else if(k.KKM(e,16384,257)&&k.KIFS(31,this.s58,t)) {r=m=1;k.KDC(0,t);k.KO(-1,t,"p̈");}else if(k.KKM(e,16400,257)&&k.KIFS(31,this.s59,t)) {r=m=1;k.KDC(0,t);k.KO(-1,t,"P̈");}else if(k.KKM(e,16400,49)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¡");}else if(k.KKM(e,16400,222)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ꞌ");}else if(k.KKM(e,16400,222)&&k.KFCM(2,t,['\"',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"\"");}else if(k.KKM(e,16400,222)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,222)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"\"");k.KDO(-1,t,0);}else if(k.KKM(e,16400,51)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"#");}else if(k.KKM(e,16400,51)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,51)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"#");k.KDO(-1,t,0);}else if(k.KKM(e,16400,52)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"$");}else if(k.KKM(e,16400,52)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,52)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"$");k.KDO(-1,t,0);}else if(k.KKM(e,16400,53)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"‰");}else if(k.KKM(e,16400,53)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"%");}else if(k.KKM(e,16400,53)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,53)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"%");k.KDO(-1,t,0);}else if(k.KKM(e,16400,55)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"&");}else if(k.KKM(e,16400,55)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,55)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"&");k.KDO(-1,t,0);}else if(k.KKM(e,16384,222)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ꞌ");}else if(k.KKM(e,16384,222)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"´");}else if(k.KKM(e,16384,222)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"'");}else if(k.KKM(e,16384,222)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16384,222)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"'");k.KDO(-1,t,0);}else if(k.KKM(e,16400,56)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"°");}else if(k.KKM(e,16400,56)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"*");}else if(k.KKM(e,16400,56)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,56)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"*");k.KDO(-1,t,0);}else if(k.KKM(e,16400,187)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"±");}else if(k.KKM(e,16384,188)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¸");}else if(k.KKM(e,16384,188)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,",");}else if(k.KKM(e,16384,188)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16384,188)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,",");k.KDO(-1,t,0);}else if(k.KKM(e,16384,189)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"­");}else if(k.KKM(e,16384,189)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"-");}else if(k.KKM(e,16384,189)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16384,189)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"-");k.KDO(-1,t,0);}else if(k.KKM(e,16384,190)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"·");}else if(k.KKM(e,16384,190)&&k.KFCM(2,t,['·','.'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"…");}else if(k.KKM(e,16384,190)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,".");}else if(k.KKM(e,16384,190)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16384,190)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,".");k.KDO(-1,t,0);}else if(k.KKM(e,16384,191)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"÷");}else if(k.KKM(e,16384,48)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⁰");}else if(k.KKM(e,16384,49)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¹");}else if(k.KKM(e,16384,50)&&k.KFCM(3,t,['1','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"½");}else if(k.KKM(e,16384,50)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"²");}else if(k.KKM(e,16384,50)&&k.KFCM(2,t,['¹','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"½");}else if(k.KKM(e,16384,51)&&k.KFCM(3,t,['1','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅓");}else if(k.KKM(e,16384,51)&&k.KFCM(3,t,['2','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅔");}else if(k.KKM(e,16384,51)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"³");}else if(k.KKM(e,16384,51)&&k.KFCM(2,t,['¹','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⅓");}else if(k.KKM(e,16384,51)&&k.KFCM(2,t,['²','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⅔");}else if(k.KKM(e,16384,52)&&k.KFCM(3,t,['1','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"¼");}else if(k.KKM(e,16384,52)&&k.KFCM(3,t,['3','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"¾");}else if(k.KKM(e,16384,52)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⁴");}else if(k.KKM(e,16384,52)&&k.KFCM(2,t,['¹','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¼");}else if(k.KKM(e,16384,52)&&k.KFCM(2,t,['³','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¾");}else if(k.KKM(e,16384,53)&&k.KFCM(3,t,['1','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅕");}else if(k.KKM(e,16384,53)&&k.KFCM(3,t,['2','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅖");}else if(k.KKM(e,16384,53)&&k.KFCM(3,t,['3','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅗");}else if(k.KKM(e,16384,53)&&k.KFCM(3,t,['4','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅘");}else if(k.KKM(e,16384,53)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⁵");}else if(k.KKM(e,16384,54)&&k.KFCM(3,t,['1','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅙");}else if(k.KKM(e,16384,54)&&k.KFCM(3,t,['5','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅚");}else if(k.KKM(e,16384,54)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⁶");}else if(k.KKM(e,16384,55)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⁷");}else if(k.KKM(e,16384,56)&&k.KFCM(3,t,['1','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅛");}else if(k.KKM(e,16384,56)&&k.KFCM(3,t,['3','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅜");}else if(k.KKM(e,16384,56)&&k.KFCM(3,t,['5','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅝");}else if(k.KKM(e,16384,56)&&k.KFCM(3,t,['7','/','/'])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"⅞");}else if(k.KKM(e,16384,56)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⁸");}else if(k.KKM(e,16384,56)&&k.KFCM(2,t,['¹','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⅛");}else if(k.KKM(e,16384,56)&&k.KFCM(2,t,['³','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⅜");}else if(k.KKM(e,16384,56)&&k.KFCM(2,t,['⁵','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⅝");}else if(k.KKM(e,16384,56)&&k.KFCM(2,t,['⁷','/'])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⅞");}else if(k.KKM(e,16384,57)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"⁹");}else if(k.KKM(e,16400,186)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¨");}else if(k.KKM(e,16400,186)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,":");}else if(k.KKM(e,16400,186)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,186)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,":");k.KDO(-1,t,0);}else if(k.KKM(e,16400,188)&&k.KFCM(1,t,['<'])&&this.s13===this.s60) {r=m=1;k.KDC(1,t);k.KO(-1,t,"«");}else if(k.KKM(e,16400,188)&&k.KFCM(2,t,['<',{t:'d',d:1}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"<<");k.KDO(-1,t,1);}else if(k.KKM(e,16400,188)&&k.KFCM(1,t,['«'])) {r=m=1;k.KDC(1,t);k.KO(-1,t,"<<");k.KDO(-1,t,1);}else if(k.KKM(e,16384,187)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"=");}else if(k.KKM(e,16384,187)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16384,187)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"=");k.KDO(-1,t,0);}else if(k.KKM(e,16400,190)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"•");}else if(k.KKM(e,16400,190)&&k.KFCM(1,t,['>'])&&this.s13===this.s61) {r=m=1;k.KDC(1,t);k.KO(-1,t,"»");}else if(k.KKM(e,16400,190)&&k.KFCM(2,t,['>',{t:'d',d:1}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,">>");k.KDO(-1,t,1);}else if(k.KKM(e,16400,190)&&k.KFCM(1,t,['»'])) {r=m=1;k.KDC(1,t);k.KO(-1,t,">>");k.KDO(-1,t,1);}else if(k.KKM(e,16400,191)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¿");}else if(k.KKM(e,16400,50)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"@");}else if(k.KKM(e,16400,50)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,50)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"@");k.KDO(-1,t,0);}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"À");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȁ");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Á");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Â");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǎ");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȃ");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ă");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ã");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ā");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ä");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Å");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȧ");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ạ");}else if(k.KKM(e,16400,65)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ą");}else if(k.KKM(e,16400,66)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ƀ");}else if(k.KKM(e,16400,66)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḇ");}else if(k.KKM(e,16400,66)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḃ");}else if(k.KKM(e,16400,66)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḅ");}else if(k.KKM(e,16400,67)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ć");}else if(k.KKM(e,16400,67)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ĉ");}else if(k.KKM(e,16400,67)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Č");}else if(k.KKM(e,16400,67)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ċ");}else if(k.KKM(e,16400,67)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ç");}else if(k.KKM(e,16400,67)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ɔ");}else if(k.KKM(e,16400,67)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¤");}else if(k.KKM(e,16400,68)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ď");}else if(k.KKM(e,16400,68)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Đ");}else if(k.KKM(e,16400,68)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḏ");}else if(k.KKM(e,16400,68)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḋ");}else if(k.KKM(e,16400,68)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḍ");}else if(k.KKM(e,16400,68)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḑ");}else if(k.KKM(e,16400,68)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ð");}else if(k.KKM(e,16400,68)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₯");}else if(k.KKM(e,16400,69)&&k.KFCM(3,t,['A','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"Æ");}else if(k.KKM(e,16400,69)&&k.KFCM(3,t,['O','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"Œ");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"È");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȅ");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"É");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ê");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ě");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȇ");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ĕ");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẽ");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ē");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ë");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ė");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẹ");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ę");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ə");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ɛ");}else if(k.KKM(e,16400,69)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"€");}else if(k.KKM(e,16400,70)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḟ");}else if(k.KKM(e,16400,70)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₣");}else if(k.KKM(e,16400,71)&&k.KFCM(3,t,['N','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"Ŋ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"G̃");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǵ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ĝ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǧ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ğ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḡ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ġ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ģ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ɂ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǥ");}else if(k.KKM(e,16400,71)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₲");}else if(k.KKM(e,16400,72)&&k.KFCM(3,t,['T','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"Þ");}else if(k.KKM(e,16400,72)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ĥ");}else if(k.KKM(e,16400,72)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȟ");}else if(k.KKM(e,16400,72)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḫ");}else if(k.KKM(e,16400,72)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ħ");}else if(k.KKM(e,16400,72)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḧ");}else if(k.KKM(e,16400,72)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḣ");}else if(k.KKM(e,16400,72)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḥ");}else if(k.KKM(e,16400,72)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḩ");}else if(k.KKM(e,16400,73)&&k.KFCM(3,t,['O','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"Ƣ");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ì");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȉ");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Í");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Î");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǐ");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȋ");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ĭ");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ĩ");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ī");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ï");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"İ");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ị");}else if(k.KKM(e,16400,73)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Į");}else if(k.KKM(e,16400,74)&&k.KFCM(3,t,['I','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"IJ");}else if(k.KKM(e,16400,74)&&k.KFCM(3,t,['L','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"LJ");}else if(k.KKM(e,16400,74)&&k.KFCM(3,t,['N','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"NJ");}else if(k.KKM(e,16400,74)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ĵ");}else if(k.KKM(e,16400,74)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ɉ");}else if(k.KKM(e,16400,74)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ɲ");}else if(k.KKM(e,16400,75)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḱ");}else if(k.KKM(e,16400,75)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǩ");}else if(k.KKM(e,16400,75)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḵ");}else if(k.KKM(e,16400,75)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḳ");}else if(k.KKM(e,16400,75)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ķ");}else if(k.KKM(e,16400,75)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₭");}else if(k.KKM(e,16400,76)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ĺ");}else if(k.KKM(e,16400,76)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ľ");}else if(k.KKM(e,16400,76)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ɫ");}else if(k.KKM(e,16400,76)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ł");}else if(k.KKM(e,16400,76)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḻ");}else if(k.KKM(e,16400,76)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŀ");}else if(k.KKM(e,16400,76)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḷ");}else if(k.KKM(e,16400,76)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ļ");}else if(k.KKM(e,16400,77)&&k.KFCM(3,t,['T','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"™");}else if(k.KKM(e,16400,77)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ḿ");}else if(k.KKM(e,16400,77)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṁ");}else if(k.KKM(e,16400,77)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṃ");}else if(k.KKM(e,16400,77)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"—");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǹ");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ń");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ň");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ñ");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṉ");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṅ");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṇ");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ņ");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŋ");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"–");}else if(k.KKM(e,16400,78)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₦");}else if(k.KKM(e,16400,79)&&k.KFCM(3,t,['N','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"№");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ò");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȍ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ó");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['\"',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ő");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ô");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǒ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȏ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŏ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Õ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ō");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ö");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȯ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ọ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǫ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ɵ");}else if(k.KKM(e,16400,79)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ø");}else if(k.KKM(e,16400,80)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"P̈");}else if(k.KKM(e,16400,80)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṕ");}else if(k.KKM(e,16400,80)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṗ");}else if(k.KKM(e,16400,80)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¶");}else if(k.KKM(e,16400,80)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₧");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȑ");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŕ");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ř");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȓ");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ɍ");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṟ");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṙ");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṛ");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŗ");}else if(k.KKM(e,16400,82)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₹");}else if(k.KKM(e,16400,83)&&k.KFCM(3,t,['S','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"ẞ");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ś");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŝ");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Š");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ș");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṡ");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṣ");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ş");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẞ");}else if(k.KKM(e,16400,83)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"§");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ť");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŧ");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṯ");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ț");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṫ");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṭ");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ţ");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Þ");}else if(k.KKM(e,16400,84)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"‡");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ù");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȕ");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ú");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['\"',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ű");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Û");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǔ");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȗ");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŭ");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ũ");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ū");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ü");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ů");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ụ");}else if(k.KKM(e,16400,85)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ų");}else if(k.KKM(e,16400,86)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṽ");}else if(k.KKM(e,16400,86)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ṿ");}else if(k.KKM(e,16400,87)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẁ");}else if(k.KKM(e,16400,87)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẃ");}else if(k.KKM(e,16400,87)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŵ");}else if(k.KKM(e,16400,87)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẅ");}else if(k.KKM(e,16400,87)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẇ");}else if(k.KKM(e,16400,87)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẉ");}else if(k.KKM(e,16400,88)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ǯ");}else if(k.KKM(e,16400,88)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẍ");}else if(k.KKM(e,16400,88)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẋ");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ỳ");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ý");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ŷ");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ỹ");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȳ");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ÿ");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẏ");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ỵ");}else if(k.KKM(e,16400,89)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ȝ");}else if(k.KKM(e,16400,90)&&k.KFCM(3,t,['D','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"DŽ");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ź");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẑ");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ž");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ƶ");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẕ");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ż");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ẓ");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ⱬ");}else if(k.KKM(e,16400,90)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"Ʒ");}else if(k.KKM(e,16384,220)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"\\");}else if(k.KKM(e,16384,220)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16384,220)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"\\");k.KDO(-1,t,0);}else if(k.KKM(e,16400,54)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"^");}else if(k.KKM(e,16400,54)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,54)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"^");k.KDO(-1,t,0);}else if(k.KKM(e,16400,189)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¯");}else if(k.KKM(e,16400,189)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"_");}else if(k.KKM(e,16400,189)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,189)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"_");k.KDO(-1,t,0);}else if(k.KKM(e,16384,192)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"`");}else if(k.KKM(e,16384,192)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16384,192)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"`");k.KDO(-1,t,0);}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"à");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȁ");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"á");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"â");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǎ");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȃ");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ă");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ã");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ā");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ä");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"å");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȧ");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ạ");}else if(k.KKM(e,16384,65)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ą");}else if(k.KKM(e,16384,66)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ƀ");}else if(k.KKM(e,16384,66)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḇ");}else if(k.KKM(e,16384,66)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḃ");}else if(k.KKM(e,16384,66)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḅ");}else if(k.KKM(e,16384,67)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ć");}else if(k.KKM(e,16384,67)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĉ");}else if(k.KKM(e,16384,67)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"č");}else if(k.KKM(e,16384,67)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ċ");}else if(k.KKM(e,16384,67)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ç");}else if(k.KKM(e,16384,67)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ɔ");}else if(k.KKM(e,16384,67)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"©");}else if(k.KKM(e,16384,67)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¢");}else if(k.KKM(e,16384,68)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ď");}else if(k.KKM(e,16384,68)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"đ");}else if(k.KKM(e,16384,68)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḏ");}else if(k.KKM(e,16384,68)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḋ");}else if(k.KKM(e,16384,68)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḍ");}else if(k.KKM(e,16384,68)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḑ");}else if(k.KKM(e,16384,68)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ð");}else if(k.KKM(e,16384,68)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₫");}else if(k.KKM(e,16384,69)&&k.KFCM(3,t,['a','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"æ");}else if(k.KKM(e,16384,69)&&k.KFCM(3,t,['o','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"œ");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"è");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȅ");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"é");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ê");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ě");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȇ");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĕ");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẽ");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ē");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ë");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ė");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẹ");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ę");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ə");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ɛ");}else if(k.KKM(e,16384,69)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"€");}else if(k.KKM(e,16384,70)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḟ");}else if(k.KKM(e,16384,70)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ª");}else if(k.KKM(e,16384,70)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₱");}else if(k.KKM(e,16384,71)&&k.KFCM(3,t,['n','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"ŋ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"g̃");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǵ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĝ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǧ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ğ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḡ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ġ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ģ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ɂ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǥ");}else if(k.KKM(e,16384,71)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₰");}else if(k.KKM(e,16384,72)&&k.KFCM(3,t,['t','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"þ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĥ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȟ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḫ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ħ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẖ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḧ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḣ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḥ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḩ");}else if(k.KKM(e,16384,72)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₴");}else if(k.KKM(e,16384,73)&&k.KFCM(3,t,['f','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"fi");}else if(k.KKM(e,16384,73)&&k.KFCM(3,t,['o','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"ƣ");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ì");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȉ");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"í");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"î");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǐ");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȋ");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĭ");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĩ");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ī");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ï");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ı");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ị");}else if(k.KKM(e,16384,73)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"į");}else if(k.KKM(e,16384,74)&&k.KFCM(3,t,['i','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"ij");}else if(k.KKM(e,16384,74)&&k.KFCM(3,t,['L','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"Lj");}else if(k.KKM(e,16384,74)&&k.KFCM(3,t,['l','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"lj");}else if(k.KKM(e,16384,74)&&k.KFCM(3,t,['N','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"Nj");}else if(k.KKM(e,16384,74)&&k.KFCM(3,t,['n','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"nj");}else if(k.KKM(e,16384,74)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĵ");}else if(k.KKM(e,16384,74)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǰ");}else if(k.KKM(e,16384,74)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ɉ");}else if(k.KKM(e,16384,74)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȷ");}else if(k.KKM(e,16384,74)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ɲ");}else if(k.KKM(e,16384,75)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḱ");}else if(k.KKM(e,16384,75)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǩ");}else if(k.KKM(e,16384,75)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḵ");}else if(k.KKM(e,16384,75)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḳ");}else if(k.KKM(e,16384,75)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ķ");}else if(k.KKM(e,16384,75)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĸ");}else if(k.KKM(e,16384,75)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₡");}else if(k.KKM(e,16384,76)&&k.KFCM(3,t,['f','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"fl");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ĺ");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ľ");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ɫ");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ł");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḻ");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŀ");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḷ");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ļ");}else if(k.KKM(e,16384,76)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₤");}else if(k.KKM(e,16384,77)&&k.KFCM(3,t,['t','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"™");}else if(k.KKM(e,16384,77)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ḿ");}else if(k.KKM(e,16384,77)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṁ");}else if(k.KKM(e,16384,77)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṃ");}else if(k.KKM(e,16384,77)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"º");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǹ");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ń");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ň");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ñ");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṉ");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṅ");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṇ");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ņ");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŋ");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ʼn");}else if(k.KKM(e,16384,78)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₪");}else if(k.KKM(e,16384,79)&&k.KFCM(3,t,['n','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"№");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ò");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȍ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ó");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['\"',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ő");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ô");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǒ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȏ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŏ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"õ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ō");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ö");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȯ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ọ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǫ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ɵ");}else if(k.KKM(e,16384,79)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ø");}else if(k.KKM(e,16384,80)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"p̈");}else if(k.KKM(e,16384,80)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṕ");}else if(k.KKM(e,16384,80)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṗ");}else if(k.KKM(e,16384,80)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"℗");}else if(k.KKM(e,16384,80)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"£");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȑ");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŕ");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ř");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȓ");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ɍ");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṟ");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṙ");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṛ");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŗ");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"®");}else if(k.KKM(e,16384,82)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₨");}else if(k.KKM(e,16384,83)&&k.KFCM(3,t,['s','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"ß");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ś");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŝ");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"š");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ș");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṡ");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṣ");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ş");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ß");}else if(k.KKM(e,16384,83)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ſ");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ť");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŧ");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṯ");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẗ");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ț");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṫ");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṭ");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ţ");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"þ");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"†");}else if(k.KKM(e,16384,84)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₮");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ù");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['#',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȕ");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ú");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['\"',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ű");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"û");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǔ");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['&',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȗ");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['*',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŭ");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ũ");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ū");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ü");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ů");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ụ");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ų");}else if(k.KKM(e,16384,85)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"µ");}else if(k.KKM(e,16384,86)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṽ");}else if(k.KKM(e,16384,86)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ṿ");}else if(k.KKM(e,16384,87)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẁ");}else if(k.KKM(e,16384,87)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẃ");}else if(k.KKM(e,16384,87)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŵ");}else if(k.KKM(e,16384,87)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẅ");}else if(k.KKM(e,16384,87)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẘ");}else if(k.KKM(e,16384,87)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẇ");}else if(k.KKM(e,16384,87)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẉ");}else if(k.KKM(e,16384,87)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"₩");}else if(k.KKM(e,16384,88)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ǯ");}else if(k.KKM(e,16384,88)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẍ");}else if(k.KKM(e,16384,88)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẋ");}else if(k.KKM(e,16384,88)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"×");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['`',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ỳ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ý");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ŷ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ỹ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȳ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,[':',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ÿ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['@',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẙ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẏ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ỵ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ȝ");}else if(k.KKM(e,16384,89)&&k.KFCM(2,t,['$',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¥");}else if(k.KKM(e,16384,90)&&k.KFCM(3,t,['D','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"Dž");}else if(k.KKM(e,16384,90)&&k.KFCM(3,t,['d','\\',{t:'d',d:0}])) {r=m=1;k.KDC(3,t);k.KO(-1,t,"dž");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,['\'',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ź");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,['^',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẑ");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,['%',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ž");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,['-',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ƶ");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,['_',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẕ");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,['.',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ż");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ẓ");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,[',',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ⱬ");}else if(k.KKM(e,16384,90)&&k.KFCM(2,t,['=',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"ʒ");}else if(k.KKM(e,16400,220)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¦");}else if(k.KKM(e,16400,220)&&k.KFCM(2,t,['|',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"|");}else if(k.KKM(e,16400,220)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,220)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"|");k.KDO(-1,t,0);}else if(k.KKM(e,16400,192)&&k.KFCM(2,t,['\\',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"¬");}else if(k.KKM(e,16400,192)&&k.KFCM(2,t,['~',{t:'d',d:0}])) {r=m=1;k.KDC(2,t);k.KO(-1,t,"~");}else if(k.KKM(e,16400,192)&&k.KIFS(31,this.s55,t)) {r=m=1;k.KDC(0,t);r=this.g1(t,e);}else if(k.KKM(e,16400,192)&&this.s13===this.s62) {r=m=1;k.KDC(0,t);k.KO(-1,t,"~");k.KDO(-1,t,0);}return r;};this.g1=function(t,e) {var k=KeymanWeb,r=0,m=0;return r;};this.KHF=function(e) { + var h = document.getElementsByTagName('head'); + var stext = + "#keyboard_europeanlatin_help { text-align: left; padding: 0; background: #f5e3de; font-size: 8pt; font-family: Tahoma; cursor: default; padding: 4px; }"+ + "#keyboard_europeanlatin_help table { margin-top: 0; }"+ + "#keyboard_europeanlatin_help tr.out td { padding: 4px; border: solid 1px #ad4a28; font-size: 10pt; font-family: Cambria, Tahoma; font-weight: bold; text-align: center; background: white; }"+ + "#keyboard_europeanlatin_help th { padding: 2px 2px; font-size: 8pt; font-family: Tahoma; }"+ + "#keyboard_europeanlatin_help span.elkey { font-weight: normal; background: #e0e0e0; border: outset 1px; padding: 2px 6px; }"+ + "#keyboard_europeanlatin_help th.prompt { text-align: left; white-space: nowrap; }"+ + "#keyboard_europeanlatin_help tr.key th.prompt { background: white; border: none; font-weight: normal; text-align: right; }"+ + "#keyboard_europeanlatin_help tr.key th.title { background: white; border: none; font-weight: bold; vertical-align: middle; text-align: left; padding: 1px; }"+ + "#keyboard_europeanlatin_help tr.key th { min-width: 14px; margin: 2px; font-weight: normal; text-align: center; background: #e0e0e0; border: outset 1px; padding: 2px 2px; }"+ + "#keyboard_europeanlatin_help tr.out th.prompt { background: white; border: none; font-weight: normal; text-align: right; }"+ + "#keyboard_europeanlatin_help tr.gap th { background: white; border: none; height: 7px; }"+ + "#keyboard_europeanlatin_help p { font-weight: bold; }"+ + "#keyboard_eurolatin_tabsheets { color: black; border: solid 1px #ad4a28; margin: 0 4px 4px 4px; background: white; z-index: 101; }"+ + ".keyboard_eurolatin_tabsheet_selected { display: block; padding: 6px 6px 0 6px; }"+ + ".keyboard_eurolatin_tabsheet { display: none; }"+ + "#keyboard_eurolatin_tabs { margin: 4px 4px 0 4px; z-index: 100; }"+ + ".keyboard_eurolatin_tab { background: white; color: #ad4a28; border: solid 1px #ad4a28; display: inline; padding: 1px 4px 0px 4px; height: 16px; }"+ + ".keyboard_eurolatin_tab_selected { background: white; color: #ad4a28; border: solid 1px #ad4a28; display: inline; padding: 1px 4px; border-bottom: none; margin-top: -1px; height: 18px;}"; + + var style = document.createElement("style"); + style.setAttribute("type", "text/css"); + if(style.styleSheet){// IE + style.styleSheet.cssText = stext; + } else {// w3c + var cssText = document.createTextNode(stext); + style.appendChild(cssText); + } + if(h && h.length > 0) + h[0].appendChild(style); + + e=document.getElementById('keyboard_europeanlatin_help'); + if (typeof e.onselectstart!="undefined") //IE route + e.onselectstart=function(){return false;}; + else + { + e.style.MozUserSelect="none"; + e.style.KhtmlUserSelect="none"; + e.style.UserSelect="none"; + } + var ch = e.getElementsByTagName('td'); + + for(var i = 0; i < ch.length; i++) + { + if(ch[i].attributes['o']) + { + var sxv = ch[i].attributes['x'].value, so = ch[i].attributes['o'].value, sc = ch[i].attributes['c'].value; + if(ch[i].attributes['p']) var sp = ch[i].attributes['p'].value; else var sp = ''; + // ch[i].title = 'The following combinations are available: '+so; + var ce = function(tag){return document.createElement(tag);} + var sx = '', sy = '', sz = (sp == '' ? '' : sy); + var e2 = ce('div'); + for(var j = 0; j < sc.length; j++) + { + var chvo = so.charAt(j), chvc = sc.charAt(j), chvp = sp.charAt(j); + /* Special case for characters p umlaut, g tilde because they aren't precomposed */ + if(sxv == ':' && chvo == 'p') { chvo = 'p̈'; chvp = 'P̈'; } + if(sxv == '~' && chvo == 'g') { chvo = 'g̃'; chvp = 'G̃'; } + sx += ''; + sy += ''; + if(sp != '') sz += ''; + } + e2.innerHTML = sx+''+sy+''+sz+(sz == '' ? '' : '')+'
'+sxv+chvc+''+chvo+''+chvp+'
'; + + var es = e2.style; + es.background = '#f3ffdf'; + es.border = 'solid 1px #ad4a28'; + es.padding = '2px'; + es.display = 'none'; + es.position = 'absolute'; + es.left = (ch[i].offsetLeft+4)+'px'; + es.top = '24px'; + e2.onclick = function() { KeymanWeb.KT(); window.event.cancelBubble=true; return false; } + + ch[i].kmw_subitems = e2; + ch[i].appendChild(e2); + ch[i].onmouseover = function() { this.style.background = KeymanWeb_KeyHoverColor; this.kmw_subitems.style.left=this.offsetLeft+4+'px'; this.kmw_subitems.style.top=(this.offsetParent.offsetTop+this.offsetTop+this.offsetHeight-2)+'px'; this.kmw_subitems.style.display = 'block'; return false; } + ch[i].onmouseout = function() { this.style.background = ''; this.kmw_subitems.style.display = 'none'; return false; } + } + else + { + ch[i].onmouseover = function() { this.style.background = KeymanWeb_KeyHoverColor; return false; } + ch[i].onmouseout = function() { this.style.background = ''; return false; } + } + + ch[i].onclick = function(e) { with(this) { if(attributes['x']) KeymanWeb.KT(attributes['x'].value,0); else if(innerHTML != ' ') KeymanWeb.KT(innerHTML); else KeymanWeb.KT(''); } if(!e)e=window.event;if(e) e.cancelBubble=true;} + ch[i].onmousedown = function(e) { KeymanWeb.KSF(); if(!e)e=window.event;if(e) e.cancelBubble=true; } + } + + e.onclick=function() { KeymanWeb.KT(''); return false; } + e.onmousedown=function() { KeymanWeb.KSF(); return false; } + + e = document.getElementById('keyboard_eurolatin_tabs'); + e2 = document.getElementById('keyboard_eurolatin_tabsheets'); + var ch = [document.getElementById('keyboard_eurolatin_letters_tab'), document.getElementById('keyboard_eurolatin_symbols_tab')]; + var ch2 = [document.getElementById('keyboard_eurolatin_letters_tabsheet'), document.getElementById('keyboard_eurolatin_symbols_tabsheet')]; + for(var i = 0; i < ch.length; i++) + ch[i].onclick = (function(i) { return function(e) + { + for(var j = 0; j < ch.length; j++) + { + ch[j].className = "keyboard_eurolatin_tab" + (i == j ? "_selected" : ""); + ch2[j].className = "keyboard_eurolatin_tabsheet" + (i == j ? "_selected" : ""); + } + } + })(i); +} +} \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index c8a64717260455fd66913e5b4c503c3b042852d2..9c3fee21d4b10c6f256a3ec2a097f6973659a658 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -1,6 +1,6 @@ @@ -16,8 +16,8 @@ function init() { //document.body.style.backgroundColor="transparent"; - window.console.log('Device type = '+device); - window.console.log('Keyboard height = '+oskHeight); + //window.console.log('Device type = '+device); + //window.console.log('Keyboard height = '+oskHeight); var kmw=window['keyman']; kmw.init({'app':device,'fonts':'packages/'}); kmw['util']['setOption']('attachType','manual'); @@ -26,6 +26,7 @@ kmw['hideKeyboard'] = hideKeyboard; kmw['getOskHeight'] = getOskHeight; kmw['getOskWidth'] = getOskWidth; + kmw['beepKeyboard'] = beepKeyboard; var ta = document.getElementById('ta'); kmw['setActiveElement'](ta); @@ -57,25 +58,25 @@ if(oskWidth <= 0) { oskWidth = window.jsInterface.getKeyboardWidth(); } - window.console.log('Keyboard width = '+oskWidth); + //window.console.log('Keyboard width = '+oskWidth); return oskWidth; } + function beepKeyboard() { + window.jsInterface.beepKeyboard(); + } + // Query KMW if a given keyboard uses chiral modifiers. function setIsChiral(keyboardProperties) { var name = typeof(keyboardProperties.internalName) == 'undefined' ? keyboardProperties.keyboardName : keyboardProperties.internalName; var kmw=window['keyman']; var isChiral = kmw.isChiral(name); - window.console.log('For keyboard "' + name + '"'); - window.console.log('setIsChiral = ' + isChiral); window.jsInterface.setIsChiral(isChiral); return true; } function setKeymanLanguage(keyboardName, internalName, languageName, langId, kbdFile, font, oskFont, package) { - //oskFont.files = ['NotoSansSyriacWestern-Regular.svg#NotoSansSyriacWesternRegular']; - //window.console.log('oskFonts = '+JSON.stringify(oskFont)); var kmw=window['keyman']; var kbdInterface=kmw['interface']; @@ -108,11 +109,11 @@ }; if (font) { k.KFont = font; - window.console.log('keyboard.html assigning k.KFont = ' + font); + //window.console.log('keyboard.html assigning k.KFont = ' + font); } if (oskFont) { k.KOskFont = oskFont; - window.console.log('keyboard.html assigning k.KOskFont = ' + oskFont); + //window.console.log('keyboard.html assigning k.KOskFont = ' + oskFont); } kbdInterface.registerStub(k); @@ -121,7 +122,7 @@ } function insertText(dn, s) { - window.console.log('insertText('+ dn +', ' + s +');'); + //window.console.log('insertText('+ dn +', ' + s +');'); window.jsInterface.insertText(dn, s); } @@ -130,6 +131,12 @@ kmw.resetContext(); } + // Tell KMW to switch to "numeric" layer + function setNumericLayer() { + var kmw=window['keyman']; + kmw.setNumericLayer(); + } + function updateKMText(text) { if(text == undefined) { text = ''; @@ -198,7 +205,6 @@ fragmentToggle = (fragmentToggle + 1) % 100; var kmw = window['keyman']; var pos = kmw['touchMenuPos'](); - window.console.log('showHelpBubble ' + pos); window.location.hash = 'showHelpBubble-' + fragmentToggle + '+keyPos=' + pos; } @@ -206,18 +212,18 @@ var kmw=window['keyman']; // KMW only needs keyID to process the popup key. keyText merely logged to console - window.console.log('executePopupKey('+keyID+'); keyText: ' + keyText); + //window.console.log('executePopupKey('+keyID+'); keyText: ' + keyText); kmw['executePopupKey'](keyID); } function executeHardwareKeystroke(code, shift, lstates) { var kmw=window['keyman']; - window.console.log('executeHardwareKeystroke:('+code+', ' + shift + ', ' + lstates + ');'); + //window.console.log('executeHardwareKeystroke:('+code+', ' + shift + ', ' + lstates + ');'); try { var r = kmw['executeHardwareKeystroke'](code, shift, lstates); - window.console.log('executeHardwareKeystroke completed with '+r); + //window.console.log('executeHardwareKeystroke completed with '+r); } catch(e) { - window.console.log('oops: '+e); + window.console.log('executeHardwareKeystroke exception: '+e); } } diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java index adcb739be131cafb66bef3ab419a1038434426ad..0c0c78ba39a97a81a39851859065827850adc0c7 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java @@ -1,7 +1,7 @@ package com.tavultesoft.kmea; -import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.content.DialogInterface; @@ -21,8 +21,8 @@ public class ConfirmDialogFragment extends DialogFragment { return new AlertDialog.Builder(getActivity()) .setTitle(title) - .setMessage("Would you like to download this keyboard?") - .setPositiveButton("Download", new DialogInterface.OnClickListener() { + .setMessage(getString(R.string.confirm_download)) + .setPositiveButton(getString(R.string.label_download), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Download keyboard @@ -33,7 +33,7 @@ public class ConfirmDialogFragment extends DialogFragment { } } }) - .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + .setNegativeButton(getString(R.string.label_cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Cancel dialog.dismiss(); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMContextWrapper.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMContextWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..a6795598e36aa9456d4f30c35dc57e5068a96983 --- /dev/null +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMContextWrapper.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2018 SIL International. All rights reserved. + */ + +package com.tavultesoft.kmea; + +import android.content.Context; +import android.content.ContextWrapper; +import android.support.v7.app.AppCompatActivity; + +public class KMContextWrapper extends ContextWrapper { + + public KMContextWrapper(Context base) { + super(base); + } + + public AppCompatActivity getAppCompatActivity() { + return (AppCompatActivity)getApplicationContext(); + } +} diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHardwareKeyboardInterpreter.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHardwareKeyboardInterpreter.java index c0de2d700414957f6f091837295d460e8ffe1601..8346efa2667548ab3afe4a75d212619c3a64622c 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHardwareKeyboardInterpreter.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMHardwareKeyboardInterpreter.java @@ -13,93 +13,109 @@ public class KMHardwareKeyboardInterpreter implements KeyEvent.Callback { * Maps relevant keycodes from android.view.keyevent to the standard keycodes used by Keyman * keyboards. Is conveniently based at 0, else we might need to HashMap it instead. */ + + /** + * From reference: https://source.android.com/devices/input#understanding-hid-usages-and-event-codes + * The Android API sometimes refers to the Linux key code associated with a key as its "scan code". + * This is technically incorrect in but it helps to distinguish Linux key codes from Android + * key codes in the API. + * + * Maps relevant Linux key code (LKC) to the standard key codes used by Keyman keyboards + * Table: https://source.android.com/devices/input/keyboard-devices + * + * Limitations: Intentionally not assigning number pad keys + */ private static final - int keyCodeMap[] = { - 0, // public static final int KEYCODE_UNKNOWN = 0; - 0, // public static final int KEYCODE_SOFT_LEFT = 1; - 0, // public static final int KEYCODE_SOFT_RIGHT = 2; - 0, // public static final int KEYCODE_HOME = 3; - 0, // public static final int KEYCODE_BACK = 4; - 0, // public static final int KEYCODE_CALL = 5; - 0, // public static final int KEYCODE_ENDCALL = 6; - '0', // public static final int KEYCODE_0 = 7; - '1', // public static final int KEYCODE_1 = 8; - '2', // public static final int KEYCODE_2 = 9; - '3', // public static final int KEYCODE_3 = 10; - '4', // public static final int KEYCODE_4 = 11; - '5', // public static final int KEYCODE_5 = 12; - '6', // public static final int KEYCODE_6 = 13; - '7', // public static final int KEYCODE_7 = 14; - '8', // public static final int KEYCODE_8 = 15; - '9', // public static final int KEYCODE_9 = 16; - 0, // public static final int KEYCODE_STAR = 17; - 0, // public static final int KEYCODE_POUND = 18; - 0, // public static final int KEYCODE_DPAD_UP = 19; - 0, // public static final int KEYCODE_DPAD_DOWN = 20; - 0, // public static final int KEYCODE_DPAD_LEFT = 21; - 0, // public static final int KEYCODE_DPAD_RIGHT = 22; - 0, // public static final int KEYCODE_DPAD_CENTER = 23; - 0, // public static final int KEYCODE_VOLUME_UP = 24; - 0, // public static final int KEYCODE_VOLUME_DOWN = 25; - 0, // public static final int KEYCODE_POWER = 26; - 0, // public static final int KEYCODE_CAMERA = 27; - 0, // public static final int KEYCODE_CLEAR = 28; - 'A', // public static final int KEYCODE_A = 29; - 'B', // public static final int KEYCODE_B = 30; - 'C', // public static final int KEYCODE_C = 31; - 'D', // public static final int KEYCODE_D = 32; - 'E', // public static final int KEYCODE_E = 33; - 'F', // public static final int KEYCODE_F = 34; - 'G', // public static final int KEYCODE_G = 35; - 'H', // public static final int KEYCODE_H = 36; - 'I', // public static final int KEYCODE_I = 37; - 'J', // public static final int KEYCODE_J = 38; - 'K', // public static final int KEYCODE_K = 39; - 'L', // public static final int KEYCODE_L = 40; - 'M', // public static final int KEYCODE_M = 41; - 'N', // public static final int KEYCODE_N = 42; - 'O', // public static final int KEYCODE_O = 43; - 'P', // public static final int KEYCODE_P = 44; - 'Q', // public static final int KEYCODE_Q = 45; - 'R', // public static final int KEYCODE_R = 46; - 'S', // public static final int KEYCODE_S = 47; - 'T', // public static final int KEYCODE_T = 48; - 'U', // public static final int KEYCODE_U = 49; - 'V', // public static final int KEYCODE_V = 50; - 'W', // public static final int KEYCODE_W = 51; - 'X', // public static final int KEYCODE_X = 52; - 'Y', // public static final int KEYCODE_Y = 53; - 'Z', // public static final int KEYCODE_Z = 54; - 188, // public static final int KEYCODE_COMMA = 55; - 190, // public static final int KEYCODE_PERIOD = 56; - 0, // public static final int KEYCODE_ALT_LEFT = 57; - 0, // public static final int KEYCODE_ALT_RIGHT = 58; - 0, // public static final int KEYCODE_SHIFT_LEFT = 59; - 0, // public static final int KEYCODE_SHIFT_RIGHT = 60; - 0, // public static final int KEYCODE_TAB = 61; - 32, // public static final int KEYCODE_SPACE = 62; - 0, // public static final int KEYCODE_SYM = 63; - 0, // public static final int KEYCODE_EXPLORER = 64; - 0, // public static final int KEYCODE_ENVELOPE = 65; - 13, // public static final int KEYCODE_ENTER = 66; - 0, // public static final int KEYCODE_DEL = 67; - 192, // public static final int KEYCODE_GRAVE = 68; - 189, // public static final int KEYCODE_MINUS = 69; - 187, // public static final int KEYCODE_EQUALS = 70; - 219, // public static final int KEYCODE_LEFT_BRACKET = 71; - 221, // public static final int KEYCODE_RIGHT_BRACKET = 72; - 220, // public static final int KEYCODE_BACKSLASH = 73; - 186, // public static final int KEYCODE_SEMICOLON = 74; - 222, // public static final int KEYCODE_APOSTROPHE = 75; - 191, // public static final int KEYCODE_SLASH = 76; - 0, // public static final int KEYCODE_AT = 77; - 0, // public static final int KEYCODE_NUM = 78; - 0, // public static final int KEYCODE_HEADSETHOOK = 79; - 0, // public static final int KEYCODE_FOCUS = 80; - 0, // public static final int KEYCODE_PLUS = 81; - 0, // public static final int KEYCODE_MENU = 82; - 0, // public static final int KEYCODE_NOTIFICATION = 83; - 0 // public static final int KEYCODE_SEARCH = 84; + int scanCodeMap[] = { + 0, // padding = 0x00; + 0, // public static final int KEY_ESC = 0x01; + '1', // public static final int KEY_1 = 0x02; + '2', // public static final int KEY_2 = 0x03; + '3', // public static final int KEY_3 = 0x04; + '4', // public static final int KEY_4 = 0x05; + '5', // public static final int KEY_5 = 0x06; + '6', // public static final int KEY_6 = 0x07; + '7', // public static final int KEY_7 = 0x08; + '8', // public static final int KEY_8 = 0x09; + '9', // public static final int KEY_9 = 0x0A; + '0', // public static final int KEY_0 = 0x0B; + 189, // public static final int KEY_MINUS = 0x0C; + 187, // public static final int KEY_EQUALS = 0x0D; + 8, // public static final int KEY_BACKSPACE = 0x0E; + 9, // public static final int KEY_TAB = 0x0F; + 'Q', // public static final int KEY_Q = 0x10; + 'W', // public static final int KEY_W = 0x11; + 'E', // public static final int KEY_E = 0x12; + 'R', // public static final int KEY_R = 0x13; + 'T', // public static final int KEY_T = 0x14; + 'Y', // public static final int KEY_Y = 0x15; + 'U', // public static final int KEY_U = 0x16; + 'I', // public static final int KEY_I = 0x17; + 'O', // public static final int KEY_O = 0x18; + 'P', // public static final int KEY_P = 0x19; + 219, // public static final int KEY_LEFTBRACE = 0x1A; + 221, // public static final int KEY_RIGHTBRACE = 0x1B; + 13, // public static final int KEY_ENTER = 0x1C; + 0, // public static final int KEY_LEFTCTRL = 0x1D; + 'A', // public static final int KEY_A = 0x1E; + 'S', // public static final int KEY_S = 0x1F; + 'D', // public static final int KEY_D = 0x20; + 'F', // public static final int KEY_F = 0x21; + 'G', // public static final int KEY_G = 0x22; + 'H', // public static final int KEY_H = 0x23; + 'J', // public static final int KEY_J = 0x24; + 'K', // public static final int KEY_K = 0x25; + 'L', // public static final int KEY_L = 0x26; + 186, // public static final int KEY_SEMICOLON = 0x27; + 222, // public static final int KEY_APOSTROPHE = 0x28; + 192, // public static final int KEY_GRAVE = 0x29; + 0, // public static final int KEY_LEFTSHIFT = 0x2A; + 220, // public static final int KEY_BACKSLASH = 0x2B; + 'Z', // public static final int KEY_Z = 0x2C; + 'X', // public static final int KEY_X = 0x2D; + 'C', // public static final int KEY_C = 0x2E; + 'V', // public static final int KEY_V = 0x2F; + 'B', // public static final int KEY_B = 0x30; + 'N', // public static final int KEY_N = 0x31; + 'M', // public static final int KEY_M = 0x32; + 188, // public static final int KEY_COMMA = 0x33; + 190, // public static final int KEY_DOT = 0x34; + 191, // public static final int KEY_SLASH = 0x35; + 0, // public static final int KEY_RIGHTSHIFT = 0x36; + 0, // public static final int KEY_KPASTERISK = 0x37; + 0, // public static final int KEY_LEFTALT = 0x38; + 32, // public static final int KEY_SPACE = 0x39; + 0, // public static final int KEY_CAPSLOCK = 0x3A; + 0, // public static final int KEY_F1 = 0x3B; + 0, // public static final int KEY_F2 = 0x3C; + 0, // public static final int KEY_F3 = 0x3D; + 0, // public static final int KEY_F4 = 0x3E; + 0, // public static final int KEY_F5 = 0x3F; + 0, // public static final int KEY_F6 = 0x40; + 0, // public static final int KEY_F7 = 0x41; + 0, // public static final int KEY_F8 = 0x42; + 0, // public static final int KEY_F9 = 0x43; + 0, // public static final int KEY_F10 = 0x44; + 0, // public static final int KEY_NUMLOCK = 0x45; + 0, // public static final int KEY_SCROLLLOCK = 0x46; + 0, // public static final int KEY_KP7 = 0x47; + 0, // public static final int KEY_KP8 = 0x48; + 0, // public static final int KEY_KP9 = 0x49; + 0, // public static final int KEY_KPMINUS = 0x4A; + 0, // public static final int KEY_KP4 = 0x4B; + 0, // public static final int KEY_KP5 = 0x4C; + 0, // public static final int KEY_KP6 = 0x4D; + 0, // public static final int KEY_KPPLUS = 0x4E; + 0, // public static final int KEY_KP1 = 0x4F; + 0, // public static final int KEY_KP2 = 0x50; + 0, // public static final int KEY_KP3 = 0x51; + 0, // public static final int KEY_KP0 = 0x52; + 0, // public static final int KEY_KPDOT = 0x53; + 0, // padding 0x54; + 0, // public static final int KEY_ZENKAKUHANKAKU = 0x55; + 226 // public static final int KEY_102ND = 0x56; + + // Many more KEYS currently not used by KMW... }; private final Context context; @@ -113,11 +129,6 @@ public class KMHardwareKeyboardInterpreter implements KeyEvent.Callback { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode > 84 || keyCode < 0) { - // The key is outside the range of keys we understand - return false; - } - boolean isChiral = KMManager.getKMKeyboard(this.keyboardType).getChirality(); // States of modifier keys @@ -156,13 +167,16 @@ public class KMHardwareKeyboardInterpreter implements KeyEvent.Callback { return false; } - if (keyCodeMap[keyCode] == 0) { + // Range check scanCode. If it's beyond our expected range, just use "0" + int scanCode = event.getScanCode(); + int code = (scanCode >= 0 && scanCode < scanCodeMap.length) ? scanCodeMap[scanCode] : 0 ; + if (code == 0) { // Not an alphanumeric, punctuation or enter/tab/space key return false; } // Send keystroke to KeymanWeb for processing: will return true to swallow the keystroke - return KMManager.executeHardwareKeystroke(keyCodeMap[keyCode], keymanModifiers, keyboardType, Lstates); + return KMManager.executeHardwareKeystroke(code, keymanModifiers, keyboardType, Lstates); } @Override 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 0690f88dbd35a0783ea988d872622fc28f67bbd9..d7005a127fe9945af02169531cdff0b259bd630a 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 @@ -17,6 +17,7 @@ import com.google.firebase.analytics.FirebaseAnalytics; import com.tavultesoft.kmea.KMManager.KeyboardType; import com.tavultesoft.kmea.KeyboardEventHandler.EventType; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; +import com.tavultesoft.kmea.util.FileUtils; import android.annotation.SuppressLint; import android.content.Context; @@ -130,6 +131,7 @@ final class KMKeyboard extends WebView { // Send console errors to Firebase Analytics. // (Ignoring spurious message "No keyboard stubs exist = ...") // TODO: Analyze if this error warrants reverting to default keyboard + // TODO: Fix base error rather than trying to ignore it "No keyboard stubs exist" if ((cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) && (!cm.message().startsWith("No keyboard stubs exist"))) { sendKMWError(cm.lineNumber(), cm.sourceId(), cm.message()); Toast.makeText(context, "Fatal Error with " + currentKeyboard + @@ -260,7 +262,7 @@ final class KMKeyboard extends WebView { * @return String */ public static String textFontFilename() { - return keyboardRoot + txtFont; + return txtFont.isEmpty() ? "" : keyboardRoot + txtFont; } /** @@ -351,9 +353,6 @@ final class KMKeyboard extends WebView { String jsFormat = "javascript:setKeymanLanguage('%s','%s','%s','%s','%s', %s, %s, '%s')"; String jsString = String.format(jsFormat, keyboardName, keyboardID, languageName, languageID, keyboardPath, tFont, oFont, packageID); loadUrl(jsString); - if (KMManager.isDebugMode()) { - Log.d("KMKeyboard", jsString); - } this.packageID = packageID; this.keyboardID = keyboardID; @@ -410,7 +409,7 @@ final class KMKeyboard extends WebView { if (kFont == null) { txtFont = ""; } else { - if (kFont.endsWith(".ttf") || kFont.endsWith(".otf")) { + if (FileUtils.hasFontExtension(kFont)) { txtFont = kFont; tFont = String.format("{\"family\":\"font_family_%s\",\"files\":[\"%s%s\"]}", kFont.substring(0, kFont.length() - 4), keyboardRoot, kFont); } else { @@ -424,7 +423,7 @@ final class KMKeyboard extends WebView { if (kOskFont == null || kOskFont.isEmpty()) { oskFont = null; } else { - if (kOskFont.endsWith(".ttf") || kOskFont.endsWith(".otf")) { + if (FileUtils.hasFontExtension(kOskFont)) { oskFont = kOskFont; oFont = String.format("{\"family\":\"font_family_%s\",\"files\":[\"%s%s\"]}", kOskFont.substring(0, kOskFont.length() - 4), keyboardRoot, kOskFont); } else { @@ -455,9 +454,6 @@ final class KMKeyboard extends WebView { String jsFormat = "javascript:setKeymanLanguage('%s','%s','%s','%s','%s', %s, %s, '%s')"; String jsString = String.format(jsFormat, keyboardName, keyboardID, languageName, languageID, keyboardPath, tFont, oFont, packageID); loadUrl(jsString); - if (KMManager.isDebugMode()) { - Log.d("KMKeyboard", jsString); - } this.packageID = packageID; this.keyboardID = keyboardID; @@ -579,7 +575,7 @@ final class KMKeyboard extends WebView { int length = sourceArray.length(); for (int i = 0; i < length; i++) { fontFile = sourceArray.getString(i); - if (fontFile.endsWith(".ttf") || fontFile.endsWith(".otf")) { + if (FileUtils.hasFontExtension(fontFile)) { font = fontFile; break; } @@ -587,7 +583,7 @@ final class KMKeyboard extends WebView { } else { String fontFile = fontObj.optString(KMManager.KMKey_FontSource); if (fontFile != null) { - if (fontFile.endsWith(".ttf") || fontFile.endsWith(".otf")) { + if (FileUtils.hasFontExtension(fontFile)) { font = fontFile; } } @@ -807,8 +803,7 @@ final class KMKeyboard extends WebView { /** * Take a font JSON object and adjust to pass to JS * 1. Replace "source" keys for "files" keys - * 2. Create full font paths for .ttf or - * .svg for Android 4.2 or 4.3 OSK + * 2. Create full font paths for .ttf or .svg * @param font String font JSON object as a string * @return String of modified font information with full paths. If font is invalid, return "''" */ @@ -834,8 +829,7 @@ final class KMKeyboard extends WebView { if (sourceArray != null) { for (int i = 0; i < sourceArray.length(); i++) { fontFile = sourceArray.getString(i); - if (fontFile.contains(".ttf") || - (fontFile.contains(".svg") && (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1 || Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR2))) { + if (FileUtils.hasFontExtension(fontFile)) { fontObj.put(KMManager.KMKey_FontFiles, keyboardRoot + fontFile); fontObj.remove(KMManager.KMKey_FontSource); return fontObj.toString(); @@ -933,7 +927,9 @@ final class KMKeyboard extends WebView { dismissHelpBubble(); //keyPreviewWindow.setAnimationStyle(R.style.KeyPreviewAnim); - keyPreviewWindow.showAtLocation(KMKeyboard.this, Gravity.TOP | Gravity.LEFT, posX, posY); + if (keyPreviewWindow != null) { + keyPreviewWindow.showAtLocation(KMKeyboard.this, Gravity.TOP | Gravity.LEFT, posX, posY); + } } protected void dismissKeyPreview(long delay) { diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java index e8eb0ded850eeacbe22466d2b1fae05f5436a105..36a039ea831d3f83261aa95acd91f5dcde393d45 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java @@ -1,6 +1,6 @@ package com.tavultesoft.kmea; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; import android.app.DialogFragment; import android.app.ProgressDialog; import android.content.Context; @@ -28,7 +28,7 @@ import static com.tavultesoft.kmea.KMManager.KMDefault_AssetPackages; import static com.tavultesoft.kmea.KMManager.KMDefault_UndefinedPackageID; import static com.tavultesoft.kmea.KMManager.KMKey_FontSource; -public class KMKeyboardDownloaderActivity extends Activity { +public class KMKeyboardDownloaderActivity extends AppCompatActivity { // Bundle Keys // Cloud public static final String ARG_PKG_ID = "KMKeyboardActivity.pkgID"; @@ -44,11 +44,10 @@ public class KMKeyboardDownloaderActivity extends Activity { public static final String ARG_URL = "KMKeyboardActivity.url"; public static final String ARG_FILENAME = "KMKeyboardActivity.filename"; - public static final String kKeymanApiBaseURL = "https://api.keyman.com/cloud/3.0/"; + public static final String kKeymanApiBaseURL = "https://api.keyman.com/cloud/4.0/languages"; public static final String kKeymanApiRemoteURL = "https://r.keymanweb.com/api/2.0/remote?url="; // Keyman public keys - public static final String KMKey_Direct = "direct"; public static final String KMKey_URL = "url"; public static final String KMKey_Keyboard = "keyboard"; public static final String KMKey_LanguageKeyboards = "keyboards"; @@ -107,7 +106,7 @@ public class KMKeyboardDownloaderActivity extends Activity { Bundle args = new Bundle(); String title = ""; if (url != null) { - title = "Custom Keyboard: " + filename; + title = String.format("%s: %s", getString(R.string.custom_keyboard), filename); } else if (customKeyboard != null && customLanguage != null && !customKeyboard.trim().isEmpty() && !customLanguage.trim().isEmpty()) { int kbIndex = KMManager.getKeyboardIndex(getApplicationContext(), customKeyboard, customLanguage); @@ -118,10 +117,10 @@ public class KMKeyboardDownloaderActivity extends Activity { return; // false } - title = customLanguage + "_" + customKeyboard; + title = String.format("%s_%s", customLanguage, customKeyboard); } else { // Download keyboard from cloud server - title = langName + ": " + kbName; + title = String.format("%s: %s", langName, kbName); } DialogFragment dialog = new ConfirmDialogFragment(); @@ -154,9 +153,9 @@ public class KMKeyboardDownloaderActivity extends Activity { super.onPreExecute(); if (showProgressDialog) { progressDialog = new ProgressDialog(context); - progressDialog.setMessage("Downloading keyboard..."); + progressDialog.setMessage(context.getString(R.string.downloading_keyboard)); progressDialog.setCancelable(false); - if (!((Activity) context).isFinishing()) { + if (!((AppCompatActivity) context).isFinishing()) { progressDialog.show(); } else { cancel(true); @@ -179,7 +178,7 @@ public class KMKeyboardDownloaderActivity extends Activity { throw new Exception(exceptionStr); } - String deviceType = context.getResources().getString(R.string.device_type); + String deviceType = context.getString(R.string.device_type); if (deviceType.equals("AndroidTablet")) { deviceType = "androidtablet"; } else { @@ -191,7 +190,7 @@ public class KMKeyboardDownloaderActivity extends Activity { remoteUrl = url; } else { // Keyman cloud - remoteUrl = String.format("%slanguages/%s/%s?version=%s&device=%s", kKeymanApiBaseURL, langID, kbID, BuildConfig.VERSION_NAME, deviceType); + remoteUrl = String.format("%s/%s/%s?version=%s&device=%s&languageidtype=bcp47", kKeymanApiBaseURL, langID, kbID, BuildConfig.VERSION_NAME, deviceType); } ret = downloadNonKMPKeyboard(remoteUrl); @@ -219,15 +218,14 @@ public class KMKeyboardDownloaderActivity extends Activity { progressDialog = null; } - ((Activity) context).finish(); + ((AppCompatActivity) context).finish(); if (result > 0) { notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED, result); } } /** - * Download a non-KMP Keyman keyboard. - * This will either be from Keyman cloud or legacy ad-hoc distribution via JSON + * Download a non-KMP Keyman keyboard from Keyman cloud via JSON * @param remoteUrl String * @return ret int -1 for fail * @throws Exception @@ -241,6 +239,11 @@ public class KMKeyboardDownloaderActivity extends Activity { throw new Exception(exceptionStr); } + if (isCustom) { + exceptionStr = "Cannot download custom non-KMP keyboard"; + throw new Exception(exceptionStr); + } + exceptionStr = "JSON file does not contain a valid \"options\" object"; JSONObject options = kbData.optJSONObject(KMKey_Options); if (options == null) { @@ -252,50 +255,31 @@ public class KMKeyboardDownloaderActivity extends Activity { } String fontBaseUri = options.optString(KMKey_FontBaseURI, ""); - JSONObject language, keyboard; - if (!isCustom) { - // Keyman cloud keyboard distribution via JSON - language = kbData.optJSONObject(KMKey_Language); - if (language == null) { - throw new Exception(exceptionStr); - } - - langName = language.optString(KMManager.KMKey_Name, ""); + JSONObject language, keyboard = null; - JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards); - if (keyboards == null) { - throw new Exception(exceptionStr); - } + // Keyman cloud keyboard distribution via JSON + language = kbData.optJSONObject(KMKey_Language); + if (language == null) { + throw new Exception(exceptionStr); + } - keyboard = keyboards.getJSONObject(0); - if (keyboard == null) { - throw new Exception(exceptionStr); - } - } else { - // Legacy method of ad-hoc keyboard distribution via JSON - keyboard = kbData.optJSONObject(KMKey_Keyboard); - if (keyboard == null) { - throw new Exception(exceptionStr); - } + langName = language.optString(KMManager.KMKey_Name, ""); - JSONArray languages = keyboard.optJSONArray(KMKey_Languages); - if (languages == null) { - throw new Exception(exceptionStr); - } + JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards); + if (keyboards == null) { + throw new Exception(exceptionStr); + } - // Concatenate langID and langName - langID = ""; - langName = ""; - int langCount = languages.length(); - for (int i = 0; i < langCount; i++) { - langID += languages.getJSONObject(i).getString(KMManager.KMKey_ID); - langName += languages.getJSONObject(i).getString(KMManager.KMKey_Name); - if (i < langCount - 1) { - langID += ";"; - langName += ";"; - } + // In case keyboards array contains multiple keyboards, get the one with matching keyboard ID + for (int index = 0; index < keyboards.length(); index++) { + keyboard = keyboards.getJSONObject(index); + if (keyboard != null && (kbID.equals(keyboard.getString(KMManager.KMKey_ID)))) { + break; } } + if (keyboard == null) { + throw new Exception(exceptionStr); + } kbID = keyboard.getString(KMManager.KMKey_ID); pkgID = keyboard.optString(KMManager.KMKey_PackageID, KMManager.KMDefault_UndefinedPackageID); @@ -334,7 +318,7 @@ public class KMKeyboardDownloaderActivity extends Activity { int result = 0; for (String url : urls) { String filename = ""; - if (url.endsWith(".js")) { + if (FileUtils.hasJavaScriptExtension(url)) { int start = kbFilename.lastIndexOf("/"); if (start < 0) { @@ -413,12 +397,10 @@ public class KMKeyboardDownloaderActivity extends Activity { String fontSourceString; try { fontSourceString = fontSource.getString(i); - if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) { + if (FileUtils.hasFontExtension(fontSourceString)) { urls.add(baseUri + fontSourceString); - } else if (isOskFont && fontSourceString.contains(".svg#")) { - String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5); + } else if (isOskFont && FileUtils.hasSVGViewBox(fontSourceString)) { + String fontFilename = FileUtils.getSVGFilename(fontSourceString); urls.add(baseUri + fontFilename); } } catch (JSONException e) { @@ -429,12 +411,10 @@ public class KMKeyboardDownloaderActivity extends Activity { String fontSourceString; try { fontSourceString = jsonFont.getString(KMManager.KMKey_FontSource); - if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) { + if (FileUtils.hasFontExtension(fontSourceString)) { urls.add(baseUri + fontSourceString); - } else if (isOskFont && fontSourceString.contains(".svg#")) { - String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5); + } else if (isOskFont && FileUtils.hasSVGViewBox(fontSourceString)) { + String fontFilename = FileUtils.getSVGFilename(fontSourceString); urls.add(baseUri + fontFilename); } } catch (JSONException e) { diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardJSHandler.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardJSHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..9e3025f3e253ac359b84f703860e1f4edba7d3f4 --- /dev/null +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardJSHandler.java @@ -0,0 +1,69 @@ +package com.tavultesoft.kmea; + +import android.content.Context; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.util.DisplayMetrics; +import android.webkit.JavascriptInterface; + +import static android.content.Context.VIBRATOR_SERVICE; + +public abstract class KMKeyboardJSHandler { + private Context context; + private KMKeyboard k = null; + private static int KM_VIBRATE_DURATION = 100; // milliseconds + + KMKeyboardJSHandler(Context context, KMKeyboard k) { + this.context = context; + this.k = k; + } + + // This annotation is required in Jelly Bean and later: + @JavascriptInterface + public String getDeviceType() { + return context.getResources().getString(R.string.device_type); + } + + // This annotation is required in Jelly Bean and later: + @JavascriptInterface + public int getKeyboardHeight() { + int kbHeight = context.getResources().getDimensionPixelSize(R.dimen.keyboard_height); + kbHeight -= kbHeight % 20; + return kbHeight; + } + + // This annotation is required in Jelly Bean and later: + @JavascriptInterface + public int getKeyboardWidth() { + DisplayMetrics dms = context.getResources().getDisplayMetrics(); + int kbWidth = (int) (dms.widthPixels / dms.density); + return kbWidth; + } + + // This annotation is required in Jelly Bean and later: + @JavascriptInterface + public void beepKeyboard() { + Vibrator v = (Vibrator) context.getSystemService(VIBRATOR_SERVICE); + if (v != null && v.hasVibrator()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + VibrationEffect effect = VibrationEffect.createOneShot(KM_VIBRATE_DURATION, VibrationEffect.DEFAULT_AMPLITUDE); + v.vibrate(effect); + } else { + v.vibrate(KM_VIBRATE_DURATION); + } + } + } + + // Store the current keyboard chirality status from KMW in the Keyboard + @JavascriptInterface + public void setIsChiral(boolean isChiral) { + if (k != null) { + k.setChirality(isChiral); + } + } + + // Insert the selected string s + @JavascriptInterface + public abstract void insertText(final int dn, final String s); +} 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 b2293cf9d809a531290ce8436a140ee151f803e7..063240b0c6d7c712bebc243fb664c3bcd3023b86 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 @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.HashMap; import android.annotation.SuppressLint; +import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -24,19 +25,22 @@ import android.graphics.Typeface; import android.inputmethodservice.InputMethodService; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.text.InputType; -import android.util.DisplayMetrics; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; import android.webkit.JavascriptInterface; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -44,6 +48,7 @@ import android.widget.FrameLayout; import android.widget.RelativeLayout; import com.google.firebase.analytics.FirebaseAnalytics; +import com.tavultesoft.kmea.KMKeyboardJSHandler; import com.tavultesoft.kmea.KeyboardEventHandler.EventType; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; import com.tavultesoft.kmea.packages.PackageProcessor; @@ -53,7 +58,6 @@ import org.json.JSONObject; public final class KMManager { - private static final String KMEngineVersion = "2.4.3"; private static final String TAG = "KMManager"; private static FirebaseAnalytics mFirebaseAnalytics; @@ -105,11 +109,8 @@ public final class KMManager { public static final String KMKey_KeyboardID = "kbId"; public static final String KMKey_KeyboardName = "kbName"; public static final String KMKey_KeyboardVersion = "version"; - public static final String KMKey_KeyboardFileSize = "fileSize"; public static final String KMKey_Font = "font"; public static final String KMKey_OskFont = "oskFont"; - public static final String KMKey_DisplayFont = "displayFont"; - public static final String KMKey_FontFamily = "family"; public static final String KMKey_FontSource = "source"; public static final String KMKey_FontFiles = "files"; public static final String KMKey_KeyboardModified = "lastModified"; @@ -117,6 +118,7 @@ public final class KMManager { public static final String KMKey_CustomKeyboard = "CustomKeyboard"; public static final String KMKey_CustomHelpLink = "CustomHelpLink"; public static final String KMKey_UserKeyboardIndex = "UserKeyboardIndex"; + public static final String KMKey_DisplayKeyboardSwitcher = "DisplayKeyboardSwitcher"; // Keyman internal keys protected static final String KMKey_ShouldShowHelpBubble = "ShouldShowHelpBubble"; @@ -131,9 +133,9 @@ public final class KMManager { public static final String KMDefault_AssetPackages = "packages"; // Default Keyboard Info - public static final String KMDefault_KeyboardID = "european2"; + public static final String KMDefault_KeyboardID = "sil_euro_latin"; public static final String KMDefault_LanguageID = "en"; - public static final String KMDefault_KeyboardName = "EuroLatin2 Keyboard"; + public static final String KMDefault_KeyboardName = "EuroLatin (SIL) Keyboard"; public static final String KMDefault_LanguageName = "English"; public static final String KMDefault_KeyboardFont = "{\"family\":\"LatinWeb\",\"source\":[\"DejaVuSans.ttf\"]}"; @@ -147,10 +149,6 @@ public final class KMManager { private static Context appContext; - public static String getVersion() { - return KMEngineVersion; - } - protected static String getResourceRoot() { return appContext.getDir("data", Context.MODE_PRIVATE).toString() + File.separator; } @@ -188,7 +186,7 @@ public final class KMManager { } else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { initSystemKeyboard(appContext); } else { - Log.w(TAG, "Cannot initialize: Invalid keyboard type"); + Log.e(TAG, "Cannot initialize: Invalid keyboard type"); } // Initializes the PackageProcessor with the base resource directory, which is the parent directory @@ -225,8 +223,6 @@ public final class KMManager { private static void initInAppKeyboard(Context appContext) { if (InAppKeyboard == null) { - if (isDebugMode()) - Log.d(TAG, "Initializing In-App Keyboard..."); int kbHeight = appContext.getResources().getDimensionPixelSize(R.dimen.keyboard_height); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, kbHeight); params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); @@ -235,15 +231,13 @@ public final class KMManager { InAppKeyboard.setVerticalScrollBarEnabled(false); InAppKeyboard.setHorizontalScrollBarEnabled(false); InAppKeyboard.setWebViewClient(new KMInAppKeyboardWebViewClient(appContext)); - InAppKeyboard.addJavascriptInterface(new KMInAppKeyboardJSHandler(appContext), "jsInterface"); + InAppKeyboard.addJavascriptInterface(new KMInAppKeyboardJSHandler(appContext, InAppKeyboard), "jsInterface"); InAppKeyboard.loadKeyboard(); } } private static void initSystemKeyboard(Context appContext) { if (SystemKeyboard == null) { - if (isDebugMode()) - Log.d(TAG, "Initializing System Keyboard..."); int kbHeight = appContext.getResources().getDimensionPixelSize(R.dimen.keyboard_height); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, kbHeight); params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); @@ -252,7 +246,7 @@ public final class KMManager { SystemKeyboard.setVerticalScrollBarEnabled(false); SystemKeyboard.setHorizontalScrollBarEnabled(false); SystemKeyboard.setWebViewClient(new KMSystemKeyboardWebViewClient(appContext)); - SystemKeyboard.addJavascriptInterface(new KMSystemKeyboardJSHandler(appContext), "jsInterface"); + SystemKeyboard.addJavascriptInterface(new KMSystemKeyboardJSHandler(appContext, SystemKeyboard), "jsInterface"); SystemKeyboard.loadKeyboard(); } } @@ -278,25 +272,6 @@ public final class KMManager { parent.removeView(SystemKeyboard); keyboardLayout.addView(SystemKeyboard); - /* - final RelativeLayout overlayLayout = new RelativeLayout(appContext); - overlayLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); - LayoutInflater inflater = (LayoutInflater) appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - RelativeLayout overlayView = (RelativeLayout) inflater.inflate(R.layout.overlay_layout, null, false); - overlayView.setLayoutParams(SystemKeyboard.getLayoutParams()); - overlayView.setBackgroundColor(Color.argb(192, 0, 0, 0)); - overlayView.setClickable(true); - overlayLayout.addView(overlayView); - Button activateButton = (Button) overlayView.findViewById(R.id.button1); - activateButton.setOnClickListener(new OnClickListener(){ - @Override - public void onClick(View v) { - mainLayout.removeView(overlayLayout); - Toast.makeText(context, "Reactivated", Toast.LENGTH_LONG).show(); - } - }); - */ - mainLayout.addView(keyboardLayout); //mainLayout.addView(overlayLayout); return mainLayout; @@ -316,9 +291,11 @@ public final class KMManager { public static void onResume() { if (InAppKeyboard != null) { + InAppKeyboard.resumeTimers(); InAppKeyboard.onResume(); } if (SystemKeyboard != null) { + SystemKeyboard.resumeTimers(); SystemKeyboard.onResume(); } } @@ -466,7 +443,7 @@ public final class KMManager { FileFilter keyboardFilter = new FileFilter() { @Override public boolean accept(File pathname) { - if (pathname.isFile() && pathname.getName().endsWith(".js")) { + if (pathname.isFile() && FileUtils.hasJavaScriptExtension(pathname.getName())) { return true; } return false; @@ -531,7 +508,7 @@ public final class KMManager { boolean shouldClearCache = false; HashMap kbInfo = kbList.get(0); String kbID = kbInfo.get(KMKey_KeyboardID); - if (kbID.equals("us")) { + if (kbID.equals("us") || (kbID.equals("european2"))) { HashMap newKbInfo = new HashMap(); newKbInfo.put(KMManager.KMKey_PackageID, KMManager.KMDefault_UndefinedPackageID); newKbInfo.put(KMManager.KMKey_KeyboardID, KMManager.KMDefault_KeyboardID); @@ -637,7 +614,7 @@ public final class KMManager { Typeface font = null; if (fontFilename != null) { - if (fontFilename.endsWith(".ttf") || fontFilename.endsWith(".otf")) { + if (FileUtils.hasFontExtension(fontFilename)) { File file = new File(fontFilename); if (file.exists()) { font = Typeface.createFromFile(file); @@ -730,11 +707,37 @@ public final class KMManager { if (InAppKeyboard != null) { InAppKeyboard.setKeyboard(pkgId, kbId, langId, kbName, langName, kFont, kOskFont); } + if (SystemKeyboard != null) { SystemKeyboard.setKeyboard(pkgId, kbId, langId, kbName, langName, kFont, kOskFont); } } + protected static IBinder getToken() { + if (IMService == null) { + return null; + } + final Dialog dialog = IMService.getWindow(); + if (dialog == null) { + return null; + } + final Window window = dialog.getWindow(); + if (window == null) { + return null; + } + return window.getAttributes().token; + } + + public static void advanceToNextInputMode() { + InputMethodManager imm = (InputMethodManager) appContext.getSystemService(Context.INPUT_METHOD_SERVICE); + + // TODO: this method is added in API level 16 and deprecated in API level 28 + // Reference: https://developer.android.com/reference/android/view/inputmethod/InputMethodManager.html#switchToNextInputMethod(android.os.IBinder,%20boolean) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + imm.switchToNextInputMethod(getToken(), false); + } + } + // TODO: Refactor InAppKeyboard / SystemKeyboard logic public static KMKeyboard getKMKeyboard(KeyboardType keyboard) { if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) { @@ -743,7 +746,7 @@ public final class KMManager { return SystemKeyboard; } else { // What should we do if KeyboardType.KEYBOARD_TYPE_UNDEFINED? - Log.w("KMManager", "Invalid keyboard"); + Log.e("KMManager", "Invalid keyboard"); return null; } } @@ -825,7 +828,7 @@ public final class KMManager { */ public boolean accept(File pathname) { String name = pathname.getName(); - if (pathname.isFile() && name.startsWith(keyboardID) && name.endsWith(".js") && pathname.length() > 0) { + if (pathname.isFile() && name.startsWith(keyboardID) && FileUtils.hasJavaScriptExtension(name) && pathname.length() > 0) { return true; } return false; @@ -843,7 +846,7 @@ public final class KMManager { int index = filename.indexOf(base); if (index == 0) { int firstIndex = base.length(); - int lastIndex = filename.lastIndexOf(".js"); + int lastIndex = filename.toLowerCase().lastIndexOf(".js"); String v = filename.substring(firstIndex, lastIndex); if (kbFileVersion != null) { if (compareVersions(v, kbFileVersion) > 0) { @@ -1052,6 +1055,7 @@ public final class KMManager { if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) { Intent i = new Intent(context, KeyboardPickerActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + i.putExtra(KMKey_DisplayKeyboardSwitcher, false); context.startActivity(i); } else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { Intent i = new Intent(context, KeyboardPickerActivity.class); @@ -1059,6 +1063,7 @@ public final class KMManager { i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + i.putExtra(KMKey_DisplayKeyboardSwitcher, true); context.startActivity(i); } } @@ -1071,14 +1076,27 @@ public final class KMManager { KeyboardPickerActivity.showLanguageList(context); } + public static void setNumericLayer(KeyboardType kbType) { + if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) { + if (InAppKeyboard != null && InAppKeyboardLoaded && !InAppKeyboardShouldIgnoreTextChange) { + InAppKeyboard.loadUrl("javascript:setNumericLayer()"); + } + } else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + if (SystemKeyboard != null && SystemKeyboardLoaded && !SystemKeyboardShouldIgnoreTextChange) { + SystemKeyboard.loadUrl("javascript:setNumericLayer()"); + } + } + } + public static boolean updateText(KeyboardType kbType, String text) { boolean result = false; + String kmText = ""; + if (text != null) { + kmText = text.toString().replace("\\", "\\u005C").replace("'", "\\u0027").replace("\n", "\\n"); + } + if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) { if (InAppKeyboard != null && InAppKeyboardLoaded && !InAppKeyboardShouldIgnoreTextChange) { - String kmText = ""; - if (text != null) { - kmText = text.toString().replace("\\", "\\u005C").replace("'", "\\u0027").replace("\n", "\\n"); - } InAppKeyboard.loadUrl(String.format("javascript:updateKMText('%s')", kmText)); result = true; } @@ -1086,10 +1104,6 @@ public final class KMManager { InAppKeyboardShouldIgnoreTextChange = false; } else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { if (SystemKeyboard != null && SystemKeyboardLoaded && !SystemKeyboardShouldIgnoreTextChange) { - String kmText = ""; - if (text != null) { - kmText = text.toString().replace("\\", "\\u005C").replace("'", "\\u0027").replace("\n", "\\n"); - } SystemKeyboard.loadUrl(String.format("javascript:updateKMText('%s')", kmText)); result = true; } @@ -1257,9 +1271,6 @@ public final class KMManager { public void onPageFinished(WebView view, String url) { if (url.endsWith(KMFilename_KeyboardHtml)) { InAppKeyboardLoaded = true; - if (isDebugMode()) { - Log.d("KMManager", "In-App Keyboard loaded."); - } if (!InAppKeyboard.keyboardSet) { SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); @@ -1435,10 +1446,6 @@ public final class KMManager { public void onPageFinished(WebView view, String url) { if (url.endsWith(KMFilename_KeyboardHtml)) { SystemKeyboardLoaded = true; - if (isDebugMode()) { - Log.d("KMManager", "System Keyboard loaded."); - } - if (!SystemKeyboard.keyboardSet) { SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); int index = prefs.getInt(KMManager.KMKey_UserKeyboardIndex, 0); @@ -1515,7 +1522,7 @@ public final class KMManager { SystemKeyboard.showHelpBubble(context, fx, fy); } } else if (url.indexOf("showKeyPreview") >= 0) { - String deviceType = context.getResources().getString(R.string.device_type); + String deviceType = context.getString(R.string.device_type); if (deviceType.equals("AndroidTablet")) { return false; } @@ -1592,42 +1599,10 @@ public final class KMManager { } } - private static final class KMInAppKeyboardJSHandler { - private Context context; + private static final class KMInAppKeyboardJSHandler extends KMKeyboardJSHandler { - KMInAppKeyboardJSHandler(Context context) { - this.context = context; - } - - // This annotation is required in Jelly Bean and later: - @JavascriptInterface - public String getDeviceType() { - return context.getResources().getString(R.string.device_type); - } - - // This annotation is required in Jelly Bean and later: - @JavascriptInterface - public int getKeyboardHeight() { - int kbHeight = context.getResources().getDimensionPixelSize(R.dimen.keyboard_height); - kbHeight -= kbHeight % 20; - return kbHeight; - } - - // This annotation is required in Jelly Bean and later: - @JavascriptInterface - public int getKeyboardWidth() { - DisplayMetrics dms = context.getResources().getDisplayMetrics(); - int kbWidth = (int) (dms.widthPixels / dms.density); - return kbWidth; - } - - // Store the current keyboard chirality status from KMW in InAppKeyboard - @JavascriptInterface - public void setIsChiral(boolean isChiral) { - if (isDebugMode()) { - Log.d("KMManager", "InAppKeyboard chirality: " + String.valueOf(isChiral)); - } - InAppKeyboard.setChirality(isChiral); + KMInAppKeyboardJSHandler(Context context, KMKeyboard k) { + super(context, k); } // This annotation is required in Jelly Bean and later: @@ -1637,8 +1612,9 @@ public final class KMManager { mainLoop.post(new Runnable() { public void run() { if (InAppKeyboard.subKeysWindow != null || KMTextView.activeView == null || KMTextView.activeView.getClass() != KMTextView.class) { - if (KMTextView.activeView == null) + if ((KMTextView.activeView == null) && isDebugMode()) { Log.w("IAK: JS Handler", "insertText failed: activeView is null"); + } return; } @@ -1649,6 +1625,15 @@ public final class KMManager { int start = textView.getSelectionStart(); int end = textView.getSelectionEnd(); + // Workaround for Android TextView bug where end < start + // Reference: https://issuetracker.google.com/issues/36911048 + if (end < start) { + Log.d(TAG, "Swapping TextView selection end:" + end + " and start:" + start); + int temp = end; + end = start; + start = temp; + } + if (dn <= 0) { if (start == end) { if (!s.isEmpty() && s.charAt(0) == '\n') { @@ -1706,42 +1691,9 @@ public final class KMManager { } } - private static final class KMSystemKeyboardJSHandler { - private Context context; - - KMSystemKeyboardJSHandler(Context context) { - this.context = context; - } - - // This annotation is required in Jelly Bean and later: - @JavascriptInterface - public String getDeviceType() { - return context.getResources().getString(R.string.device_type); - } - - // This annotation is required in Jelly Bean and later: - @JavascriptInterface - public int getKeyboardHeight() { - int kbHeight = context.getResources().getDimensionPixelSize(R.dimen.keyboard_height); - kbHeight -= kbHeight % 20; - return kbHeight; - } - - // This annotation is required in Jelly Bean and later: - @JavascriptInterface - public int getKeyboardWidth() { - DisplayMetrics dms = context.getResources().getDisplayMetrics(); - int kbWidth = (int) (dms.widthPixels / dms.density); - return kbWidth; - } - - // Store the current keyboard chirality status from KMW in SystemKeyboard - @JavascriptInterface - public void setIsChiral(boolean isChiral) { - if (isDebugMode()) { - Log.d("KMManager", "SystemKeyboard chirality: " + String.valueOf(isChiral)); - } - SystemKeyboard.setChirality(isChiral); + private static final class KMSystemKeyboardJSHandler extends KMKeyboardJSHandler { + KMSystemKeyboardJSHandler(Context context, KMKeyboard k) { + super(context, k); } // This annotation is required in Jelly Bean and later: @@ -1756,7 +1708,9 @@ public final class KMManager { InputConnection ic = IMService.getCurrentInputConnection(); if (ic == null) { - Log.w("SWK: JS Handler", "insertText failed: InputConnection is null"); + if (isDebugMode()) { + Log.w("SWK: JS Handler", "insertText failed: InputConnection is null"); + } return; } diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMTextView.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMTextView.java index e4ab1c5149b4c42a340d8392faef547c4c75fb85..6aa8ebb9e74e42a8346f1656042211c98d06819b 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMTextView.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMTextView.java @@ -11,6 +11,9 @@ import com.tavultesoft.kmea.KeyboardEventHandler.EventType; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; import android.app.Activity; +import android.content.ContextWrapper; +import android.view.ContextThemeWrapper; +import android.support.v7.app.AppCompatActivity; import android.content.Context; import android.graphics.Color; import android.graphics.Typeface; @@ -23,11 +26,11 @@ import android.view.ViewGroup.OnHierarchyChangeListener; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; +import android.support.v7.widget.AppCompatEditText; import android.widget.FrameLayout; import android.widget.RelativeLayout; -public final class KMTextView extends EditText { +public final class KMTextView extends AppCompatEditText { private Context context; protected KMHardwareKeyboardInterpreter hardwareKeyboardInterpreter; @@ -58,7 +61,8 @@ public final class KMTextView extends EditText { this.context = context; this.hardwareKeyboardInterpreter = new KMHardwareKeyboardInterpreter(context, KeyboardType.KEYBOARD_TYPE_INAPP); - Activity activity = (Activity) context; + AppCompatActivity activity = (AppCompatActivity)context; + Window mainWindow = activity.getWindow(); mainWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); FrameLayout mainLayout = (FrameLayout) mainWindow.getDecorView().findViewById(android.R.id.content); @@ -102,21 +106,17 @@ public final class KMTextView extends EditText { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { - if (KMManager.isDebugMode()) - Log.d("OnFocusChange", "gotFocus: " + v.toString()); activeView = v; if (KMManager.InAppKeyboardLoaded) { KMTextView textView = (KMTextView) activeView; int selStart = textView.getSelectionStart(); int selEnd = textView.getSelectionEnd(); - KMManager.InAppKeyboard.loadUrl(String.format("javascript:updateKMText('%s')", textView.getText().toString())); - KMManager.InAppKeyboard.loadUrl(String.format("javascript:updateKMSelectionRange(%d,%d)", selStart, selEnd)); + KMManager.updateText(KeyboardType.KEYBOARD_TYPE_INAPP, textView.getText().toString()); + KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd); KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP); } showKeyboard(); } else { - if (KMManager.isDebugMode()) - Log.d("OnFocusChange", "lostFocus: " + v.toString()); activeView = null; dismissKeyboard(); } @@ -163,11 +163,10 @@ public final class KMTextView extends EditText { @Override public void onWindowFocusChanged(boolean hasWindowFocus) { - Activity activity = (Activity) context; + AppCompatActivity activity = (AppCompatActivity)context; + Window mainWindow = activity.getWindow(); if (hasWindowFocus) { - if (KMManager.isDebugMode()) - Log.d("onWindowFocusChanged", "gotFocus:" + mainWindow.toString()); KMManager.KMInAppKeyboardWebViewClient.context = context; activeView = mainWindow.getCurrentFocus(); @@ -179,25 +178,18 @@ public final class KMTextView extends EditText { } if (activeView != null && activeView.equals(this)) { - if (KMManager.isDebugMode()) { - Log.d("onWindowFocusChanged", "activeView = " + activeView.toString()); - } if (KMManager.InAppKeyboardLoaded) { KMTextView textView = (KMTextView) activeView; int selStart = textView.getSelectionStart(); int selEnd = textView.getSelectionEnd(); - KMManager.InAppKeyboard.loadUrl(String.format("javascript:updateKMText('%s')", textView.getText().toString())); - KMManager.InAppKeyboard.loadUrl(String.format("javascript:updateKMSelectionRange(%d,%d)", selStart, selEnd)); + KMManager.updateText(KeyboardType.KEYBOARD_TYPE_INAPP, textView.getText().toString()); + KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd); } if (keyboardVisible) { showKeyboard(); } } - } else { - if (KMManager.isDebugMode()) { - Log.d("onWindowFocusChanged", "lostFocus:" + mainWindow.toString()); - } } } @@ -267,12 +259,13 @@ public final class KMTextView extends EditText { } private void showKeyboard() { - Activity activity = (Activity) context; + AppCompatActivity activity = (AppCompatActivity)context; + Window mainWindow = activity.getWindow(); FrameLayout mainLayout = (FrameLayout) mainWindow.getDecorView().findViewById(android.R.id.content); mainWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - ((InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(mainLayout.getWindowToken(), 0); + ((InputMethodManager) activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(mainLayout.getWindowToken(), 0); ViewGroup parent = (ViewGroup) keyboardLayout.getParent(); if (parent != null) { @@ -291,25 +284,27 @@ public final class KMTextView extends EditText { mainLayout.requestLayout(); mainLayout.invalidate(); - if (KMManager.isDebugMode()) - Log.d("showKeyboard()", "Keyboard shown"); KeyboardEventHandler.notifyListeners(kbEventListeners, KeyboardType.KEYBOARD_TYPE_INAPP, EventType.KEYBOARD_SHOWN, null); } public void dismissKeyboard() { - Activity activity = (Activity) context; + AppCompatActivity activity; + if (context instanceof ContextThemeWrapper) { + activity = (AppCompatActivity)(((ContextThemeWrapper)context).getBaseContext()); + } else { + activity = (AppCompatActivity)context; + } + Window mainWindow = activity.getWindow(); FrameLayout mainLayout = (FrameLayout) mainWindow.getDecorView().findViewById(android.R.id.content); //mainWindow.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - ((InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(mainLayout.getWindowToken(), 0); + ((InputMethodManager) activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(mainLayout.getWindowToken(), 0); //keyboardLayout.setAnimation(AnimationUtils.loadAnimation(context, R.anim.slide_out)); //keyboardLayout.setAnimation(null); keyboardLayout.setVisibility(View.GONE); keyboardLayout.setEnabled(false); keyboardVisible = false; - if (KMManager.isDebugMode()) - Log.d("dismissKeyboard()", "Keyboard dismissed"); KeyboardEventHandler.notifyListeners(kbEventListeners, KeyboardType.KEYBOARD_TYPE_INAPP, EventType.KEYBOARD_DISMISSED, null); } diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardInfoActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardInfoActivity.java index 1e1d198ac199df02ed4face9bccaa41581d75a8c..9fe38b96bbe8fcdfbe8688a2a8c87fdd3abf0e12 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardInfoActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardInfoActivity.java @@ -9,7 +9,8 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -29,9 +30,12 @@ import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; +import com.tavultesoft.kmea.util.FileUtils; + // Public access is necessary to avoid IllegalAccessException -public final class KeyboardInfoActivity extends Activity { +public final class KeyboardInfoActivity extends AppCompatActivity { + private static Toolbar toolbar = null; private static ListView listView = null; private static ArrayList> infoList = null; protected static Typeface titleFont = null; @@ -42,28 +46,19 @@ public final class KeyboardInfoActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); final Context context = this; - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - try { - int titleContainerId = (Integer) Class.forName("com.android.internal.R$id").getField("title_container").get(null); - ((ViewGroup) getWindow().findViewById(titleContainerId)).removeAllViews(); - } catch (Exception e) { - Log.e("KeyboardInfoActivity", "Error: " + e); - } - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.list_title_layout1); - setContentView(R.layout.list_layout); - listView = (ListView) findViewById(R.id.listView); + setContentView(R.layout.activity_list_layout); + toolbar = (Toolbar) findViewById(R.id.list_toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); - final ImageButton backButton = (ImageButton) findViewById(R.id.left_button); - backButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - finish(); - } - }); + listView = (ListView) findViewById(R.id.listView); final String kbID = getIntent().getStringExtra(KMManager.KMKey_KeyboardID); - //String langID = getIntent().getStringExtra(KMManager.KMKey_LanguageID); final TextView textView = (TextView) findViewById(R.id.bar_title); String kbName = getIntent().getStringExtra(KMManager.KMKey_KeyboardName); @@ -77,16 +72,16 @@ public final class KeyboardInfoActivity extends Activity { infoList = new ArrayList>(); String icon = "0"; HashMap hashMap = new HashMap(); - hashMap.put(titleKey, "Keyboard version"); + hashMap.put(titleKey, getString(R.string.keyboard_version)); hashMap.put(subtitleKey, kbVersion); hashMap.put(iconKey, icon); infoList.add(hashMap); final String customHelpLink = getIntent().getStringExtra(KMManager.KMKey_CustomHelpLink); if (!isCustomKeyboard || customHelpLink != null) { - icon = String.valueOf(R.drawable.ic_action_next); + icon = String.valueOf(R.drawable.ic_arrow_forward); hashMap = new HashMap(); - hashMap.put(titleKey, "Help link"); + hashMap.put(titleKey, getString(R.string.help_link)); hashMap.put(subtitleKey, ""); hashMap.put(iconKey, icon); infoList.add(hashMap); @@ -104,7 +99,7 @@ public final class KeyboardInfoActivity extends Activity { Intent i = new Intent(Intent.ACTION_VIEW); if (customHelpLink != null) { - if (customHelpLink.endsWith("welcome.htm")) { + if (FileUtils.isWelcomeFile(customHelpLink)) { File customHelp = new File(new File(customHelpLink).getAbsolutePath()); i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Starting with Android N, you can't pass file:// to intents, so we use FileProvider @@ -127,6 +122,17 @@ public final class KeyboardInfoActivity extends Activity { } } } + + }); + + } -} \ No newline at end of file + + @Override + public boolean onSupportNavigateUp() { + super.onBackPressed(); + return true; + } + +} diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java index f8d33973c0081dc2dc38e8a9bf924db6310f10fa..926d759959e11f98a3751800b006e7d7f44969a1 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java @@ -15,7 +15,8 @@ import org.json.JSONObject; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; import android.app.DialogFragment; import android.content.Context; import android.content.Intent; @@ -32,8 +33,9 @@ import android.widget.TextView; import android.widget.Toast; // Public access is necessary to avoid IllegalAccessException -public final class KeyboardListActivity extends Activity implements OnKeyboardDownloadEventListener { +public final class KeyboardListActivity extends AppCompatActivity implements OnKeyboardDownloadEventListener { + private static Toolbar toolbar = null; private static ListView listView = null; private static JSONArray languages = LanguageListActivity.languages(); private static JSONArray keyboards = null; @@ -44,26 +46,17 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); final Context context = this; - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - try { - int titleContainerId = (Integer) Class.forName("com.android.internal.R$id").getField("title_container").get(null); - ((ViewGroup) getWindow().findViewById(titleContainerId)).removeAllViews(); - } catch (Exception e) { - Log.e("KeyboardListActivity", "Error: " + e); - } - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.list_title_layout1); - setContentView(R.layout.list_layout); - listView = (ListView) findViewById(R.id.listView); + setContentView(R.layout.activity_list_layout); + toolbar = (Toolbar) findViewById(R.id.list_toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); - final ImageButton backButton = (ImageButton) findViewById(R.id.left_button); - backButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - showLanguageList(); - finish(); - } - }); + listView = (ListView) findViewById(R.id.listView); final TextView textView = (TextView) findViewById(R.id.bar_title); @@ -86,7 +79,7 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo String kbKey = String.format("%s_%s", langID, kbID); if (KeyboardPickerActivity.containsKeyboard(context, kbKey)) { isEnabled = "false"; - icon = String.valueOf(R.drawable.ic_action_check); + icon = String.valueOf(R.drawable.ic_check); } HashMap hashMap = new HashMap(); @@ -143,18 +136,14 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo } @Override - public void onBackPressed() { - showLanguageList(); - super.onBackPressed(); + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; } - private void showLanguageList() { - Intent i = new Intent(this, LanguageListActivity.class); - i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - i.putExtra("listPosition", getIntent().getIntExtra("listPosition", 0)); - i.putExtra("offsetY", getIntent().getIntExtra("offsetY", 0)); - startActivity(i); + @Override + public void onBackPressed() { + finish(); } @Override 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 c5c46d926e84279a3fd2fc54d5c5a5d8bf486c72..6f79be7b127f76d1960cb62f90fad21a7439d2c2 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 @@ -24,8 +24,8 @@ import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener import com.tavultesoft.kmea.util.FileUtils; import com.tavultesoft.kmea.BuildConfig; -import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; @@ -36,22 +36,26 @@ import android.graphics.Typeface; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.widget.AdapterView; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.BaseAdapter; +import android.widget.Button; import android.widget.ImageButton; import android.widget.ListView; import android.widget.PopupMenu; import android.widget.Toast; -public final class KeyboardPickerActivity extends Activity implements OnKeyboardDownloadEventListener { +public final class KeyboardPickerActivity extends AppCompatActivity implements OnKeyboardDownloadEventListener { + private static Toolbar toolbar = null; private static ListView listView = null; + private static ImageButton addButton = null; + private static Button closeButton = null; private static KMKeyboardPickerAdapter listAdapter = null; private static ArrayList> keyboardsList = null; private static HashMap keyboardVersions = null; @@ -78,23 +82,35 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); final Context context = this; - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - try { - int titleContainerId = (Integer) Class.forName("com.android.internal.R$id").getField("title_container").get(null); - ((ViewGroup) getWindow().findViewById(titleContainerId)).removeAllViews(); - } catch (Exception e) { - Log.e("KeyboardPickerActivity", "Error: " + e); + setContentView(R.layout.keyboard_picker_list_layout); + + toolbar = (Toolbar) findViewById(R.id.keyboard_picker_toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); + + closeButton = (Button) findViewById(R.id.close_keyman_button); + Bundle bundle = getIntent().getExtras(); + if (bundle != null) { + if (!bundle.getBoolean(KMManager.KMKey_DisplayKeyboardSwitcher)) { + closeButton.setVisibility(View.GONE); + } } + closeButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + KMManager.advanceToNextInputMode(); + if (dismissOnSelect) { + finish(); + } + } + }); - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.list_title_layout2); - setContentView(R.layout.list_layout); listView = (ListView) findViewById(R.id.listView); - - File file = new File(context.getDir("userdata", Context.MODE_PRIVATE), KMManager.KMFilename_KeyboardsList); - if (file.exists()) { - keyboardsList = getKeyboardsList(context); - } else { + keyboardsList = getKeyboardsList(context); + if (keyboardsList == null) { keyboardsList = new ArrayList>(); HashMap kbInfo = new HashMap(); kbInfo.put(KMManager.KMKey_PackageID, KMManager.KMDefault_UndefinedPackageID); @@ -107,7 +123,12 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard kbInfo.put(KMManager.KMKey_CustomKeyboard, "N"); kbInfo.put(KMManager.KMKey_Font, KMManager.KMDefault_KeyboardFont); keyboardsList.add(kbInfo); - saveKeyboardsList(context); + + // We'd prefer not to overwrite a file if it exists + File file = new File(context.getDir("userdata", Context.MODE_PRIVATE), KMManager.KMFilename_KeyboardsList); + if (!file.exists()) { + saveKeyboardsList(context); + } } String[] from = new String[]{KMManager.KMKey_LanguageName, KMManager.KMKey_KeyboardName}; @@ -149,14 +170,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard } }); - final ImageButton leftButton = (ImageButton) findViewById(R.id.left_button); - leftButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - finish(); - } - }); - - final ImageButton addButton = (ImageButton) findViewById(R.id.right_button); + addButton = (ImageButton) findViewById(R.id.add_button); addButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (KMManager.hasConnection(context) || LanguageListActivity.getCacheFile(context).exists()) { @@ -166,17 +180,17 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard context.startActivity(i); } else { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - dialogBuilder.setTitle(context.getResources().getString(R.string.title_add_keyboard)); - dialogBuilder.setIcon(context.getResources().getDrawable(android.R.drawable.ic_input_add)); - dialogBuilder.setMessage("\nCannot connect to Keyman server!\n"); - dialogBuilder.setPositiveButton("OK", null); + dialogBuilder.setTitle(getString(R.string.title_add_keyboard)); + dialogBuilder.setMessage(String.format("\n%s\n", getString(R.string.cannot_connect))); + dialogBuilder.setPositiveButton(getString(R.string.label_ok), null); AlertDialog dialog = dialogBuilder.create(); dialog.show(); } } }); - if (!canAddNewKeyboard) + if (!canAddNewKeyboard) { addButton.setVisibility(View.GONE); + } int curKbPos = getCurrentKeyboardIndex(); setSelection(curKbPos); @@ -200,7 +214,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard public void run() { boolean shouldCheckUpdate = false; if (lastUpdateCheck == null) { - SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + SharedPreferences prefs = context.getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); Long lastUpdateCheckTime = prefs.getLong("lastUpdateCheck", 0); if (lastUpdateCheckTime > 0) { lastUpdateCheck = Calendar.getInstance(); @@ -254,6 +268,17 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard } } + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + @Override + public void onBackPressed() { + finish(); + } + private static int getCurrentKeyboardIndex() { int pos = 0; @@ -516,10 +541,9 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard context.startActivity(i); } else { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - dialogBuilder.setTitle(context.getResources().getString(R.string.title_add_keyboard)); - dialogBuilder.setIcon(context.getResources().getDrawable(android.R.drawable.ic_input_add)); - dialogBuilder.setMessage("\nCannot connect to Keyman server!\n"); - dialogBuilder.setPositiveButton("OK", null); + dialogBuilder.setTitle(context.getString(R.string.title_add_keyboard)); + dialogBuilder.setMessage(String.format("\n%s\n", context.getString(R.string.cannot_connect))); + dialogBuilder.setPositiveButton(context.getString(R.string.label_ok), null); AlertDialog dialog = dialogBuilder.create(); dialog.show(); } @@ -537,9 +561,9 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard checkingUpdates = true; if (hasConnection) { progressDialog = new ProgressDialog(context); - progressDialog.setMessage("Checking keyboard updates..."); + progressDialog.setMessage(context.getString(R.string.checking_keyboard_updates)); progressDialog.setCancelable(false); - if (!((Activity) context).isFinishing()) { + if (!((AppCompatActivity) context).isFinishing()) { progressDialog.show(); } else { cancel(true); @@ -553,7 +577,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard int ret = 0; if (hasConnection && !isCancelled()) { try { - String deviceType = context.getResources().getString(R.string.device_type); + String deviceType = context.getString(R.string.device_type); if (deviceType.equals("AndroidTablet")) { deviceType = "androidtablet"; } else { @@ -567,7 +591,8 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard String languageID = keyboardsList.get(i).get(KMManager.KMKey_LanguageID); String keyboardID = keyboardsList.get(i).get(KMManager.KMKey_KeyboardID); String kbVersion = keyboardsList.get(i).get(KMManager.KMKey_KeyboardVersion); - String url = String.format("%slanguages/%s/%s?version=%s&device=%s", KMKeyboardDownloaderActivity.kKeymanApiBaseURL, languageID, keyboardID, BuildConfig.VERSION_NAME, deviceType); + String url = String.format("%s/%s/%s?version=%s&device=%s&languageidtype=bcp47", + KMKeyboardDownloaderActivity.kKeymanApiBaseURL, languageID, keyboardID, BuildConfig.VERSION_NAME, deviceType); JSONObject kbData = jsonParser.getJSONObjectFromUrl(url); JSONObject language = kbData.optJSONObject(KMKeyboardDownloaderActivity.KMKey_Language); JSONArray keyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards); @@ -611,9 +636,9 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard failedUpdateCount = 0; updateCount = result; AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - dialogBuilder.setTitle("Keyboard Updates Available"); - dialogBuilder.setMessage("Would you like to update keyboards now?"); - dialogBuilder.setPositiveButton("Update", new DialogInterface.OnClickListener() { + dialogBuilder.setTitle(context.getString(R.string.keyboard_updates_available)); + dialogBuilder.setMessage(context.getString(R.string.confirm_update)); + dialogBuilder.setPositiveButton(context.getString(R.string.label_update), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Update keyboards if (KMManager.hasConnection(context)) { @@ -632,7 +657,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard if (Float.valueOf(newKbVersion) > Float.valueOf(kbVersion)) { if (updateProgress == null || !updateProgress.isShowing()) { updateProgress = new ProgressDialog(context); - updateProgress.setMessage("Updating keyboards..."); + updateProgress.setMessage(context.getString(R.string.updating_keyboards)); updateProgress.setCancelable(false); updateProgress.show(); } @@ -657,7 +682,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard } }); - dialogBuilder.setNegativeButton("Later", new DialogInterface.OnClickListener() { + dialogBuilder.setNegativeButton(context.getString(R.string.label_later), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { lastUpdateCheck = Calendar.getInstance(); checkingUpdates = false; @@ -665,7 +690,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard }); AlertDialog dialog = dialogBuilder.create(); - if (!((Activity) context).isFinishing()) { + if (!((AppCompatActivity) context).isFinishing()) { dialog.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { @@ -695,15 +720,6 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard }.execute(); } - /* - @Override - public void onBackPressed() { - Intent setIntent = new Intent(Intent.ACTION_MAIN); - setIntent.addCategory(Intent.CATEGORY_HOME); - setIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(setIntent); - }*/ - @Override public void onKeyboardDownloadStarted(HashMap keyboardInfo) { // Do nothing diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java index 4a41972839cd44ab418a7bf3c13e17484338a8aa..f70d1b6c24d4b4b170859c152b42d94d8fc5eeff 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java @@ -26,12 +26,13 @@ import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener import com.tavultesoft.kmea.BuildConfig; import android.annotation.SuppressLint; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -42,9 +43,10 @@ import android.widget.ListAdapter; import android.widget.ListView; import android.widget.Toast; -public final class LanguageListActivity extends Activity implements OnKeyboardDownloadEventListener { +public final class LanguageListActivity extends AppCompatActivity implements OnKeyboardDownloadEventListener { private Context context; + private static Toolbar toolbar = null; private static ListView listView = null; private static ArrayList> languagesArrayList = null; private boolean didExecuteParser = false; @@ -71,27 +73,19 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); context = this; - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - try { - int titleContainerId = (Integer) Class.forName("com.android.internal.R$id").getField("title_container").get(null); - ((ViewGroup) getWindow().findViewById(titleContainerId)).removeAllViews(); - } catch (Exception e) { - Log.e("LanguageListActivity", "Error: " + e); - } + setContentView(R.layout.activity_list_layout); + + toolbar = (Toolbar) findViewById(R.id.list_toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.list_title_layout1); - setContentView(R.layout.list_layout); listView = (ListView) findViewById(R.id.listView); listView.setFastScrollEnabled(true); - final ImageButton backButton = (ImageButton) findViewById(R.id.left_button); - backButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - finish(); - } - }); - languagesArrayList = new ArrayList>(); } @@ -113,6 +107,17 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo // ensure onKeyboardDownloadFinished() gets called } + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + @Override + public void onBackPressed() { + finish(); + } + @Override public void onKeyboardDownloadStarted(HashMap keyboardInfo) { // Do nothing @@ -334,9 +339,9 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo if (hasConnection && !loadFromCache) { progressDialog = new ProgressDialog(context); - progressDialog.setMessage("Loading..."); + progressDialog.setMessage(getString(R.string.loading)); progressDialog.setCancelable(false); - if (!((Activity) context).isFinishing()) { + if (!((AppCompatActivity) context).isFinishing()) { progressDialog.show(); } else { cancel(true); @@ -357,14 +362,15 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo jsonObj = getCachedJSONObject(context); } else if (hasConnection) { try { - String deviceType = context.getResources().getString(R.string.device_type); + String deviceType = getString(R.string.device_type); if (deviceType.equals("AndroidTablet")) { deviceType = "androidtablet"; } else { deviceType = "androidphone"; } - - jsonObj = jsonParser.getJSONObjectFromUrl(KMKeyboardDownloaderActivity.kKeymanApiBaseURL + "languages?version=" + BuildConfig.VERSION_NAME + "&device=" + deviceType); + String remoteUrl = String.format("%s?version=%s&device=%s&languageidtype=bcp47", + KMKeyboardDownloaderActivity.kKeymanApiBaseURL, BuildConfig.VERSION_NAME, deviceType); + jsonObj = jsonParser.getJSONObjectFromUrl(remoteUrl); } catch (Exception e) { jsonObj = null; } @@ -428,7 +434,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo kbKey = String.format("%s_%s", langID, kbID); if (KeyboardPickerActivity.containsKeyboard(context, kbKey)) { isEnabled = "false"; - icon = String.valueOf(R.drawable.ic_action_check); + icon = String.valueOf(R.drawable.ic_check); } HashMap hashMap = new HashMap(); @@ -442,7 +448,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo if (keyboardModifiedDates.get(kbID) == null) keyboardModifiedDates.put(kbID, keyboard.getString(KMManager.KMKey_KeyboardModified)); } else { - icon = String.valueOf(R.drawable.ic_action_next); + icon = String.valueOf(R.drawable.ic_arrow_forward); for (int j = 0; j < kbLength; j++) { keyboard = langKeyboards.getJSONObject(j); kbID = keyboard.getString(KMManager.KMKey_ID); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/packages/PackageProcessor.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/packages/PackageProcessor.java index 040d355786b214bf74f6e04c502e76eb54a02439..a0983fdd106842343ebb3a6fb2f4757a6262b43a 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/packages/PackageProcessor.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/packages/PackageProcessor.java @@ -33,6 +33,7 @@ import org.json.JSONObject; */ public class PackageProcessor { private static File resourceRoot = null; + public static final String PPDefault_Version = "1.0"; public static void initialize(File resourceRoot) { PackageProcessor.resourceRoot = resourceRoot; @@ -48,12 +49,12 @@ public class PackageProcessor { if(filename.lastIndexOf('.') != -1) { // Android's temp downloads attach a suffix to the extension; .kmp isn't the end of the filename. - if(filename.lastIndexOf(".kmp") == -1) { + if(!FileUtils.hasKeyboardPackageExtension(filename)) { throw new IllegalArgumentException("Invalid file passed to the KMP unpacker!"); } // Extract our best-guess name for the package and construct the temporary package name. - return filename.substring(0, filename.lastIndexOf(".kmp")); + return filename.substring(0, filename.toLowerCase().lastIndexOf(".kmp")); } else { throw new IllegalArgumentException("Invalid file passed to the KMP unpacker!"); } @@ -100,7 +101,7 @@ public class PackageProcessor { JSONParser parser = new JSONParser(); return parser.getJSONObjectFromFile(infoFile); } else { - Log.d("PackageProcessor", infoFile.toString() + " does not exist."); + Log.e("PackageProcessor", infoFile.toString() + " does not exist."); return null; } } @@ -192,21 +193,22 @@ public class PackageProcessor { return name; } } catch (Exception e) { - return null; + // Developer will never allow package name to be undefined, but just in case, reuse package ID + return getPackageID(kmpPath); } } /** * Simply extracts the package's version number. + * If undefined, return default version "1.0" * @param json The metadata JSONObject for the package. * @return The version number (via String) - * @throws JSONException */ - public static String getPackageVersion(JSONObject json) throws JSONException { - if(json == null) { - return null; - } else { + public static String getPackageVersion(JSONObject json) { + try { return json.getJSONObject("info").getJSONObject("version").getString("description"); + } catch (JSONException e) { + return PPDefault_Version; } } @@ -221,7 +223,7 @@ public class PackageProcessor { return version; } } catch (Exception e) { - return null; + return PPDefault_Version; } } @@ -322,7 +324,7 @@ public class PackageProcessor { FileFilter touchKeyboardFilter = new FileFilter() { @Override public boolean accept(File pathname) { - if (pathname.isFile() && pathname.getName().startsWith(keyboardId) && pathname.getName().endsWith(".js")) { + if (pathname.isFile() && pathname.getName().startsWith(keyboardId) && FileUtils.hasJavaScriptExtension(pathname.getName())) { return true; } return false; @@ -345,7 +347,7 @@ public class PackageProcessor { FileFilter welcomeFilter = new FileFilter() { @Override public boolean accept(File pathname) { - if (pathname.isFile() && pathname.getName().equals("welcome.htm")) { + if (pathname.isFile() && FileUtils.isWelcomeFile(pathname.getName())) { return true; } return false; diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/Connection.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/Connection.java index 6d6a8ef9da571a377f66f63917d0ae2120519c41..645633121653a747fe456ccac2a6d64423f0e18a 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/Connection.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/Connection.java @@ -88,14 +88,11 @@ public final class Connection { urlValid = true; ret = true; } else { - Log.d("util.Connection", "HttpURLConnection response code: " + status); - // Handle HTTP Status Codes 3xx if (HttpURLConnection.HTTP_MULT_CHOICE <= status && status <= HttpURLConnection.HTTP_USE_PROXY && status != HttpURLConnection.HTTP_NOT_MODIFIED) { urlStr = urlConnection.getHeaderField("Location"); - Log.d("util.Connection", "Redirecting from " + url + " to " + urlStr); } else { // Abort for all other Status Codes break; diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileUtils.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileUtils.java index 9cd855ec38aa834c4937a8fcc117aabae7090df3..f8446045dbd537c67278924a6799e83700cf276b 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileUtils.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileUtils.java @@ -1,6 +1,7 @@ package com.tavultesoft.kmea.util; import android.content.Context; +import android.os.Build; import android.util.Log; import com.tavultesoft.kmea.KMManager; @@ -24,6 +25,18 @@ public final class FileUtils { public static final int DOWNLOAD_ERROR = -1; public static final int DOWNLOAD_SUCCESS = 1; + // File extensions and file types + public static final String JAVASCRIPT = ".js"; + public static final String TRUETYPEFONT = ".ttf"; + public static final String OPENTYPEFONT = ".otf"; + + public static final String SVGFONT = ".svg"; + public static final String SVGVIEWBOX = ".svg#"; + public static final String WOFFFONT = ".woff"; + + public static final String KEYBOARDPACKAGE = ".kmp"; + public static final String WELCOME_HTM = "welcome.htm"; + /** * Utility to download a file from urlStr and store it at destinationDir/destinationFilename. * If the destination directory does not exist, it will be created. @@ -63,7 +76,7 @@ public final class FileUtils { if (destinationFilename == null || destinationFilename.isEmpty()) { filename = getFilename(Connection.getFile()); - if (filename.lastIndexOf(".js") > 0 && !filename.contains("-")) { + if (hasJavaScriptExtension(filename) && !filename.contains("-")) { filename = filename.substring(0, filename.lastIndexOf(".js")) + "-1.0.js"; } } else { @@ -96,8 +109,6 @@ public final class FileUtils { } if (!tmpFile.renameTo(file)) { ret = DOWNLOAD_ERROR; - } else if (KMManager.isDebugMode()) { - Log.d("FileUtils", "Download finished for filename " + file.toString()); } } else { ret = DOWNLOAD_ERROR; @@ -109,9 +120,7 @@ public final class FileUtils { if (tmpFile.exists()) { tmpFile.delete(); } - if (KMManager.isDebugMode()) { - Log.d("FileUtils", "Could not download filename " + file.toString()); - } + Log.e("FileUtils", "Could not download filename " + file.toString()); } Connection.disconnect(); @@ -206,4 +215,50 @@ public final class FileUtils { } return filename; } + + /** + * Utility if a given file is a font. We prefer TTF or OTF, but old Android devices use SVG or WOFF. + * @param filename + * @return boolean + */ + public static boolean hasFontExtension(String filename) { + String f = filename.toLowerCase(); + return f.endsWith(TRUETYPEFONT) || f.endsWith(OPENTYPEFONT) || + f.endsWith(WOFFFONT) || f.endsWith(SVGFONT); + } + + public static boolean hasSVGViewBox(String filename) { + String f = filename.toLowerCase(); + return f.contains(SVGVIEWBOX); + } + + public static boolean hasJavaScriptExtension(String filename) { + String f = filename.toLowerCase(); + return f.endsWith(JAVASCRIPT); + } + + public static boolean hasKeyboardPackageExtension(String filename) { + String f = filename.toLowerCase(); + return f.endsWith(KEYBOARDPACKAGE); + } + + public static boolean isWelcomeFile(String filename) { + String f = getFilename(filename); + return f.equalsIgnoreCase(WELCOME_HTM); + } + + /** + * Utility to extract the .svg# filename. + * @param filename String + * @return String. Empty string if filename is not .svg# + */ + public static String getSVGFilename(String filename) { + if (!hasSVGViewBox(filename)) { + return ""; + } + + String f = filename.toLowerCase(); + int i = f.indexOf(SVGVIEWBOX) + SVGVIEWBOX.length(); + return filename.substring(0, i); + } } diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_back.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_back.png deleted file mode 100644 index 0380d8994137f0bac7a05522ea4e79c8e088e841..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_back.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_check.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_check.png deleted file mode 100644 index 6c023edfe685de0f60ec8fa73e5852c889eb64bf..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_check.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_next.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_next.png deleted file mode 100644 index e47143306eb4bb75a1e0c2b325fc713f1b4ce043..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-hdpi/ic_action_next.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_add_white.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_add_white.png new file mode 100644 index 0000000000000000000000000000000000000000..8630f40d50c4deadfbf85b25318593dcd2158703 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-hdpi/ic_add_white.png differ diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_forward.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..252342d3d7ee47e6eb72f1c652f1a135190b3d13 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_forward.png differ diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_check.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_check.png new file mode 100644 index 0000000000000000000000000000000000000000..e802d90aeb092474fe4441d4904624e19e33aa19 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-hdpi/ic_check.png differ diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_light_action_info.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_light_action_info.png new file mode 100644 index 0000000000000000000000000000000000000000..0b465fc75a7e94d529b166d8f9ffbf03b8fb6980 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-hdpi/ic_light_action_info.png differ diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_back.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_back.png deleted file mode 100644 index 8ad6402973e68f67417fe5e7de443b3230007d97..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_back.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_check.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_check.png deleted file mode 100644 index 89aead0f81259774c1eb5293080cd945eaac953a..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_check.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_next.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_next.png deleted file mode 100644 index f29e602567f824e72bdf0b699d458ba31dca9c22..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-mdpi/ic_action_next.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/ic_add_white.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_add_white.png new file mode 100644 index 0000000000000000000000000000000000000000..694179bd46b685e55da1b4dde2575bde0b2bfd64 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-mdpi/ic_add_white.png differ diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_forward.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..52dd7b042b062ec7c726b3c5eeb8633fb9bda749 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_forward.png differ diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/ic_check.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_check.png new file mode 100644 index 0000000000000000000000000000000000000000..1c14c9c44592e95983dec13ca705ab99a6c54f21 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-mdpi/ic_check.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_light_action_info.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_light_action_info.png similarity index 100% rename from android/KMAPro/kMAPro/src/main/res/drawable-mdpi/ic_light_action_info.png rename to android/KMEA/app/src/main/res/drawable-mdpi/ic_light_action_info.png diff --git a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_back.png b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_back.png deleted file mode 100644 index 9d4c36e79f56380e061e54b0a89d8de488e821b3..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_back.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_check.png b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_check.png deleted file mode 100644 index d0fc8427f9c3b48faad2b1ae45df756071c1e5df..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_check.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_next.png b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_next.png deleted file mode 100644 index d12582a6968a02ee2a917a5010dfc578cb0eb933..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_action_next.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_add_white.png b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_add_white.png new file mode 100644 index 0000000000000000000000000000000000000000..0fdced8fce76a0f9a527388935adecebf63d0dae Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_add_white.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_arrow_forward.png b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_arrow_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..27767f1326411dd24dc69e87fe30a85154d6b3c1 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_arrow_forward.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_check.png b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_check.png new file mode 100644 index 0000000000000000000000000000000000000000..64a4944f7531ab9fb745fd34dd00c778cff1573f Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_check.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_light_action_info.png b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_light_action_info.png new file mode 100644 index 0000000000000000000000000000000000000000..3847a9fe74774d3593dd694cff48ccca13cdec5a Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_light_action_info.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_back.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_back.png deleted file mode 100644 index d34e7e0496055541fa759b1d0dbde294b7d166b6..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_back.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_check.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_check.png deleted file mode 100644 index d0c65872a422bdbbe6214de15e754473c5a3174a..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_check.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_next.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_next.png deleted file mode 100644 index c768823ab6931a0057fa5021a672b533cd85f23a..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_action_next.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_add_white.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_add_white.png new file mode 100644 index 0000000000000000000000000000000000000000..8b46191cc657c221652fae37a2c8a36986e0de3d Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_add_white.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_forward.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..a124259898f2cbaa970223e6b3e995bcf35f4bba Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_forward.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_check.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_check.png new file mode 100644 index 0000000000000000000000000000000000000000..b26a2c05e3f261641734c91aef9322830ab10daf Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_check.png differ diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_light_action_info.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_light_action_info.png similarity index 100% rename from android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/ic_light_action_info.png rename to android/KMEA/app/src/main/res/drawable-xxhdpi/ic_light_action_info.png diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_back.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_back.png deleted file mode 100644 index da7b8e5263ff755055d430dc82fa887bf4fd72dd..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_back.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_check.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_check.png deleted file mode 100644 index 3f6beb75f38e97074c9d3a673847afaaaa5b4736..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_check.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_next.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_next.png deleted file mode 100644 index 3a1c003af67ec0da8789ab0534820259f18bfb3c..0000000000000000000000000000000000000000 Binary files a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_action_next.png and /dev/null differ diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_add_white.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_add_white.png new file mode 100644 index 0000000000000000000000000000000000000000..7e699137220bb79450d0d9d5f361c9d1d00ed593 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_add_white.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_forward.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..8b5a68183db0af5ae96767121f57a6a5f4290587 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_forward.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_check.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_check.png new file mode 100644 index 0000000000000000000000000000000000000000..2f6d6386de9510fa6dd8c83cbb61a6f2e0fab9b2 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_check.png differ diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_light_action_info.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_light_action_info.png new file mode 100644 index 0000000000000000000000000000000000000000..f41f4010a622518310c844a6b3ba9258455ff881 Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_light_action_info.png differ diff --git a/android/KMEA/app/src/main/res/drawable/list_header.xml b/android/KMEA/app/src/main/res/drawable/list_header.xml deleted file mode 100644 index 9d08cf435f4a5fc78086f9388f629e7c91ab4b28..0000000000000000000000000000000000000000 --- a/android/KMEA/app/src/main/res/drawable/list_header.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/KMEA/app/src/main/res/drawable/list_item.xml b/android/KMEA/app/src/main/res/drawable/list_item.xml index c26b08b0aae9e063efee5d3a8a7db52a3e9e5090..ca3fc604083001ad16bc3ca293dd83bfab001aa4 100644 --- a/android/KMEA/app/src/main/res/drawable/list_item.xml +++ b/android/KMEA/app/src/main/res/drawable/list_item.xml @@ -1,6 +1,7 @@ + - - \ No newline at end of file + + diff --git a/android/KMEA/app/src/main/res/layout/activity_list_layout.xml b/android/KMEA/app/src/main/res/layout/activity_list_layout.xml new file mode 100644 index 0000000000000000000000000000000000000000..51c506fc28520921b9af7454af934f3cfce0b1d4 --- /dev/null +++ b/android/KMEA/app/src/main/res/layout/activity_list_layout.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml b/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml index 229aaeb549fee47e7aae4efaa2a9343a26d692f6..ef36e81bc8e54d1055f7eae3496e93efc1483fa1 100644 --- a/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml +++ b/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml @@ -1,10 +1,10 @@ @@ -20,5 +20,5 @@ android:text="@string/help_bubble_text" android:textColor="#404040" android:textSize="12sp" /> - - \ No newline at end of file + + diff --git a/android/KMEA/app/src/main/res/layout/key_preview_layout.xml b/android/KMEA/app/src/main/res/layout/key_preview_layout.xml index 27027a594a3b17f5883b6f692680f516d80e3502..c63c1c1c0278295c55fd3aee0b62ba02b3c0a79b 100644 --- a/android/KMEA/app/src/main/res/layout/key_preview_layout.xml +++ b/android/KMEA/app/src/main/res/layout/key_preview_layout.xml @@ -1,10 +1,10 @@ @@ -16,7 +16,7 @@ android:layout_gravity="center" android:gravity="center" android:textSize="32sp" - android:textStyle="bold" - android:textColor="#ffffffff" /> - + android:textStyle="bold" + android:textColor="#ffffffff" /> + diff --git a/android/KMEA/app/src/main/res/layout/keyboard_picker_list_layout.xml b/android/KMEA/app/src/main/res/layout/keyboard_picker_list_layout.xml new file mode 100644 index 0000000000000000000000000000000000000000..d19ca72bcd69316396c98877fc992ec570f932ca --- /dev/null +++ b/android/KMEA/app/src/main/res/layout/keyboard_picker_list_layout.xml @@ -0,0 +1,56 @@ + + + + + + + + + + \ No newline at end of file + diff --git a/android/KMEA/app/src/main/res/layout/subkeys_popup_layout.xml b/android/KMEA/app/src/main/res/layout/subkeys_popup_layout.xml index 28367ffeb653eed555e26d0f504c71796a5e7caa..8979ad9218cb19235e4ca239f4df15d9a1d50fb3 100644 --- a/android/KMEA/app/src/main/res/layout/subkeys_popup_layout.xml +++ b/android/KMEA/app/src/main/res/layout/subkeys_popup_layout.xml @@ -1,19 +1,19 @@ - + - - - - - - \ No newline at end of file + + + + + + diff --git a/android/KMEA/app/src/main/res/menu/popup.xml b/android/KMEA/app/src/main/res/menu/popup.xml index a53b686e05d8cf3bc071a51519ff7b743b8065db..6e6b8e3165465deb465479663e880019a3ef2f35 100644 --- a/android/KMEA/app/src/main/res/menu/popup.xml +++ b/android/KMEA/app/src/main/res/menu/popup.xml @@ -2,5 +2,5 @@ + android:title="@string/label_delete" /> diff --git a/android/KMEA/app/src/main/res/values-land/dimens.xml b/android/KMEA/app/src/main/res/values-land/dimens.xml index 4b615b2378630ec7bce385c7ef5ae7a4de20ba3b..5d20b97e5bab8308c44925b7f1291d9416c2389e 100644 --- a/android/KMEA/app/src/main/res/values-land/dimens.xml +++ b/android/KMEA/app/src/main/res/values-land/dimens.xml @@ -13,4 +13,7 @@ 60dp 0.5dp 6dp -
\ No newline at end of file + 12dp + 10dp + 70dp + diff --git a/android/KMEA/app/src/main/res/values-sw600dp-land/dimens.xml b/android/KMEA/app/src/main/res/values-sw600dp-land/dimens.xml index a4edfb6eb46ee5b262090d1ebca539e9214bbd4b..0d9efd3708afc4e0cf694adb76ef90737d555509 100644 --- a/android/KMEA/app/src/main/res/values-sw600dp-land/dimens.xml +++ b/android/KMEA/app/src/main/res/values-sw600dp-land/dimens.xml @@ -13,4 +13,4 @@ 85dp 0.5dp 8dp - \ No newline at end of file + diff --git a/android/KMEA/app/src/main/res/values-sw600dp/colors.xml b/android/KMEA/app/src/main/res/values-sw600dp/colors.xml deleted file mode 100644 index cf126744c4792c8c6449dae4bc4bb2c30ff85cbe..0000000000000000000000000000000000000000 --- a/android/KMEA/app/src/main/res/values-sw600dp/colors.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - #ffcc8822 - #ffa4a4a4 - #ff414040 - #ff414040 - \ No newline at end of file diff --git a/android/KMEA/app/src/main/res/values-sw600dp/dimens.xml b/android/KMEA/app/src/main/res/values-sw600dp/dimens.xml index d70dd27bc3929ec86abc4c3fee47901581c48e7b..3a46f2826fda89c2aa3aca67addcf400a773752b 100644 --- a/android/KMEA/app/src/main/res/values-sw600dp/dimens.xml +++ b/android/KMEA/app/src/main/res/values-sw600dp/dimens.xml @@ -13,4 +13,4 @@ 70dp 0.5dp 8dp - \ No newline at end of file + diff --git a/android/KMEA/app/src/main/res/values-sw720dp-land/dimens.xml b/android/KMEA/app/src/main/res/values-sw720dp-land/dimens.xml index 5c643113be445ccb9b37bd33252c9c309deb91bf..d716766e31a189f3b860aea39f6031d15c19e89d 100644 --- a/android/KMEA/app/src/main/res/values-sw720dp-land/dimens.xml +++ b/android/KMEA/app/src/main/res/values-sw720dp-land/dimens.xml @@ -13,4 +13,4 @@ 100dp 0.5dp 10dp - \ No newline at end of file + diff --git a/android/KMEA/app/src/main/res/values-sw720dp/dimens.xml b/android/KMEA/app/src/main/res/values-sw720dp/dimens.xml index 378307082e2e020f286710912e161908b6b67319..912f74b8098011498bf0e13aa7510e1b35ffc9ee 100644 --- a/android/KMEA/app/src/main/res/values-sw720dp/dimens.xml +++ b/android/KMEA/app/src/main/res/values-sw720dp/dimens.xml @@ -13,4 +13,4 @@ 80dp 0.5dp 10dp - \ No newline at end of file + diff --git a/android/KMEA/app/src/main/res/values-v11/styles.xml b/android/KMEA/app/src/main/res/values-v11/styles.xml deleted file mode 100644 index 3c02242ad044be9b8c7c09e7c90c5d427763fe97..0000000000000000000000000000000000000000 --- a/android/KMEA/app/src/main/res/values-v11/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/android/KMEA/app/src/main/res/values-v14/styles.xml b/android/KMEA/app/src/main/res/values-v14/styles.xml deleted file mode 100644 index 3b3e1300aee6682da4a415b0cb2b7c1caa389f3c..0000000000000000000000000000000000000000 --- a/android/KMEA/app/src/main/res/values-v14/styles.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/android/KMEA/app/src/main/res/values-v21/styles.xml b/android/KMEA/app/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..34942ed532b5e737069fb25f49a2f358f8ca3df7 --- /dev/null +++ b/android/KMEA/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/android/KMEA/app/src/main/res/values/colors.xml b/android/KMEA/app/src/main/res/values/colors.xml index 0d5be8de75ad52e5225d0e0a7b4725ff02751073..86ce5e004ec69e5e849d77f02d19e82d7cc47d70 100644 --- a/android/KMEA/app/src/main/res/values/colors.xml +++ b/android/KMEA/app/src/main/res/values/colors.xml @@ -1,6 +1,8 @@ - #ffcc8822 + + @android:color/holo_orange_light + #EEEEEEEE #ff282828 #ff000000 #ff282828 @@ -8,5 +10,5 @@ #ff838383 #ff333333 #ff737373 - - \ No newline at end of file + + diff --git a/android/KMEA/app/src/main/res/values/dimens.xml b/android/KMEA/app/src/main/res/values/dimens.xml index ee471c9e5c0d62b52109d053d0539b29edb54bb6..7dfbd8a441d371bf07495f2085817d878eb4a8cc 100644 --- a/android/KMEA/app/src/main/res/values/dimens.xml +++ b/android/KMEA/app/src/main/res/values/dimens.xml @@ -13,4 +13,7 @@ 60dp 0.5dp 6dp - \ No newline at end of file + 12dp + 10dp + 70dp + diff --git a/android/KMEA/app/src/main/res/values/strings.xml b/android/KMEA/app/src/main/res/values/strings.xml index f7badabdfb0aadbae6a9d230d0127faab68ccddd..baf64a1f9acab4f4b8ab44058b4fe156b02a30f3 100644 --- a/android/KMEA/app/src/main/res/values/strings.xml +++ b/android/KMEA/app/src/main/res/values/strings.xml @@ -1,38 +1,56 @@ - + AndroidMobile - + KMEA - - Settings - Keyboards + Keyboard Add New Keyboard - + - Action Button - Done Add - Back - + Cancel + Close + Close Keyman + Download + Install + Later + OK + Update + + + + + Cannot connect to Keyman server! + Checking keyboard updates… + Would you like to download this keyboard? + Would you like to update keyboards now? + Custom Keyboard + Downloading keyboard… + Keyboard Updates Available + Loading… + Updating keyboards… + + + Keyboard version + Help link + Image Button Image View - Delete + Delete KMAPreferences Tap here to change keyboard - Trial period has expired! - Reactivate - + diff --git a/android/KMEA/app/src/main/res/values/styles.xml b/android/KMEA/app/src/main/res/values/styles.xml index 394a07869287a01ed6e3889afbca6edbfb940f6c..35f8f7820123e50907386b2a246c38507cce63e0 100644 --- a/android/KMEA/app/src/main/res/values/styles.xml +++ b/android/KMEA/app/src/main/res/values/styles.xml @@ -1,10 +1,11 @@ - - - - + - + @anim/fade_in + @anim/fade_out + + + @anim/key_preview_fade_in + @anim/key_preview_fade_out + diff --git a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/PackageProcessorTest.java b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/PackageProcessorTest.java index d82409d276733a16b60ac646f3429cff02f2c0dc..4ddadc43f636210880f3413a3a15a389b438d051 100644 --- a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/PackageProcessorTest.java +++ b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/packages/PackageProcessorTest.java @@ -35,12 +35,18 @@ public class PackageProcessorTest { private static final File TEST_GFF_KMP_TARGET_ALT = new File(TEST_EXTRACTION_ROOT, "packages" + File.separator + TEST_GFF_KMP_NAME_ALT); + private static final String TEST_GFF_KMP_NAME_UNDEFINED_VER = TEST_GFF_KMP_NAME; + private static final File TEST_GFF_KMP_FILE_UNDEFINED_VER = new File(TEST_RESOURCE_ROOT, "v" + File.separator + TEST_GFF_KMP_NAME_UNDEFINED_VER + ".kmp"); + private static final File TEST_GFF_KMP_TARGET_UNDEFINED_VER = new File(TEST_EXTRACTION_ROOT, "packages" + + File.separator + TEST_GFF_KMP_NAME_UNDEFINED_VER); + private static final int TEST_GFF_KBD_COUNT = 2; private static final String TEST_GFF_PACKAGE_NAME = "GFF Amharic Keyboard"; private static final String TEST_GFF_KBD_ID = "gff_amh_7"; private static File tempPkg; private static File tempPkgAlt; + private static File tempPkgUndefinedVer; // Each test gets a fresh version of the extracted package. @Before @@ -63,6 +69,15 @@ public class PackageProcessorTest { } } + // Some tests wish to utilize package with undefined version... + public void extractUndefinedVerTestPackage() { + PackageProcessor.initialize(TEST_EXTRACTION_ROOT); + try { + tempPkgUndefinedVer = PackageProcessor.unzipKMP(TEST_GFF_KMP_FILE_UNDEFINED_VER); + } catch (IOException e) { + System.err.println(e); + } + } /** * Post-test cleanup. While the temp/ directory is .gitignore'd, this provides an extra layer @@ -73,6 +88,7 @@ public class PackageProcessorTest { public void eraseTestPackages() throws IOException { FileUtils.deleteDirectory(tempPkg); FileUtils.deleteQuietly(tempPkgAlt); + FileUtils.deleteQuietly(tempPkgUndefinedVer); FileUtils.deleteDirectory(TEST_GFF_KMP_TARGET); } @@ -218,6 +234,14 @@ public class PackageProcessorTest { Assert.assertNotEquals(TEST_GFF_KMP_NAME, PackageProcessor.getPackageName(json)); } + @Test + public void test_getPackageVersion() throws Exception { + extractUndefinedVerTestPackage(); + JSONObject json = PackageProcessor.loadPackageInfo(tempPkgUndefinedVer); + + Assert.assertEquals(PackageProcessor.PPDefault_Version, PackageProcessor.getPackageVersion(json)); + } + @Test public void test_versionChecks() throws Exception { Assert.assertFalse(PackageProcessor.isDowngrade(TEST_GFF_KMP_FILE)); diff --git a/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/FileUtilsTest.java b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/FileUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..675dc8526aa35931cc26e087e6f4d5d877876b45 --- /dev/null +++ b/android/KMEA/app/src/test/java/com/tavultesoft/kmea/util/FileUtilsTest.java @@ -0,0 +1,152 @@ +package com.tavultesoft.kmea.util; + +import android.os.Build; +import android.support.v4.widget.TextViewCompat; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class FileUtilsTest { + + @Test + public void test_hasFontExtension() { + String filename = "test/abc.ttf"; + Assert.assertTrue(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.TTF"; + Assert.assertTrue(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.otf"; + Assert.assertTrue(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.OTF"; + Assert.assertTrue(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.woff"; + Assert.assertTrue(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.WOFF"; + Assert.assertTrue(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.svg"; + Assert.assertTrue(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.SVG"; + Assert.assertTrue(FileUtils.hasFontExtension(filename)); + + filename = "test/abcttf"; + Assert.assertFalse(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.font"; + Assert.assertFalse(FileUtils.hasFontExtension(filename)); + + filename = "test/abcsvg"; + Assert.assertFalse(FileUtils.hasFontExtension(filename)); + + filename = "test/abc.svg#"; + Assert.assertFalse(FileUtils.hasFontExtension(filename)); + + filename = "test/abcwoff"; + Assert.assertFalse(FileUtils.hasFontExtension(filename)); + + filename = ""; + Assert.assertFalse(FileUtils.hasFontExtension(filename)); + } + + @Test + public void test_hasSVGViewBox() { + String filename = "test/abc.svg#xyz"; + Assert.assertTrue(FileUtils.hasSVGViewBox(filename)); + + filename = "test/abc.SVG#xyz"; + Assert.assertTrue(FileUtils.hasSVGViewBox(filename)); + + filename = "test/abc.svg#xyz"; + Assert.assertTrue(FileUtils.hasSVGViewBox(filename)); + + filename = "test/abcsvg#xyz"; + Assert.assertFalse(FileUtils.hasSVGViewBox(filename)); + + filename = ""; + Assert.assertFalse(FileUtils.hasSVGViewBox(filename)); + } + + @Test + public void test_hasJavascriptExtension() { + String filename = "test/abc.js"; + Assert.assertTrue(FileUtils.hasJavaScriptExtension(filename)); + + filename = "test/abc.JS"; + Assert.assertTrue(FileUtils.hasJavaScriptExtension(filename)); + + filename = "test/abc.sh"; + Assert.assertFalse(FileUtils.hasJavaScriptExtension(filename)); + + filename = "test/abcjs"; + Assert.assertFalse(FileUtils.hasJavaScriptExtension(filename)); + + filename = ""; + Assert.assertFalse(FileUtils.hasJavaScriptExtension(filename)); + } + + @Test + public void test_hasKeyboardPackageExtension() { + String filename = "test/abc.kmp"; + Assert.assertTrue(FileUtils.hasKeyboardPackageExtension(filename)); + + filename = "test/abc.KMP"; + Assert.assertTrue(FileUtils.hasKeyboardPackageExtension(filename)); + + filename = "test/abc.kmpo"; + Assert.assertFalse(FileUtils.hasKeyboardPackageExtension(filename)); + + filename = ""; + Assert.assertFalse(FileUtils.hasKeyboardPackageExtension(filename)); + } + + @Test + public void test_isWelcomeFile() { + String filename = "test/welcome.htm"; + Assert.assertTrue(FileUtils.isWelcomeFile(filename)); + + filename = "test/welcome.HTM"; + Assert.assertTrue(FileUtils.isWelcomeFile(filename)); + + filename = "test/WELCOME.HTM"; + Assert.assertTrue(FileUtils.isWelcomeFile(filename)); + + filename = "test/welcome.html"; + Assert.assertFalse(FileUtils.isWelcomeFile(filename)); + + filename = "test/zap-welcome.htm"; + Assert.assertFalse(FileUtils.isWelcomeFile(filename)); + + filename = "test/welcomehtm"; + Assert.assertFalse(FileUtils.isWelcomeFile(filename)); + + filename = ""; + Assert.assertFalse(FileUtils.isWelcomeFile(filename)); + } + + @Test + public void test_getSVGFilename() { + String filename = "test/abc.svg#xyz"; + Assert.assertEquals("test/abc.svg#", FileUtils.getSVGFilename(filename)); + + filename = "test/abc.SVG#XYZ"; + Assert.assertEquals("test/abc.SVG#", FileUtils.getSVGFilename(filename)); + + filename = ""; + Assert.assertEquals("", FileUtils.getSVGFilename(filename)); + + filename = "test/abc.notsvg#"; + Assert.assertEquals("", FileUtils.getSVGFilename(filename)); + + filename = "test/abc.svg"; + Assert.assertEquals("", FileUtils.getSVGFilename(filename)); + + } +} diff --git a/android/KMEA/build.gradle b/android/KMEA/build.gradle index 97d43c4dd2ac50be18aa3cb52beb04836e908c32..b6cb00b4fccf9911941bd7337adb2f4b7c83ee96 100644 --- a/android/KMEA/build.gradle +++ b/android/KMEA/build.gradle @@ -1,17 +1,17 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { - jcenter() google() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.2' } } allprojects { repositories { - jcenter() google() + jcenter() } } diff --git a/android/KMEA/gradle/wrapper/gradle-wrapper.properties b/android/KMEA/gradle/wrapper/gradle-wrapper.properties index a56ec474c2a365e7443f7d234027f4917c768109..41c09b90bc80b4ac360c8a9acc89542eea0d95d0 100644 --- a/android/KMEA/gradle/wrapper/gradle-wrapper.properties +++ b/android/KMEA/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/android/KMEA/test_resources/v/gff_amh_7_test_json.kmp b/android/KMEA/test_resources/v/gff_amh_7_test_json.kmp new file mode 100644 index 0000000000000000000000000000000000000000..5d6d76388c2d74a18ccf9d1217c2f5896e84f6ca Binary files /dev/null and b/android/KMEA/test_resources/v/gff_amh_7_test_json.kmp differ diff --git a/android/Samples/KMSample1/.idea/.name b/android/Samples/KMSample1/.idea/.name deleted file mode 100644 index d91a1663a7a614ac81890b85b4ec3dc6d5a9482c..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample1/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -KMSample1 \ No newline at end of file diff --git a/android/Samples/KMSample1/.idea/compiler.xml b/android/Samples/KMSample1/.idea/compiler.xml deleted file mode 100644 index 217af471a9e60576e8c36373fa22c50a63a00b2c..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample1/.idea/compiler.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - diff --git a/android/Samples/KMSample1/.idea/copyright/profiles_settings.xml b/android/Samples/KMSample1/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3377d40335424fd605124d4761390218bb..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample1/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/android/Samples/KMSample1/.idea/encodings.xml b/android/Samples/KMSample1/.idea/encodings.xml deleted file mode 100644 index e206d70d8595e2a50675ba11de48efcfa012497d..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample1/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/android/Samples/KMSample1/.idea/scopes/scope_settings.xml b/android/Samples/KMSample1/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b8433bcad6ce9778a37628d738faa26389..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample1/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/Samples/KMSample1/app/build.gradle b/android/Samples/KMSample1/app/build.gradle index 90fe1da883f20e4a5295691058cf13d845d794fa..ea8d63935957f5543ef75d560a220cfe19e9e61c 100644 --- a/android/Samples/KMSample1/app/build.gradle +++ b/android/Samples/KMSample1/app/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 25 - buildToolsVersion "26.0.2" + compileSdkVersion 27 defaultConfig { applicationId "com.keyman.kmsample1" minSdkVersion 15 - targetSdkVersion 25 + targetSdkVersion 27 versionCode 1 versionName "1.0" } @@ -24,15 +23,21 @@ repositories { dirs 'libs' } google() + +} + +ext { + currentFirebaseVersion = "15.0.2" } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:25.2.0' - implementation 'com.google.firebase:firebase-core:11.8.0' - implementation 'com.google.firebase:firebase-crash:11.8.0' - implementation('com.crashlytics.sdk.android:crashlytics:2.9.0@aar') { + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' + api(name: 'keyman-engine', ext: 'aar') + implementation "com.google.firebase:firebase-core:$currentFirebaseVersion" + implementation "com.google.firebase:firebase-crash:$currentFirebaseVersion" + implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') { transitive = true } - api (name:'keyman-engine', ext:'aar') } diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index d1137bc5d4d4e54fac395ed6e33a9677641b7c94..f196a4e277858dfb6febc8491fdc8c5926aa40d8 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -1,6 +1,6 @@ package com.keyman.kmsample1; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; import android.content.res.Configuration; import android.os.Bundle; import android.util.Log; @@ -18,12 +18,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class MainActivity extends Activity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener { +public class MainActivity extends AppCompatActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener { private KMTextView textView; @Override protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); KMManager.setDebugMode(true); diff --git a/android/Samples/KMSample1/app/src/main/res/layout/activity_main.xml b/android/Samples/KMSample1/app/src/main/res/layout/activity_main.xml index 317c86c3201b0262e7f0825f4d6aff7fc3f63186..37613d7e252959042695d566277e967506e55215 100644 --- a/android/Samples/KMSample1/app/src/main/res/layout/activity_main.xml +++ b/android/Samples/KMSample1/app/src/main/res/layout/activity_main.xml @@ -16,5 +16,4 @@ - diff --git a/android/Samples/KMSample1/app/src/main/res/values/styles.xml b/android/Samples/KMSample1/app/src/main/res/values/styles.xml index e84bc5ec14237011150645019d355336215bb1d8..aff9398525ed80b7e67d4ffc078897ba64113d1e 100644 --- a/android/Samples/KMSample1/app/src/main/res/values/styles.xml +++ b/android/Samples/KMSample1/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ - diff --git a/android/Samples/KMSample1/build.gradle b/android/Samples/KMSample1/build.gradle index 22690c837d056871fb70de7fb4e5cdcac9832823..c6db9aa806c657ed364e2a254e6d0eaa3acf1342 100644 --- a/android/Samples/KMSample1/build.gradle +++ b/android/Samples/KMSample1/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,6 +15,7 @@ buildscript { allprojects { repositories { + google() jcenter() } } diff --git a/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties b/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties index dade367f1042da843437a3220e6d9f359873cbb5..330e907be4e2b7e7de339f6e250f5d58ddef8974 100644 --- a/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties +++ b/android/Samples/KMSample1/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/android/Samples/KMSample2/.idea/.name b/android/Samples/KMSample2/.idea/.name deleted file mode 100644 index 2fee6f865d0ff61a3e51ee95127c5ac3c1ba1106..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample2/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -KMSample2 \ No newline at end of file diff --git a/android/Samples/KMSample2/.idea/compiler.xml b/android/Samples/KMSample2/.idea/compiler.xml deleted file mode 100644 index 217af471a9e60576e8c36373fa22c50a63a00b2c..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample2/.idea/compiler.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - diff --git a/android/Samples/KMSample2/.idea/copyright/profiles_settings.xml b/android/Samples/KMSample2/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3377d40335424fd605124d4761390218bb..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample2/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/android/Samples/KMSample2/.idea/encodings.xml b/android/Samples/KMSample2/.idea/encodings.xml deleted file mode 100644 index e206d70d8595e2a50675ba11de48efcfa012497d..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample2/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/android/Samples/KMSample2/.idea/scopes/scope_settings.xml b/android/Samples/KMSample2/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b8433bcad6ce9778a37628d738faa26389..0000000000000000000000000000000000000000 --- a/android/Samples/KMSample2/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/Samples/KMSample2/app/build.gradle b/android/Samples/KMSample2/app/build.gradle index 725b8346c38917010fc134bb85a7aefabcb4f115..166124ad9b0ad34c4c5616dc8e1bad26e9570baf 100644 --- a/android/Samples/KMSample2/app/build.gradle +++ b/android/Samples/KMSample2/app/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 25 - buildToolsVersion "26.0.2" + compileSdkVersion 27 defaultConfig { applicationId "com.keyman.kmsample2" minSdkVersion 15 - targetSdkVersion 25 + targetSdkVersion 27 versionCode 1 versionName "1.0" } @@ -26,13 +25,18 @@ repositories { google() } +ext { + currentFirebaseVersion = "15.0.2" +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:25.2.0' - implementation 'com.google.firebase:firebase-core:11.8.0' - implementation 'com.google.firebase:firebase-crash:11.8.0' - implementation('com.crashlytics.sdk.android:crashlytics:2.9.0@aar') { + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' + api (name:'keyman-engine', ext:'aar') + implementation "com.google.firebase:firebase-core:$currentFirebaseVersion" + implementation "com.google.firebase:firebase-crash:$currentFirebaseVersion" + implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') { transitive = true } - api (name:'keyman-engine', ext:'aar') } diff --git a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/MainActivity.java b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/MainActivity.java index d66b47d6010683d42155ba880eae729ed67e2e65..38f376c41f2f075af53b316edc06a24affe770c6 100644 --- a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/MainActivity.java +++ b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/MainActivity.java @@ -1,6 +1,6 @@ package com.keyman.kmsample2; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; import android.content.Context; import android.content.Intent; import android.provider.Settings; @@ -11,10 +11,11 @@ import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; -public class MainActivity extends Activity { +public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); diff --git a/android/Samples/KMSample2/app/src/main/res/xml/method.xml b/android/Samples/KMSample2/app/src/main/res/xml/method.xml index d246624c472fa9b0d6a83082e29dc94ef2e91c07..7c78fdc1b56cabeebbf6dc5f47a6a4fc8779495e 100644 --- a/android/Samples/KMSample2/app/src/main/res/xml/method.xml +++ b/android/Samples/KMSample2/app/src/main/res/xml/method.xml @@ -20,4 +20,5 @@ - + diff --git a/android/Samples/KMSample2/build.gradle b/android/Samples/KMSample2/build.gradle index 6b9a6dc60daf610356654ae393192730567810da..c6db9aa806c657ed364e2a254e6d0eaa3acf1342 100644 --- a/android/Samples/KMSample2/build.gradle +++ b/android/Samples/KMSample2/build.gradle @@ -2,11 +2,11 @@ buildscript { repositories { - jcenter() google() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,6 +15,7 @@ buildscript { allprojects { repositories { + google() jcenter() } } diff --git a/android/Samples/KMSample2/gradle/wrapper/gradle-wrapper.properties b/android/Samples/KMSample2/gradle/wrapper/gradle-wrapper.properties index 6730497cf8606279e53fad964429cba250db8576..478707bafe9ea88634f5524f00dc8607b45dfc00 100644 --- a/android/Samples/KMSample2/gradle/wrapper/gradle-wrapper.properties +++ b/android/Samples/KMSample2/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/android/Tests/KeyboardHarness/app/build.gradle b/android/Tests/KeyboardHarness/app/build.gradle index b031c663fc367c5788441df711721a8ce0f1abee..b31edf5a64444fd0238c817eecc0436d7c69a235 100644 --- a/android/Tests/KeyboardHarness/app/build.gradle +++ b/android/Tests/KeyboardHarness/app/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 25 - buildToolsVersion "26.0.2" + compileSdkVersion 27 defaultConfig { applicationId "com.keyman.android.tests.keyboardHarness" minSdkVersion 15 - targetSdkVersion 25 + targetSdkVersion 27 versionCode 1 versionName "1.0" } @@ -24,15 +23,21 @@ repositories { dirs 'libs' } google() + +} + +ext { + currentFirebaseVersion = "15.0.2" } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:25.2.0' - implementation 'com.google.firebase:firebase-core:11.8.0' - implementation 'com.google.firebase:firebase-crash:11.8.0' - implementation('com.crashlytics.sdk.android:crashlytics:2.9.0@aar') { + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' + api (name:'keyman-engine', ext:'aar') + implementation "com.google.firebase:firebase-core:$currentFirebaseVersion" + implementation "com.google.firebase:firebase-crash:$currentFirebaseVersion" + implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') { transitive = true } - api (name:'keyman-engine', ext:'aar') } diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/cloud/platformtest-1.0.js b/android/Tests/KeyboardHarness/app/src/main/assets/cloud/platformtest-1.0.js new file mode 100644 index 0000000000000000000000000000000000000000..e62165ce22391c721d1fb06c119e0d9faace698f --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/assets/cloud/platformtest-1.0.js @@ -0,0 +1,380 @@ + +KeymanWeb.KR(new Keyboard_platformtest()); + +function Keyboard_platformtest() +{ + + this.KI = "Keyboard_platformtest"; + this.KN = "PlatformTest"; + this.KMINVER = "9.0"; + this.KV = null; + this.KH = ''; + this.KM = 0; + this.KBVER = "1.0"; + this.KMBM = 0x0000; + this.s17 = "touch"; + this.s18 = "hardware"; + this.s21 = "windows"; + this.s22 = "android"; + this.s23 = "ios"; + this.s24 = "macosx"; + this.s25 = "linux"; + this.s28 = "desktop"; + this.s29 = "tablet"; + this.s30 = "phone"; + this.s33 = "native"; + this.s34 = "web"; + this.s37 = "ie"; + this.s38 = "chrome"; + this.s39 = "edge"; + this.s40 = "firefox"; + this.s41 = "safari"; + this.s42 = "opera"; + this.s45 = "touch"; + this.s46 = "hardware"; + this.s49 = "platform-x"; + this.s52 = "WinDOWS"; + this.s53 = "ANDroid"; + this.s54 = "iOS"; + this.s55 = "macOSX"; + this.s56 = "LINUX"; + this.s58 = "touch"; + this.s59 = "hardware"; + this.s60 = "windows"; + this.s61 = "android"; + this.s62 = "ios"; + this.s63 = "macosx"; + this.s64 = "linux"; + this.s65 = "desktop"; + this.s66 = "tablet"; + this.s67 = "phone"; + this.s68 = "native"; + this.s69 = "web"; + this.s70 = "ie"; + this.s71 = "chrome"; + this.s72 = "edge"; + this.s73 = "firefox"; + this.s74 = "safari"; + this.s75 = "opera"; + this.s76 = "touch"; + this.s77 = "hardware"; + this.s78 = "platform-x"; + this.s79 = "WinDOWS"; + this.s80 = "ANDroid"; + this.s81 = "iOS"; + this.s82 = "macOSX"; + this.s83 = "LINUX"; + this.KVER = "10.0.1109.0"; + this.gs = function(t, e) { + return this.g0(t, e); + }; + this.g0 = function(t, e) { + var k = KeymanWeb, + r = 0, + m = 0; + if (k.KKM(e, 16384, 65)) { + r = m = 1; + r = this.g1(t, e); + } + if (!m && k.KIK(e)) { + r = 1; + r = this.g9(t, e); + } + return r; + }; + this.g1 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s17, t)) { + m = 1; + k.KO(0, t, "touch"); + } else if (k.KIFS(31, this.s18, t)) { + m = 1; + k.KO(0, t, "hardware"); + } + if (m) { + r = this.g2(t, e); + } + if (!m) { + k.KO(-1, t, "undefined"); + r = this.g2(t, e); + } + return r; + }; + this.g2 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s21, t)) { + m = 1; + k.KO(0, t, " windows"); + } else if (k.KIFS(31, this.s22, t)) { + m = 1; + k.KO(0, t, " android"); + } else if (k.KIFS(31, this.s23, t)) { + m = 1; + k.KO(0, t, " ios"); + } else if (k.KIFS(31, this.s24, t)) { + m = 1; + k.KO(0, t, " macosx"); + } else if (k.KIFS(31, this.s25, t)) { + m = 1; + k.KO(0, t, " linux"); + } + if (m) { + r = this.g3(t, e); + } + if (!m) { + k.KO(-1, t, " undefined"); + r = this.g3(t, e); + } + return r; + }; + this.g3 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s28, t)) { + m = 1; + k.KO(0, t, " desktop"); + } else if (k.KIFS(31, this.s29, t)) { + m = 1; + k.KO(0, t, " tablet"); + } else if (k.KIFS(31, this.s30, t)) { + m = 1; + k.KO(0, t, " phone"); + } + if (m) { + r = this.g4(t, e); + } + if (!m) { + k.KO(-1, t, " undefined"); + r = this.g4(t, e); + } + return r; + }; + this.g4 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s33, t)) { + m = 1; + k.KO(0, t, " native"); + } else if (k.KIFS(31, this.s34, t)) { + m = 1; + k.KO(0, t, " web"); + } + if (m) { + r = this.g5(t, e); + } + if (!m) { + k.KO(-1, t, " undefined"); + r = this.g5(t, e); + } + return r; + }; + this.g5 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s37, t)) { + m = 1; + k.KO(0, t, " ie"); + } else if (k.KIFS(31, this.s38, t)) { + m = 1; + k.KO(0, t, " chrome"); + } else if (k.KIFS(31, this.s39, t)) { + m = 1; + k.KO(0, t, " edge"); + } else if (k.KIFS(31, this.s40, t)) { + m = 1; + k.KO(0, t, " firefox"); + } else if (k.KIFS(31, this.s41, t)) { + m = 1; + k.KO(0, t, " safari"); + } else if (k.KIFS(31, this.s42, t)) { + m = 1; + k.KO(0, t, " opera"); + } + if (m) { + r = this.g6(t, e); + } + if (!m) { + k.KO(-1, t, " undefined"); + r = this.g6(t, e); + } + return r; + }; + this.g6 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (!k.KIFS(31, this.s45, t)) { + m = 1; + k.KO(0, t, " !touch"); + } else if (!k.KIFS(31, this.s46, t)) { + m = 1; + k.KO(0, t, " !hardware"); + } + if (m) { + r = this.g7(t, e); + } + if (!m) { + k.KO(-1, t, " undefined"); + r = this.g7(t, e); + } + return r; + }; + this.g7 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s49, t)) { + m = 1; + k.KO(0, t, " platform-x"); + } + if (m) { + r = this.g8(t, e); + } + if (!m) { + k.KO(-1, t, " undefined"); + r = this.g8(t, e); + } + return r; + }; + this.g8 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s52, t)) { + m = 1; + k.KO(0, t, " windows"); + } else if (k.KIFS(31, this.s53, t)) { + m = 1; + k.KO(0, t, " android"); + } else if (k.KIFS(31, this.s54, t)) { + m = 1; + k.KO(0, t, " ios"); + } else if (k.KIFS(31, this.s55, t)) { + m = 1; + k.KO(0, t, " macosx"); + } else if (k.KIFS(31, this.s56, t)) { + m = 1; + k.KO(0, t, " linux"); + } + if (!m) { + k.KO(-1, t, " undefined"); + } + return r; + }; + this.g9 = function(t, e) { + var k = KeymanWeb, + r = 0, + m = 0; + if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s79, t)) { + r = m = 1; + k.KO(0, t, " Windows"); + } else if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s80, t)) { + r = m = 1; + k.KO(0, t, " Android"); + } else if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s81, t)) { + r = m = 1; + k.KO(0, t, " iOS"); + } else if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s82, t)) { + r = m = 1; + k.KO(0, t, " macOS"); + } else if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s83, t)) { + r = m = 1; + k.KO(0, t, " Linux"); + } else if (k.KKM(e, 16384, 69)) { + r = m = 1; + k.KO(0, t, " [OS Undefined (case insensitive test)]"); + } else if (k.KKM(e, 16384, 73) && k.KIFS(31, this.s65, t)) { + r = m = 1; + k.KO(0, t, " Desktop"); + } else if (k.KKM(e, 16384, 73) && k.KIFS(31, this.s66, t)) { + r = m = 1; + k.KO(0, t, " Tablet"); + } else if (k.KKM(e, 16384, 73) && k.KIFS(31, this.s67, t)) { + r = m = 1; + k.KO(0, t, " Phone"); + } else if (k.KKM(e, 16384, 73)) { + r = m = 1; + k.KO(0, t, " [FF Undefined]"); + } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s60, t)) { + r = m = 1; + k.KO(0, t, " Windows"); + } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s61, t)) { + r = m = 1; + k.KO(0, t, " Android"); + } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s62, t)) { + r = m = 1; + k.KO(0, t, " iOS"); + } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s63, t)) { + r = m = 1; + k.KO(0, t, " OSX"); + } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s64, t)) { + r = m = 1; + k.KO(0, t, " Linux"); + } else if (k.KKM(e, 16384, 79)) { + r = m = 1; + k.KO(0, t, " [OS Undefined]"); + } else if (k.KKM(e, 16384, 80) && k.KIFS(31, this.s58, t)) { + r = m = 1; + k.KO(0, t, "Touch"); + } else if (k.KKM(e, 16384, 80) && k.KIFS(31, this.s59, t)) { + r = m = 1; + k.KO(0, t, "Hardware"); + } else if (k.KKM(e, 16384, 80)) { + r = m = 1; + k.KO(0, t, "[UI Undefined]"); + } else if (k.KKM(e, 16384, 82) && k.KIFS(31, this.s78, t)) { + r = m = 1; + k.KO(0, t, " Platform-X"); + } else if (k.KKM(e, 16384, 82)) { + r = m = 1; + k.KO(0, t, " [Platform-X Undefined]"); + } else if (k.KKM(e, 16384, 84) && !k.KIFS(31, this.s76, t)) { + r = m = 1; + k.KO(0, t, " !Touch"); + } else if (k.KKM(e, 16384, 84) && !k.KIFS(31, this.s77, t)) { + r = m = 1; + k.KO(0, t, " !Hardware"); + } else if (k.KKM(e, 16384, 84)) { + r = m = 1; + k.KO(0, t, " [Inverted OS Undefined]"); + } else if (k.KKM(e, 16384, 85) && k.KIFS(31, this.s68, t)) { + r = m = 1; + k.KO(0, t, " Native"); + } else if (k.KKM(e, 16384, 85) && k.KIFS(31, this.s69, t)) { + r = m = 1; + k.KO(0, t, " Web"); + } else if (k.KKM(e, 16384, 85)) { + r = m = 1; + k.KO(0, t, " [Nativeness Undefined]"); + } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s70, t)) { + r = m = 1; + k.KO(0, t, " IE"); + } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s71, t)) { + r = m = 1; + k.KO(0, t, " Chrome"); + } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s72, t)) { + r = m = 1; + k.KO(0, t, " Edge"); + } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s73, t)) { + r = m = 1; + k.KO(0, t, " Firefox"); + } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s74, t)) { + r = m = 1; + k.KO(0, t, " Safari"); + } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s75, t)) { + r = m = 1; + k.KO(0, t, " Opera"); + } else if (k.KKM(e, 16384, 89)) { + r = m = 1; + k.KO(0, t, " [Browser Undefined]"); + } + return r; + }; +} diff --git a/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java index 37f438a1f837838914ba9bccfc4be9d6ba423500..1c728c6586af9ea1960f0eeb9afc3458ee78e1e0 100644 --- a/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java +++ b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java @@ -1,6 +1,6 @@ package com.keyman.android.tests.keyboardHarness; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; import android.content.res.Configuration; import android.os.Bundle; import android.util.Log; @@ -18,12 +18,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class MainActivity extends Activity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener { +public class MainActivity extends AppCompatActivity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener { private KMTextView textView; @Override protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); KMManager.setDebugMode(true); @@ -56,6 +57,17 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O longpressKBbInfo.put(KMManager.KMKey_KeyboardVersion, "1.0"); longpressKBbInfo.put(KMManager.KMKey_Font, "code2001.ttf"); KMManager.addKeyboard(this, longpressKBbInfo); + + // Platform test keyboard + HashMap platformtestKBbInfo = new HashMap(); + platformtestKBbInfo.put(KMManager.KMKey_PackageID, "cloud"); + platformtestKBbInfo.put(KMManager.KMKey_KeyboardID, "platformtest"); + platformtestKBbInfo.put(KMManager.KMKey_LanguageID, "en"); + platformtestKBbInfo.put(KMManager.KMKey_KeyboardName, "platformtest Keyboard"); + platformtestKBbInfo.put(KMManager.KMKey_LanguageName, "English"); + platformtestKBbInfo.put(KMManager.KMKey_KeyboardVersion, "1.0"); + platformtestKBbInfo.put(KMManager.KMKey_Font, KeyboardFont); + KMManager.addKeyboard(this, platformtestKBbInfo); } @Override diff --git a/android/Tests/KeyboardHarness/app/src/main/res/values/styles.xml b/android/Tests/KeyboardHarness/app/src/main/res/values/styles.xml index e84bc5ec14237011150645019d355336215bb1d8..aff9398525ed80b7e67d4ffc078897ba64113d1e 100644 --- a/android/Tests/KeyboardHarness/app/src/main/res/values/styles.xml +++ b/android/Tests/KeyboardHarness/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ - diff --git a/android/Tests/KeyboardHarness/build.gradle b/android/Tests/KeyboardHarness/build.gradle index 6b9a6dc60daf610356654ae393192730567810da..c6db9aa806c657ed364e2a254e6d0eaa3acf1342 100644 --- a/android/Tests/KeyboardHarness/build.gradle +++ b/android/Tests/KeyboardHarness/build.gradle @@ -2,11 +2,11 @@ buildscript { repositories { - jcenter() google() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,6 +15,7 @@ buildscript { allprojects { repositories { + google() jcenter() } } diff --git a/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties b/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties index bb759f148c833c252a79202bfbac2356ae711cec..89bf9a1635ff0aa6747db4978b38a3f7f0f59527 100644 --- a/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties +++ b/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/android/Tests/keycode/.gitignore b/android/Tests/keycode/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..fd45b12f05e6d4870898dbeb9ac17a499fc17915 --- /dev/null +++ b/android/Tests/keycode/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea/caches/build_file_checksums.ser +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/android/Tests/keycode/app/.gitignore b/android/Tests/keycode/app/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/android/Tests/keycode/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/android/Tests/keycode/app/build.gradle b/android/Tests/keycode/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..5702606e4f9331af397ecfd35bfce1858a89dc8a --- /dev/null +++ b/android/Tests/keycode/app/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.keyman.android.tests.keycode" + minSdkVersion 15 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/android/Tests/keycode/app/proguard-rules.pro b/android/Tests/keycode/app/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f1b424510da51fd82143bc74a0a801ae5a1e2fcd --- /dev/null +++ b/android/Tests/keycode/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/Tests/keycode/app/src/androidTest/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java b/android/Tests/keycode/app/src/androidTest/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d4bbda1b0533ec80c20cb844f872ce5cb92c3ad5 --- /dev/null +++ b/android/Tests/keycode/app/src/androidTest/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.keyman.android.tests.keycode; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.keyman.android.tests.keycode", appContext.getPackageName()); + } +} diff --git a/android/Tests/keycode/app/src/main/AndroidManifest.xml b/android/Tests/keycode/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..d2586771dfd1a442fa0a33e3534474af78006e22 --- /dev/null +++ b/android/Tests/keycode/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/Tests/keycode/app/src/main/java/com/keyman/android/tests/keycode/MainActivity.java b/android/Tests/keycode/app/src/main/java/com/keyman/android/tests/keycode/MainActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..35e28c16a84f3c7b2cc1edc6586513f1a8d3139a --- /dev/null +++ b/android/Tests/keycode/app/src/main/java/com/keyman/android/tests/keycode/MainActivity.java @@ -0,0 +1,27 @@ +package com.keyman.android.tests.keycode; + +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.KeyEvent; + +import android.os.Bundle; +import android.widget.TextView; + +public class MainActivity extends AppCompatActivity { + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + String info = String.format("keyCode: %d\nscanCode: %d", event.getKeyCode(), event.getScanCode()); + Log.d("hw", info); + + TextView textView = (TextView) findViewById(R.id.main_text); + textView.setText(info); + return true; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } +} diff --git a/android/Tests/keycode/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/Tests/keycode/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000000000000000000000000000000000..1f6bb290603d7caa16c5fb6f61bbfdc750622f5c --- /dev/null +++ b/android/Tests/keycode/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/android/Tests/keycode/app/src/main/res/drawable/ic_launcher_background.xml b/android/Tests/keycode/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..0d025f9bf6b67c63044a36a9ff44fbc69e5c5822 --- /dev/null +++ b/android/Tests/keycode/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/Tests/keycode/app/src/main/res/layout/activity_main.xml b/android/Tests/keycode/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..adce24ea4e17ca6e87768c79baa71d83a3d243c4 --- /dev/null +++ b/android/Tests/keycode/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/android/Tests/keycode/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/Tests/keycode/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000000000000000000000000000000000..eca70cfe52eac1ba66ba280a68ca7be8fcf88a16 --- /dev/null +++ b/android/Tests/keycode/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/Tests/keycode/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/Tests/keycode/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000000000000000000000000000000000..eca70cfe52eac1ba66ba280a68ca7be8fcf88a16 --- /dev/null +++ b/android/Tests/keycode/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/Tests/keycode/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/Tests/keycode/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..898f3ed59ac9f3248734a00e5902736c9367d455 Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/Tests/keycode/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..dffca3601eba7bf5f409bdd520820e2eb5122c75 Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/Tests/keycode/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..64ba76f75e9ce021aa3d95c213491f73bcacb597 Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/Tests/keycode/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..dae5e082342fcdeee5db8a6e0b27028e2d2808f5 Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/Tests/keycode/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ed46597ea8447d91ab1786a34e30f1c26b18bd Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/Tests/keycode/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..14ed0af35023e4f1901cf03487b6c524257b8483 Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/Tests/keycode/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b0907cac3bfd8fbfdc46e1108247f0a1055387ec Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/Tests/keycode/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..d8ae03154975f397f8ed1b84f2d4bf9783ecfa26 Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/Tests/keycode/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2c18de9e66108411737e910f5c1972476f03ddbf Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/Tests/keycode/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/Tests/keycode/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..beed3cdd2c32af5114a7dc70b9ef5b698eb8797e Binary files /dev/null and b/android/Tests/keycode/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/Tests/keycode/app/src/main/res/values/colors.xml b/android/Tests/keycode/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000000000000000000000000000000000..69b22338c6510250df3b43672635120dbce2fa49 --- /dev/null +++ b/android/Tests/keycode/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #008577 + #00574B + #D81B60 + diff --git a/android/Tests/keycode/app/src/main/res/values/strings.xml b/android/Tests/keycode/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..ff0a8d144f2bfddd9c181771c5bd7fed74d9a8b4 --- /dev/null +++ b/android/Tests/keycode/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + keycode + diff --git a/android/Tests/keycode/app/src/main/res/values/styles.xml b/android/Tests/keycode/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..5885930df6d10edf3d6df40d6556297d11f953da --- /dev/null +++ b/android/Tests/keycode/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleUnitTest.java b/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleUnitTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7e0e3c7481687e9d4d2393f97119ff33e3ef9c0f --- /dev/null +++ b/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.example.darcy.keycode; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/android/Tests/keycode/build.gradle b/android/Tests/keycode/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..8d3ef8e5c1a6111ae2ba26ee0212ca035a39bc76 --- /dev/null +++ b/android/Tests/keycode/build.gradle @@ -0,0 +1,27 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/Tests/keycode/gradle.properties b/android/Tests/keycode/gradle.properties new file mode 100644 index 0000000000000000000000000000000000000000..82618cecb4d1cf137df18eca8dbe88e1b3b2c2b8 --- /dev/null +++ b/android/Tests/keycode/gradle.properties @@ -0,0 +1,15 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + + diff --git a/android/Tests/keycode/gradle/wrapper/gradle-wrapper.jar b/android/Tests/keycode/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 Binary files /dev/null and b/android/Tests/keycode/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/Tests/keycode/gradle/wrapper/gradle-wrapper.properties b/android/Tests/keycode/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..9a4163a4f5ee222ba10e7c7c2f4de529dc0b52a1 --- /dev/null +++ b/android/Tests/keycode/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/Tests/keycode/gradlew b/android/Tests/keycode/gradlew new file mode 100644 index 0000000000000000000000000000000000000000..cccdd3d517fc5249beaefa600691cf150f2fa3e6 --- /dev/null +++ b/android/Tests/keycode/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/android/Tests/keycode/gradlew.bat b/android/Tests/keycode/gradlew.bat new file mode 100644 index 0000000000000000000000000000000000000000..f9553162f122c71b34635112e717c3e733b5b212 --- /dev/null +++ b/android/Tests/keycode/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/Tests/keycode/settings.gradle b/android/Tests/keycode/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..e7b4def49cb53d9aa04228dd3edb14c9e635e003 --- /dev/null +++ b/android/Tests/keycode/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/android/history.md b/android/history.md index feee8bc1e5531fb18b23cf85c15625f5b62ed07b..2c0686be99dd105ef6bbd86afbbbfbddd14a5f01 100644 --- a/android/history.md +++ b/android/history.md @@ -1,14 +1,129 @@ # Keyman for Android + +## 11.0 alpha +* Move to 11.0 +* Add round launcher icons (#1077) +* Change default keyboard from `european` to `sil_euro_latin` (#1112) +* Remove deprecated ad-hoc distribution of keyboards via `keyman://` protocol (#1109) +* Add splash screen (#1151) +* Update app to use Material Design theme (#681) +* Fix globe button when pausing WebBrowser (#1213) +* Change system keyboard to "numeric" layer for digit/phone number text fields (#1218) +* Add feature to vibrate device when Keyman Web calls `beep` (#1227) +* Add support for extra key found on European hardware keyboards (#1291) +* Add feature for keyboard picker to switch to next keyboard (#1283) +* Update to Cloud API 4.0 for downloading keyboards (#1320) +* Fixes issue where file extensions are upper-case, e.g. ".TTF" (#1333) + +## 2018-11-14 10.0.508 stable +* Fix crash that can occur when text selection ends before the starting position (#1313) + +## 2018-10-04 10.0.507 stable +* Fix crash that can occur when displaying preview key (#1230, #1234) + +## 2018-08-23 10.0.505 stable +* Validate keyboard ID when downloading keyboard from Keyman cloud (#1121) + +## 2018-08-22 10.0.504 stable +* Fixes crash when installed keyboards list is invalid (#1119) + +## 2018-08-16 10.0.503 stable +* Fixes crashes for release configurations when InputConfiguration or package name is null (#1103) + +## 2018-07-06 10.0.502 stable +* Fixes issue for embedded Android, iOS apps where a keyboard with varying row counts in different layers could crash (#1055) + +## 2018-06-28 10.0.500 stable +* 10.0 stable release + +## 2018-06-27 10.0.405 beta +* Fixes issue where next layer was not correctly selected when the first longpress key pressed (#1027) +* Fixes issue where a quote character in some contexts could cause the keyboard to fail (#1028) + +## 2018-06-26 10.0.404 beta +* Add documentation for version 10.0 (#1023) + +## 2018-06-21 10.0.403 beta +* Fixes an issue where opening a menu could cause the keyboard to unload (#1014) + +## 2018-06-20 10.0.402 beta +* Support script subtags for keyboard languages supplied from cloud (#1012) + +## 2018-06-15 10.0.401 beta +* No changes to Keyman for Android + +## 2018-06-13 10.0.400 beta +* No changes to Keyman for Android + +## 2018-06-11 10.0.399 beta +* Consolidate Info view (#972) +* Platform tests (in keyboards) are now consistent across all platforms (#969) + +## 2018-06-05 10.0.398 beta +* Fix globe button for system keyboard (#942) + +## 2018-05-27 10.0.397 beta +* Improve intent-filter for *.kmp extensions (#902) + +## 2018-05-22 10.0.396 beta +* No changes to Keyman for Android. + +## 2018-05-22 10.0.395 beta +* No changes to Keyman for Android. + +## 2018-05-18 10.0.394 beta +* No changes to Keyman for Android. + +## 2018-05-17 10.0.393 beta +* No changes to Keyman for Android. + +## 2018-05-11 10.0.392 beta +* Fix globe button when exiting in-app browser (#848) + +## 2018-05-11 10.0.391 beta +* Update compile and target Android SDK version to 27 (#750) + +## 2018-05-08 10.0.386 beta +* Fix crashes from invalid package name/version (#819) +* Clean up console log (#748) + +## 2018-04-30 10.0.385 beta +* No changes to Keyman for Android (updated Keyman Web Engine, #834) + +## 2018-04-30 10.0.384 beta +* No changes to Keyman for Android. + +## 2018-05-03 10.0.383 beta +* No changes to Keyman for Android. + +## 2018-04-30 10.0.382 beta +* No changes to Keyman for Android (updated Keyman Web Engine, #797) + +## 2018-04-30 10.0.381 beta +* Fix OSK missing some keys on older Android configurations (#781) + ## 2018-04-30 10.0.380 beta -* Fix OSK missing some keys on older Android configurations (#304) +* No changes to Keyman for Android. ## 2018-04-27 10.0.379 beta * Fixed app crash when cancelling Keyboard download dialog (#786) +## 2018-04-25 10.0.378 beta +* No changes to Keyman for Android (updated Keyman Web Engine, #772). + +## 2018-04-25 10.0.377 beta +* No changes to Keyman for Android (updated Keyman Web Engine, #773). + ## 2018-04-24 10.0.376 beta * Hide system OSK when resuming Keyman app. Disable text suggestions (#711) +## 2018-04-12 10.0.375 beta +* No changes to Keyman for Android. + +## 2018-04-12 10.0.374 beta +* Fixes for back button in web browser (#737) + ## 2018-03-22 10.0.373 beta * Initial beta release of Keyman for Android 10.0 diff --git a/common/engine/keyboardprocessor/.gitignore b/common/engine/keyboardprocessor/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9a44da12f818a34200d63dbf4840d251fec8993b --- /dev/null +++ b/common/engine/keyboardprocessor/.gitignore @@ -0,0 +1,2 @@ +.vs/ +build/ diff --git a/common/engine/keyboardprocessor/debian/changelog b/common/engine/keyboardprocessor/debian/changelog new file mode 100644 index 0000000000000000000000000000000000000000..18a0929f915772f300480734eb3a44c84ad9d235 --- /dev/null +++ b/common/engine/keyboardprocessor/debian/changelog @@ -0,0 +1,5 @@ +keyman-keyboardprocessor (0.0.0~2018-1) unstable; urgency=medium + + * Initial release. + + -- Daniel Glassey Tue, 20 Nov 2018 10:01:32 +0700 diff --git a/common/engine/keyboardprocessor/debian/compat b/common/engine/keyboardprocessor/debian/compat new file mode 100644 index 0000000000000000000000000000000000000000..ec635144f60048986bc560c5576355344005e6e7 --- /dev/null +++ b/common/engine/keyboardprocessor/debian/compat @@ -0,0 +1 @@ +9 diff --git a/common/engine/keyboardprocessor/debian/control b/common/engine/keyboardprocessor/debian/control new file mode 100644 index 0000000000000000000000000000000000000000..a806954d74be5f65cd15fcefd2f115dcff57722f --- /dev/null +++ b/common/engine/keyboardprocessor/debian/control @@ -0,0 +1,56 @@ +Source: keyman-keyboardprocessor +Build-Depends: debhelper (>=9~), + meson (>= 0.40), + ninja-build +Section: text +Priority: optional +Maintainer: Debian Input Method Team +Uploaders: Daniel Glassey +Standards-Version: 3.9.8 +Homepage: https://www.keyman.com +Vcs-Browser: https://salsa.debian.org/input-method-team/keyman-keyboardprocessor +Vcs-Git: https://salsa.debian.org/input-method-team/keyman-keyboardprocessor.git + +Package: libkmnkbp0-0 +Architecture: any +Section: libs +Pre-Depends: ${misc:Pre-Depends} +Depends: ${shlibs:Depends}, ${misc:Depends} +Suggests: keyman +Multi-Arch: same +Description: Keyman keyboard processing library + Originally created in 1993 to type Lao on Windows, Keyman is now a free and + open source keyboarding platform which allows anyone to write a keyboard layout + for their language. Keyman is available for many platforms, including Windows, + macOS, iOS, Android, Linux and the web. + . + Keyboard layouts are defined with a clear and easy to understand keyboard + grammar. Keyman's contextual input model means keyboard layouts can be + intelligent and make it simple to type even the most complex languages. + Keyboard layouts are distributed through an open catalog to all major desktop + and mobile platforms. + . + The Keyman keyboardprocessor library processes input from input method engines + and applies rules from compiled Keyman 11 or earlier kmx keyboard files. + +Package: libkmnkbp-dev +Architecture: any +Section: libdevel +Depends: libkmnkbp0-0 (= ${binary:Version}), ${misc:Depends} +Multi-Arch: same +Description: Development files for Keyman keyboard processing library + Originally created in 1993 to type Lao on Windows, Keyman is now a free and + open source keyboarding platform which allows anyone to write a keyboard layout + for their language. Keyman is available for many platforms, including Windows, + macOS, iOS, Android, Linux and the web. + . + Keyboard layouts are defined with a clear and easy to understand keyboard + grammar. Keyman's contextual input model means keyboard layouts can be + intelligent and make it simple to type even the most complex languages. + Keyboard layouts are distributed through an open catalog to all major desktop + and mobile platforms. + . + The Keyman keyboardprocessor library processes input from input method engines + and applies rules from compiled Keyman 11 or earlier kmx keyboard files. + . + This package contains development headers and libraries. diff --git a/common/engine/keyboardprocessor/debian/copyright b/common/engine/keyboardprocessor/debian/copyright new file mode 100644 index 0000000000000000000000000000000000000000..c2c2c26644b0634717632ab602fa7345b9163d33 --- /dev/null +++ b/common/engine/keyboardprocessor/debian/copyright @@ -0,0 +1,27 @@ +Format: http://anonscm.debian.org/viewvc/dep/web/deps/dep5.mdwn?view=markup&pathrev=174 +Upstream-Name: keyman-keyboardprocessor +Upstream-Contact: SIL International + +Files: * +Copyright: Copyright (C) 2018 SIL International +License: MIT + +License: MIT + 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/common/engine/keyboardprocessor/debian/libkmnkbp-dev.install b/common/engine/keyboardprocessor/debian/libkmnkbp-dev.install new file mode 100644 index 0000000000000000000000000000000000000000..12fcb139bf495b072c1448cc096c961255c8770d --- /dev/null +++ b/common/engine/keyboardprocessor/debian/libkmnkbp-dev.install @@ -0,0 +1,2 @@ +usr/include/keyman/* +usr/lib/*/lib*.so diff --git a/common/engine/keyboardprocessor/debian/libkmnkbp0-0.install b/common/engine/keyboardprocessor/debian/libkmnkbp0-0.install new file mode 100644 index 0000000000000000000000000000000000000000..3ddde584198421f0d2a3fef9d36cc110f3b8b76d --- /dev/null +++ b/common/engine/keyboardprocessor/debian/libkmnkbp0-0.install @@ -0,0 +1 @@ +usr/lib/*/lib*.so.* diff --git a/common/engine/keyboardprocessor/debian/rules b/common/engine/keyboardprocessor/debian/rules new file mode 100755 index 0000000000000000000000000000000000000000..aeaa0bfcede684a61647d3996ee100019cb27a27 --- /dev/null +++ b/common/engine/keyboardprocessor/debian/rules @@ -0,0 +1,31 @@ +#!/usr/bin/make -f + +export DH_VERBOSE=1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# xenial needs this to be explicit +export LC_ALL=C.UTF-8 + +%: + dh $@ + +override_dh_auto_clean: + rm -fr debian/build + +override_dh_auto_configure: + mkdir -p debian/build + cd debian/build && meson \ + --prefix=/usr \ + ../.. + +override_dh_auto_build: + cd debian/build && ninja -v + +override_dh_auto_test: +ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) + cd debian/build && ninja test +endif + +override_dh_auto_install: + cd debian/build && DESTDIR=${CURDIR}/debian/tmp ninja install + diff --git a/common/engine/keyboardprocessor/debian/source/format b/common/engine/keyboardprocessor/debian/source/format new file mode 100644 index 0000000000000000000000000000000000000000..163aaf8d82b6c54f23c45f32895dbdfdcc27b047 --- /dev/null +++ b/common/engine/keyboardprocessor/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/common/engine/keyboardprocessor/debian/watch b/common/engine/keyboardprocessor/debian/watch new file mode 100644 index 0000000000000000000000000000000000000000..0423b9164755003e52a20f64e8f045441953da63 --- /dev/null +++ b/common/engine/keyboardprocessor/debian/watch @@ -0,0 +1,2 @@ +version=4 +https://downloads.keyman.com/linux/(?:alpha|beta|stable)/(\d\S+)/keyman-keyboardprocessor-(.+)\.tar\.gz debian uupdate diff --git a/common/engine/keyboardprocessor/doc/BUILDING.md b/common/engine/keyboardprocessor/doc/BUILDING.md new file mode 100644 index 0000000000000000000000000000000000000000..5bf2b70ffa34626f3da8cc0b0dcb4acff3f4f2f9 --- /dev/null +++ b/common/engine/keyboardprocessor/doc/BUILDING.md @@ -0,0 +1,64 @@ +# How to build the keyboard processor + +## Prerequisites +- Python 3 +- Meson build system. +- C++14 or later compiler. +- lib std::fs +- kmcomp (for tests) -- must be added to path + +## Installing Python3 +### Linux +You will be able to install a python3 package in any reputable recent version +of linux using its package manager if it's not already installed. + +### Mac OS X +You can get the official installer from the official Python site: +[https://www.python.org/downloads/mac-osx/]() + +### Windows +You can get the official installer from the official Python site: +[https://www.python.org/downloads/windows/]() + + +## Installing Meson +Ensure you have Python3 correctly installed and can run the command `pip3`. +``` +python3 -m pip install meson +``` + +## Building +In your source directory do the following: +``` +cd keyboardprocessor +meson build --werror +cd build +ninja +meson test +``` + +## Note on kmcomp +kmcomp is the command-line compiler from Keyman Developer, available from https://keyman.com/ or +in this repo in /windows/src/developer/kmcomp. The compiler is currently available as a Windows +PE executable only, but it does run under WINE. + +### Windows +The search path can be edited through System settings / Advanced system settings / +Environment Variables / User environment variables. + +If you have Keyman Developer installed, add %KeymanDeveloperPath% to your path. Otherwise, add +the path where you extracted the kmcomp archive. + +### Linux +You need a wrapper `kmcomp` shell script: + +``` +#!/bin/bash +wine `dirname "$0"`/kmcomp.exe "$@" +``` + +Place this in the same folder as you extracted kmcomp.exe, and `chmod +x kmcomp`. Add the folder +to the path (e.g. `export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`) + +### macOS +TODO \ No newline at end of file diff --git a/common/engine/keyboardprocessor/doc/hotdoc.json b/common/engine/keyboardprocessor/doc/hotdoc.json new file mode 100644 index 0000000000000000000000000000000000000000..61a0028fb0bd464c8e01ff40a8d4b22a32b41d51 --- /dev/null +++ b/common/engine/keyboardprocessor/doc/hotdoc.json @@ -0,0 +1,14 @@ +{ + "project_name": "@project_name@", + "project_version": "@project_version@", + "sitemap": "../../doc/sitemap.txt", + "index": "../../doc/markdown_files/index.md", + "c_sources": [ + "../include/keyboardprocessor.h" + ], + "c_smart_index" : true, + "output": ".", + "extra_assets": [ + "../../assets" + ] +} diff --git a/common/engine/keyboardprocessor/doc/introspection.schema b/common/engine/keyboardprocessor/doc/introspection.schema new file mode 100644 index 0000000000000000000000000000000000000000..ff10c7c96950bdf3f80a9328222c549b1e7536df --- /dev/null +++ b/common/engine/keyboardprocessor/doc/introspection.schema @@ -0,0 +1,62 @@ +{"$schema": "http://json-schema.org/draft-06/schema#", + "$id": "@replace_me_with_introspection_schema_uri@", + "$comment": "© 2018 SIL International. MIT Licensed", + "title": "Keyboard Processor State object introspection", + "description": "Internal data from a Keyboard Processor State object, sufficient for debugging and diagnostics", + "type": "object", + + "definitions": { + "options": { + "type": "object", + "properties": { + "scope": { "enum": ["unknown", "environment", "keyboard"] }, + "options": { + "type": "object", + "default": {} + } + }, + "required": ["scope", "options"] + }, + "rule": { + "type": "object" + }, + "keyboard" : { + "type": "object", + "properties": { + "id": { "type": "string" }, + "version": { "type": "string" }, + "folder": { "type": "string"}, + "options": { "$ref": "#definitions/options" }, + "rules": { + "type":"array", + "items": { "$ref": "#/definitions/rule" } + } + }, + "required": ["id", "version", "folder", "options", "rules"] + }, + "context": { + "type":"array", + "items": {"$ref": "#/definitions/context_item"}, + "default": [] + }, + "context_item": { + "oneOf": [ + {"type": "string", "minLength": 1, "maxLength": 4}, + {"type": "number"} + ] + } + }, + "properties": { + "$schema": { "const": "keyman/keyboardprocessor/doc/introspection.schema" }, + "keyboard": { "$ref": "#/definitions/keyboard" }, + "options": { + "type": "object", + "properties": { + "enviroment": { "$ref": "#/definitions/options" }, + "dynamic": { "$ref": "#/definitions/options" } + }, + "required": [ "enviroment","dynamic"] + }, + "context": { "$ref": "#/definitions/context" } + } +} diff --git a/common/engine/keyboardprocessor/doc/markdown_files/index.md b/common/engine/keyboardprocessor/doc/markdown_files/index.md new file mode 100644 index 0000000000000000000000000000000000000000..b58484289668c44a752a52c27a783099d1373598 --- /dev/null +++ b/common/engine/keyboardprocessor/doc/markdown_files/index.md @@ -0,0 +1,50 @@ +# Keyman Keyboard Processor API + +## Requirements +1. Cross platform. +2. Cross language. +3. Facilitate stateless operation of the Engine. +4. Keyboard format agnostic -- support both KMN and future LDML based keyboards. +5. Support querying Engine attributes. +6. Support querying Keyboard attributes. +7. Idempotent + + +## Design decisions in support of requirements: +- Use C or C99 types and calling convention for the interface, it has the + broadest language FFI support. [1,2] +- Have client (platform glue) code load keyboards, manage & pass state. [3,4,7] +- Provide query calls to return static attributes data for keyboards and + engine [5,6] +- Provide get/set calls for client accessible keyboard state information [3,4] + + +## Glossary +- __Platform layer:__ +the code that consumes the Keyman Keyboard Processor API, and provides the +operating system-specific handling of keystroke events and integration with +applications. +- __Client Application:__ +the application that has the focus and receives text events from the Platform +layer. +- __Context:__ Text preceding the insertion point +- __Marker:__ Positional state that can be placed in the Context. +- __Keyboard:__ A set of rules for execution my an Engine +- __Option:__ A variable in a dynamic or static key value store. +- __Processor:__ +The component that implements this API and can parse and execute a particular +keyboard. +- __State:__ An object that hold internal state of the Processor for a given +insertion point +- __Action:__ +A directive output by the processor detailing how the Platform layer should +transform the Client Application's text buffer. There may be several items +produced by a single keyboard event. +- __Keyboard Event:__ +A virtual key board event and modifier map recevied from the platform to be +processed with the state object for this Client application. + + +## API +### Namespace +All calls, types and enums are prefixed with the namespace identifier `km_kbp_` diff --git a/common/engine/keyboardprocessor/doc/meson.build b/common/engine/keyboardprocessor/doc/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..fceeca03f954a914abb8550b045b3c3aae7e8bbd --- /dev/null +++ b/common/engine/keyboardprocessor/doc/meson.build @@ -0,0 +1,31 @@ +# Copyright: © 2018 SIL International. +# Description: Cross platform build script to extract and generate +# documentation for the API. This is speculative ATM. +# Create Date: 5 Oct 2018 +# Authors: Tim Eves (TSE) +# + +hotdoc = find_program('hotdoc', required: false) +installdir = join_paths(get_option('datadir'), 'doc', meson.project_name()) + +if hotdoc.found() + cfg = configuration_data() + cfg.set('project_name', meson.project_name()) + cfg.set('project_version', meson.project_version()) + configure_file(input: 'hotdoc.json', + output: 'hotdoc.json', + configuration: cfg) + deps = files( + '../include/keyboardprocessor.h.in', + '../src/json.hpp', + '../src/utfcodec.hpp' + ) + + docs = custom_target('docs', + output: ['html'], + input: ['sitemap.txt', 'markdown_files/index.md'], + command: [hotdoc, '--verbose', '--conf-file=doc/hotdoc.json', '--output=doc', 'run'], + depend_files: deps, + install: true, + install_dir: installdir) +endif diff --git a/common/engine/keyboardprocessor/doc/sitemap.txt b/common/engine/keyboardprocessor/doc/sitemap.txt new file mode 100644 index 0000000000000000000000000000000000000000..047dd866e9690967a40cfa1fe4d891b5eb553342 --- /dev/null +++ b/common/engine/keyboardprocessor/doc/sitemap.txt @@ -0,0 +1,2 @@ +index.md + c-index diff --git a/common/engine/keyboardprocessor/include/keyman/keyboardprocessor.h.in b/common/engine/keyboardprocessor/include/keyman/keyboardprocessor.h.in new file mode 100644 index 0000000000000000000000000000000000000000..e27814473427296e81ea9982b11e51a2665c04c6 --- /dev/null +++ b/common/engine/keyboardprocessor/include/keyman/keyboardprocessor.h.in @@ -0,0 +1,984 @@ +/* + Copyright: © 2018 SIL International. + Description: Cross platform API C/C++ declarations for libkmnkbp keyboard + processor. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 18 Oct 2018 - TSE - Finialised verion of API. + 6 Oct 2018 - TSE - Move into keyman folder. + +*/ +#pragma once +/* +# Keyman Keyboard Processor API + +## Requirements +1. Cross platform. +2. Cross language. +3. Facilitate stateless operation of the Engine. +4. Keyboard format agnostic -- support both KMN and future LDML based keyboards. +5. Support querying Engine attributes. +6. Support querying Keyboard attributes. +7. Idempotent + + +## Design decisions in support of requirements: +- Use C or C99 types and calling convention for the interface, it has the + broadest language FFI support. [1,2] +- Have client (Platform layer) code load keyboards, manage & pass state. [3,4,7] +- Provide query calls to return static attributes data for keyboards and + engine [5,6] +- Provide get/set calls for client accessible keyboard state information [3,4] + + +## Glossary +- __Platform layer:__ +The code that consumes the Keyman Keyboard Processor API, and provides the +operating system-specific handling of keystroke events and integration with +applications. +- __Client Application:__ +The application that has the focus and receives text events from the Platform +layer. +- __Context:__ Text preceding the insertion point +- __Marker:__ Positional state that can be placed in the Context. +- __Keyboard:__ A set of rules for execution my an Engine +- __Option:__ A variable in a dynamic or static key value store. +- __Processor:__ +The component that implements this API and can parse and execute a particular +keyboard. +- __State:__ An object that hold internal state of the Processor for a given +insertion point +- __Action:__ +A directive output by the processor detailing how the Platform layer should +transform the Client Application's text buffer. There may be several items +produced by a single keyboard event. +- __Keyboard Event:__ +A virtual key event and modifier map received from the platform to be +processed with the state object for this Client application. +- __Virtual Key:__ +A code based on the US English layout, with values matching the Windows +virtual key codes. See keyboardprocessor_vkeys.h for definitions. +- __Modifier Key:__ +The set of Control, Shift, Alt, Caps Lock keys. On some platforms these may +have other names (e.g. Alt is called Option on macOS); other platform-specific +modifiers such as Windows key are excluded from this set. Some modifiers are +transient, such as Control, and others have long-lasting state, such as +Caps Lock. + +## API +### Namespace +All calls, types and enums are prefixed with the namespace identifier `km_kbp_` + +### API idioms +Almost all calls marshalling variable length aggregate data in or out of an API +object take the form: +> km_kbp_status *fn_name*(object_ref, buffer_ptr, size_ptr) + +where the buffer is nullable and all other arguments are required (will result +in an `KM_KBP_STATUS_INVALID_ARGUMENT` status being returned if nulled). When +`buffer` is `nullptr` or `0` the function will place the size of the required +buffer in the variable pointed to by `size_ptr`. + +Calls which result in the allocation of resources, regardless of resulting +ownership, are of the form: +> km_kbp_status *fn_name*(object_ref, out_ptr) + +where `out_ptr` is a valid pointer to a caller allocated variable to hold the +resulting ouput. This is often a reference to a created object. All arguments +are required (will result in an `KM_KBP_STATUS_INVALID_ARGUMENT` status being +returned if nulled). + +For accessors to fixed size attributes of an object these will take the form: +> attr_value __fn_name__(object_ref) + +`object_ref` is required to be valid and will result in a nonsense value being +returned if `nullptr` or `0`. + +All dispose calls are designed to accept null as a valid value and will do +nothing in that event. +```c +*/ +#include +#include +#include +#include + +#define KM_KBP_LIB_CURRENT @lib_curr@ +#define KM_KBP_LIB_AGE @lib_age@ +#define KM_KBP_LIB_REVISION @lib_rev@ + +#if defined(__cplusplus) +extern "C" +{ +#endif +// Basic types +// +#if defined(__cplusplus) +typedef char16_t km_kbp_cp; +typedef char32_t km_kbp_usv; +#else +typedef uint16_t km_kbp_cp; // code point +typedef uint32_t km_kbp_usv; // Unicode Scalar Value +#endif +typedef uint16_t km_kbp_virtual_key; // A virtual key code. +typedef uint32_t km_kbp_status; // Status return code. + +// Opaque object types. +// +typedef struct km_kbp_context km_kbp_context; +typedef struct km_kbp_keyboard km_kbp_keyboard; +typedef struct km_kbp_state km_kbp_state; +typedef struct km_kbp_options km_kbp_options; + +// Forward declarations +// +typedef struct km_kbp_option_item km_kbp_option_item; + +/*``` +### Error Handling +Error handling and success failure notification are communicated through a +general mechanism similar to COM’s `HRESULT` scheme (unlike COM, any non-zero +value is an error). Any functions that can fail will always return a status +value and all results are returned via outparams passed to the function. +```c +*/ +enum km_kbp_status_codes { + KM_KBP_STATUS_OK = 0, + KM_KBP_STATUS_NO_MEM = 1, + KM_KBP_STATUS_IO_ERROR = 2, + KM_KBP_STATUS_INVALID_ARGUMENT = 3, + KM_KBP_STATUS_KEY_ERROR = 4, + KM_KBP_STATUS_INSUFFICENT_BUFFER = 5, + KM_KBP_STATUS_INVALID_UTF = 6, + KM_KBP_STATUS_INVALID_KEYBOARD = 7, + KM_KBP_STATUS_OS_ERROR = 0x80000000 +}; + +/* +``` +The final status code KM_KBP_STATUS_OS_ERROR is intended to allow encapsulating +a platform error code; the remaining 31 low bits are the error code returned by +the OS for cases where the failure mode is platform specific. For HRESULT codes +this only permits failure codes to be passed. + + +### Context +The context is the text prior to the insertion point (caret, cursor). +The context is constructed by the Platform layer, typically by interrogating the +Client Application. The context will be updated by the engine for keystroke +events. If the Platform layer code caches the context, the context should be +reset when a context state change is detected. Context state changes can occur +when the user uses the mouse to move the insertion point, uses cursor keys, +switches applications or input fields, or presses hotkeys such as Ctrl+N to +start a new document. The full set of context state change triggers is up to the +Platform layer. + +Context can also contain positional Markers (also known as 'deadkeys' in kmn +keyboards), which are transitory state flags that are erased whenever a context +state change is detected. Markers are always controlled by the Engine. + +Contexts are always owned by their state. They may be set to a list of +context_items or interrogated for their current list of context items. +```c +*/ +enum km_kbp_context_type { + KM_KBP_CT_END, + KM_KBP_CT_CHAR, + KM_KBP_CT_MARKER +}; + +typedef struct { + uint8_t type; + uint8_t _reserved[3]; + union { + km_kbp_usv character; + uint32_t marker; + }; +} km_kbp_context_item; + +#define KM_KBP_CONTEXT_ITEM_END {KM_KBP_CT_END, {0,}, {0,}} +/* +``` +### `km_kbp_context_items_from_utf16` +##### Description: +Convert a UTF16 encoded Unicode string into an array of `km_kbp_context_item` +structures. Allocates memory as needed. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated for the + output buffer. +- `KM_KBP_STATUS_INVALID_UTF`: In the event the UTF16 string cannot be decoded + because it contains unpaired surrogate codeunits. +##### Parameters: +- __text__: a pointer to a null terminated array of utf16 encoded data. +- __out_ptr__: a pointer to the result variable: + A pointer to the start of the `km_kbp_context_item` array containing the + representation of the input string. + Terminated with a type of `KM_KBP_CT_END`. Must be disposed of with + `km_kbp_context_items_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_items_from_utf16(km_kbp_cp const *text, + km_kbp_context_item **out_ptr); + +/* +``` +### `km_kbp_context_items_from_utf8` +##### Description: +Convert an UTF8 encoded Unicode string into an array of `km_kbp_context_item` +structures. Allocates memory as needed. +##### Status: +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event it cannot allocate enough memory for the + output buffer. +- `KM_KBP_STATUS_INVALID_UTF`: In the event the UTF8 string cannot be +decoded. +##### Parameters: +- __text__: a pointer to a null terminated array of utf8 encoded data. +- __out_ptr__: a pointer to the result variable: + A pointer to the start of the `km_kbp_context_item` array containing the + representation of the input string. + Terminated with a type of `KM_KBP_CT_END`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_items_from_utf8(char const *text, + km_kbp_context_item **out_ptr); + +/* +``` +### `km_kbp_context_items_to_utf16` +##### Description: +Convert a context item array into a UTF-16 encoded string placing it into +the supplied buffer of specified size, and return the number codepoints +actually used in the conversion. If null is passed as the buffer the +number codeunits required is returned. This will strip markers from the +context during the conversion. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_INSUFFICENT_BUFFER`: If the buffer is not large enough. + `buf_size` will contain the space required. The contents of the buffer are + undefined. +##### Parameters: +- __context_items__: A pointer to the start of an array `km_kbp_context_item`. + Must be terminated with a type of `KM_KBP_CT_END`. +- __buf__: A pointer to the buffer to place the UTF-16 string into. May be be + null to request size calculation. +- __buf_size__: a pointer to the result variable: + A pointer the size of the supplied buffer in codeunits or filled with the + size required if `buf` is null. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_items_to_utf16(km_kbp_context_item const *item, + km_kbp_cp *buf, + size_t *buf_size); + +/* +``` +### `km_kbp_context_items_to_utf8` +##### Description: +Convert a context item array into a UTF-8 encoded string placing it into +the supplied buffer of specified size, and return the number codepoints +actually used in the conversion. If null is passed as the buffer the +number codeunits required is returned. This will strip markers from the +context during the conversion. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_INSUFFICENT_BUFFER`: If the buffer is not large enough. + `buf_size` will contain the space required. The contents of the buffer are + undefined. +##### Parameters: +- __context_items__: A pointer to the start of an array `km_kbp_context_item`. + Must be terminated with a type of `KM_KBP_CT_END`. +- __buf__: A pointer to the buffer to place the UTF-8 string into. May be be + null to request size calculation. +- __buf_size__: a pointer to the result variable: + A pointer the size of the supplied buffer in codeunits or filled with the + size required if `buf` is null. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_items_to_utf8(km_kbp_context_item const *item, + char *buf, + size_t *buf_size); + +/* +``` +### `km_kbp_context_items_dispose` +##### Description: +Free the allocated memory belonging to a `km_kbp_context_item` array previously +returned by `km_kbp_context_items_from_utf16` or `km_kbp_context_get` +##### Parameters: +- __context_items__: A pointer to the start of the `km_kbp_context_item` array + to be disposed of. + +```c +*/ +KMN_API +void +km_kbp_context_items_dispose(km_kbp_context_item *context_items); + +/* +``` +### `km_kbp_context_set` +##### Description: +Replace the contents of the current context with a new sequence of +`km_kbp_context_item` entries. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated to + grow the context buffer internally. +##### Parameters: +- __context__: A pointer to an opaque context object +- __context_items__: A pointer to the start of the `km_kbp_context_item` + array containing the new context. It must be terminated with an item + of type `KM_KBP_CT_END`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_set(km_kbp_context *context, + km_kbp_context_item const *context_items); + +/* +``` +### `km_kbp_context_get` +##### Description: +Copies all items in the context into a new array and returns the new array. +This must be disposed of by caller using `km_kbp_context_items_dispose`. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated for the + output buffer. +##### Parameters: +- __context_items__: A pointer to the start of an array `km_kbp_context_item`. +- __out__: a pointer to the result variable: + A pointer to the start of the `km_kbp_context_item` array containing a + copy of the context. Terminated with a type of `KM_KBP_CT_END`. Must be + disposed of with `km_kbp_context_items_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_get(km_kbp_context const *context_items, + km_kbp_context_item **out); + +/* +``` +### `km_kbp_context_clear` +##### Description: +Removes all context_items from the internal array. If `context` is +null, has no effect. +##### Parameters: +- __context__: A pointer to an opaque context object + +```c +*/ +KMN_API +void +km_kbp_context_clear(km_kbp_context *); + +/* +``` +### `km_kbp_context_length` +##### Description: +Return the number of items in the context. +##### Return: +The number of items in the context, and will return 0 if passed a null `context` +pointer. +##### Parameters: +- __context__: A pointer to an opaque context object + +```c +*/ +KMN_API +size_t +km_kbp_context_length(km_kbp_context *); + +/* +``` +### `km_kbp_context_append` +##### Description: +Add more items to the end (insertion point) of the context. If these exceed the +maximum context length the same number of items will be dropped from the +beginning of the context. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated to + grow the context buffer internally. +##### Parameters: +- __context__: A pointer to an opaque context object. +- __context_items__: A pointer to the start of the `KM_KBP_CT_END` terminated + array of `km_kbp_context_item` to append. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_append(km_kbp_context *context, + km_kbp_context_item const *context_items); + +/* +``` +### `km_kbp_context_shrink` +##### Description: +Remove a specified number of items from the end of the context, optionally +add up to the same number of the supplied items to the front of the context. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: in the event it cannot allocated enough memory to grow + the context internally. +##### Parameters: +- __context__: A pointer to an opaque context object. +- __num__: The number of items to remove from the end of context. +- __context_items__: Pointer to the start of the `KM_KBP_CT_END` terminated + array of `km_kbp_context_item` to add to the front. Up to `num` items will + be prepended. This may be null if not required. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_shrink(km_kbp_context *context, + size_t num, + km_kbp_context_item const *prefix); + +/* +``` +### Action Items +These provide the results of processing a key event to the Platform layer and +should be processed by the Platform layer to issue commands to the os text +services framework to transform the text store in the Client Application, among +other actions. +```c +*/ +typedef struct { + uint8_t type; + uint8_t _reserved[sizeof(void*)-sizeof(uint8_t)]; + union { + uintptr_t marker; // MARKER type + km_kbp_option_item const * option; // OPT types + km_kbp_usv character; // CHAR type + km_kbp_virtual_key vkey; // VKEY types + size_t erased; // BACK type + }; +} km_kbp_action_item; + +enum km_kbp_action_type { + KM_KBP_IT_END = 0, // Marks end of action items list. + KM_KBP_IT_CHAR = 1, // A Unicode character has been generated. + KM_KBP_IT_MARKER = 2, // Correlates to kmn's "deadkey" markers. + KM_KBP_IT_ALERT = 3, // The keyboard has triggered a alert/beep/bell. + KM_KBP_IT_BACK = 4, // Delete the codepoint preceding the insertion point. + KM_KBP_IT_PERSIST_OPT = 5, // The indicated option needs to be stored. + KM_KBP_IT_EMIT_KEYSTROKE = 6, // Emit the current keystroke to the application + KM_KBP_IT_INVALIDATE_CONTEXT = 7, + // The processor requests that the context buffer be cleared; + // for applications where context is cached, this clears the context; + // for applications where context is read from the focused text store, + // the context is just re-read and markers flushed. + KM_KBP_IT_MAX_TYPE_ID +}; + + +/* +``` +### Options +A state’s default options are set from the keyboard at creation time and the +environment. The Platform layer is then is expected to apply any persisted +options it is maintaining. Options are passed into and out of API functions as +simple C arrays of `km_kbp_option_item` terminated with a `KM_KBP_OPTIONS_END` +sentinel value. A state's options are exposed and manipulatable via the +`km_kbp_options` API. All option values are of type C string. + +During processing when the Platform layer finds a PERSIST action type it should +store the updated option in the appropriate place, based on its scope. +For RESET the processor will apply the pristine value from the original scope, +the Platform layer should update that only if it manages a previously persisted +value. +```c +*/ + +enum km_kbp_option_scope { + KM_KBP_OPT_UNKNOWN = 0, + KM_KBP_OPT_KEYBOARD = 1, + KM_KBP_OPT_ENVIRONMENT = 2, + KM_KBP_OPT_MAX_SCOPES +}; + +struct km_kbp_option_item { + km_kbp_cp const * key; + km_kbp_cp const * value; + uint8_t scope; // Scope which an option belongs to. +}; + +#define KM_KBP_OPTIONS_END { 0, 0, 0 } + + +/* +``` +### `km_kbp_options_list_size` +##### Description: +Return the length of a terminated `km_kbp_option_item` array (options +list). +##### Return: +The number of items in the list or 0 if `opts` is null. +##### Parameters: +- __opts__: A pointer to a `KM_KBP_OPTIONS_END` terminated array of + `km_kbp_option_item` values. + +```c +*/ +KMN_API +size_t +km_kbp_options_list_size(km_kbp_option_item const *opts); + +/* +``` +### `km_kbp_options_lookup` +##### Description: +Lookup an option based on its key, in an options list. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null, or + if the scope is invalid. +- `KM_KBP_STATUS_KEY_ERROR`: The key cannot be found. +##### Parameters: +- __state__: An opaque pointer to a state object. +- __scope__: Which key-value store to interrogate. +- __key__: A UTF-16 string that matches the key in the target `km_kbp_option_item`. +- __value__: A pointer to the result variable: + A pointer to a copy of the UTF-16 string value; undefined if return status + is anything other than `KM_KBP_STATUS_OK`. This memory must be disposed of + with `km_kbp_cp_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_options_lookup(km_kbp_state const *state, + uint8_t scope, + km_kbp_cp const *key, + km_kbp_cp const **value); + +/* +``` +### `km_kbp_options_update` +##### Description: +Adds or updates one or more options from a list of `km_kbp_option_item`s. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. +- `KM_KBP_STATUS_KEY_ERROR`: The key cannot be found. +##### Parameters: +- __state__: An opaque pointer to a `km_kbp_state`. +- __new_opts__: An array of `km_kbp_option_item` objects to update or add. Must be + terminated with `KM_KBP_OPTIONS_END`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_options_update(km_kbp_state *state, + km_kbp_option_item const *new_opts); + +/* +``` +### `km_kbp_options_to_json` +##### Description: +Export the contents of a `km_kbp_options` array to a JSON formatted document and +place it in the supplied buffer, reporting how much space was used. If null is +passed as the buffer the number of bytes required is returned in `space`. If +there is insufficent space to hold the document the contents of the buffer is +undefined. The returned buffer uses UTF-8 encoding. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. +##### Parameters: +- __opts__: An opaque pointer to an options object. +- __buf__: A pointer to the buffer to place the C string containing the JSON +document into, can be null. +- __space__: A pointer to a size_t variable. This variable must contain the +number of bytes available in the buffer pointed to by `buf`, unless `buf` is +null. On return it will hold how many bytes were used. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_options_to_json(km_kbp_options const *opts, + char *buf, + size_t *space); + + +/* +``` +### Keyboards +A keyboard is a set of rules and transforms in a Processor specific format for +transforming key events into action items. The keyboard is parsed and loaded by +the processsor and made available in an immutable fashion for use with any number +of state objects. +```c +*/ +typedef struct { + km_kbp_cp const * version_string; // Processor specific version string. + km_kbp_cp const * id; // Keyman keyboard ID string. + km_kbp_path_name folder_path; // Path to the unpacked folder containing + // the keyboard and associated resources. + km_kbp_option_item const * default_options; +} km_kbp_keyboard_attrs; + +/* +``` +### `km_kbp_keyboard_load` +##### Description: +Parse and load keyboard from the supplied path and a pointer to the loaded keyboard +into the out paramter. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. +- `KM_KBP_STATUS_IO_ERROR`: + In the event the keyboard file is unparseable for any reason +- `KM_KBP_STATUS_INVALID_ARGUMENT`: + In the event the file doesn't exist or is inaccesible or `keyboard` is null. +- `KM_KBP_STATUS_OS_ERROR`: Bit 31 (high bit) set, bits 0-30 are an OS-specific + error code. +##### Parameters: +- __kb_path__: On Windows, a UTF-16 string; on other platforms, a C string: + contains a valid path to the keyboard file. +- __keyboard__: A pointer to result variable: + A pointer to the opaque keyboard object returned by the Processor. This + memory must be freed with a call to `km_kbp_keyboard_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_keyboard_load(km_kbp_path_name kb_path, + km_kbp_keyboard **keyboard); + +/* +``` +### `km_kbp_keyboard_dispose` +##### Description: +Free the allocated memory belonging to an opaque keyboard object previously +returned by `km_kbp_keyboard_load`. +##### Parameters: +- __keyboard__: A pointer to the opaque keyboard object to be + disposed of. + +```c +*/ +KMN_API +void +km_kbp_keyboard_dispose(km_kbp_keyboard *keyboard); + +/* +``` +### `km_kbp_keyboard_get_attrs` +##### Description: +Returns the const internal attributes of the keyboard. This structure is valid +for the lifetime of the opaque keyboard object. Do not modify the returned data. +##### 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 the result: + A pointer to a `km_kbp_keyboard_attrs` structure. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, + km_kbp_keyboard_attrs const **out); + +/* +``` +### State +A State object maintains all per keyboard related state including context +and dynamic options ("option stores" in kmn format). + +```c +*/ + +/* +``` +### `km_kbp_state_create` +##### Description: +Create a keyboard processor state object, maintaining state for the keyboard in +the environment passed. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: + In the event memory is unavailable to allocate a state object. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: + In the event the `keyboard` or `out` pointer are null. +##### Parameters: +- __keyboard__: +A pointer to the opaque keyboard object this object will hold state for. +- __env__: +The array of `km_kbp_option_item` key/value pairs used to initialise the +environment, terminated by `KM_KBP_OPTIONS_END`. +- __out__: +A pointer to result variable: A pointer to the opaque state object +returned by the Processor, initalised to maintain state for `keyboard`. +This must be disposed of by a call to `km_kbp_state_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_state_create(km_kbp_keyboard const *keyboard, + km_kbp_option_item const *env, + km_kbp_state **out); + +/* +``` +### `km_kbp_state_clone` +##### Description: +Clone an existing opaque state object. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: +In the event memory is unavailable to allocate a state object. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: +In the event the `state` or `out` pointer are null. +##### Parameters: +- __state__: +A pointer to the opaque statea object to be cloned. +- __out__: +A pointer to result variable: A pointer to the opaque state object +returned by the Processor, cloned from the existing object `state`. This +must be disposed of by a call to `km_kbp_state_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_state_clone(km_kbp_state const *state, + km_kbp_state **out); + +/* +``` +### `km_kbp_state_dispose` +##### Description: +Free the allocated resources belonging to a `km_kbp_state` object previously +returned by `km_kbp_state_create` or `km_kbp_state_clone`. After this all +pointers previously returned by any km_kbp_state family of calls will become +invalid. +##### Parameters: +- __state__: A pointer to the opaque state object to be disposed. + +```c +*/ +KMN_API +void +km_kbp_state_dispose(km_kbp_state *state); + +/* +``` +### `km_kbp_state_context` +##### Description: +Get access to the state object's context. +##### Return: +A pointer to an opaque state object. This pointer is valid for the lifetime +of the state object. If null is passed in, then null is returned. +##### Parameters: +- __state__: A pointer to the opaque state object to be queried. + +```c +*/ +KMN_API +km_kbp_context * +km_kbp_state_context(km_kbp_state *state); + +/* +``` +### `km_kpb_state_options` +##### Description: +Get access to the state object's options. +##### Return: +A pointer to an opaque state object. This pointer is valid for the lifetime +of the state object. If null is passed in, then null is returned. +##### Parameters: +- __state__: A pointer to the opaque state object to be queried. + +```c +*/ +KMN_API +km_kbp_options * +km_kbp_state_options(km_kbp_state *state); + +/* +``` +### `km_kbp_state_action_items` +##### Description: +Get the list of action items generated by the last call to +`km_kbp_process_event`. +##### Return: +A pointer to a `km_kbp_action_item` list, of `*num_items` in length. This data +becomes invalid when the state object is destroyed, or after a call to +`km_kbp_process_event`. Do not modify the contents of this data. The returned +array is terminated with a `KM_KBP_IT_END` entry. +##### Parameters: +- __state__: A pointer to the opaque `km_kbp_state` object to be queried. +- __num_items__: +A pointer to a result variable: The number of items in the action item list +including the `KM_KBP_IT_END` terminator. May be null if not that +information is required. + +```c +*/ +KMN_API +km_kbp_action_item const * +km_kbp_state_action_items(km_kbp_state const *state, + size_t *num_items); + +/* +``` +### `km_kpb_state_to_json` +##### Description: +Export the internal state of a `km_kbp_state` object to a JSON format document +and place it in the supplied buffer, reporting how much space was used. If null +is passed as the buffer the number of bytes required is returned. If there is +insufficent space to hold the document, the contents of the buffer is undefined. +The encoding of the returned data is UTF-8. + +__WARNING__: The structure and format of the JSON document while independently +versioned is not part of this API and is intended solely for use in diagnostics +or by development and debugging tools which are aware of processor +implementation details. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. +##### Parameters: +- __state__: An pointer to an opaque state object. +- __buf__: A pointer to the buffer to place the C string containing the JSON +document into. May be null. +- __space__: A pointer to a size_t variable. This variable must contain the +number of bytes available in the buffer pointed to by `buf`, unless `buf` is +null. On return it will hold how many bytes were used. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_state_to_json(km_kbp_state const *state, + char *buf, + size_t *space); + +/* +``` +### Processor +```c +*/ +typedef struct { + size_t max_context; // Maximum context size supported by processor. + uint16_t current; // Current API number supported. + uint16_t revision; // Implementation number of current API. + uint16_t age; // current - age == Oldest API number supported. + uint16_t technology; // A bit field specifiying which Keyboard + // technologies the engine supports. + char const *vendor; // Implementor of the processor. +} km_kbp_attr; + +enum km_kbp_tech_value { + KM_KBP_TECH_UNSPECIFIED = 0, + KM_KBP_TECH_MOCK = 1 << 0, + KM_KBP_TECH_KMX = 1 << 1, + KM_KBP_TECH_LDML = 1 << 2 +}; + + +/* +``` +### `km_kbp_get_engine_attrs` +##### Description: +Get access processors attributes describing version and technology implemented. +##### Return: +A pointer to a `km_kbp_attr` structure. Do not modify the contents of this +structure. +##### Parameters: +- __state__: An opaque pointer to an `km_kbp_state`. +```c +*/ +KMN_API +km_kbp_attr const * +km_kbp_get_engine_attrs(km_kbp_state const *state); + +/* +``` +### `km_kbp_cp_dispose` +##### Description: +Free the allocated memory belonging to a `km_kbp_cp` array previously +returned by `km_kbp_options_lookup`. +##### Parameters: +- __cp__: A pointer to the start of the `km_kbp_cp` array + to be disposed of. + +```c +*/ +KMN_API +void +km_kbp_cp_dispose(km_kbp_cp const *cp); + +/* +``` +### `km_kbp_process_event` +##### Description: +Run the keyboard on an opaque state object with the provided virtual key and modifer +key state. Updates the state object as appropriate and fills out its action list. + +The action list will be cleared at the start of this call; options and context in +the state may also be modified. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: +In the event memory is unavailable to allocate internal buffers. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: +In the event the `state` pointer is null or an invalid virtual key or modifier +state is passed. + +##### Parameters: +- __state__: A pointer to the opaque state object. +- __vk__: A virtual key to be processed. +- __modifier_state__: +The combinations of modifier keys set at the time key `vk` was pressed, bitmask +from the `km_kbp_modifier_state` enum. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_process_event(km_kbp_state *state, + km_kbp_virtual_key vk, + uint16_t modifier_state); + +#if defined(__cplusplus) +} // extern "C" +#endif +/*``` +*/ diff --git a/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_bits.h b/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_bits.h new file mode 100644 index 0000000000000000000000000000000000000000..ac34757dbaa5daa20e4011444c8a5c1d972ef9c3 --- /dev/null +++ b/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_bits.h @@ -0,0 +1,56 @@ +/* + Copyright: © 2018 SIL International. + Description: Cross platform support macros to define API function + declspec/attributes and how each supported compiler or OS + allows us to specify them. + Create Date: 18 Oct 2018 + Authors: Tim Eves (TSE) + History: 18 Oct 2018 - TSE - Imported from graphite2 project. + 6 Oct 2018 - TSE - Move into keyman folder. + +*/ +#pragma once + +// Define API function declspec/attributes and how each supported compiler or OS +// allows us to specify them. +#if defined __GNUC__ + #define _kmn_and , + #define _kmn_tag_fn(a) __attribute__((a)) + #define _kmn_deprecated_flag deprecated + #define _kmn_export_flag visibility("default") + #define _kmn_import_flag visibility("default") + #define _kmn_static_flag visibility("hidden") + #define _kmn_unused(x) UNUSED_ ## x __attribute__((__unused__)) +#else + #define _kmn_unused(x) UNUSED_ ## x + +#endif + +#if defined _WIN32 || defined __CYGWIN__ + typedef wchar_t const * km_kbp_path_name; + #if defined __GNUC__ // These three will be redefined for Windows + #undef _kmn_export_flag + #undef _kmn_import_flag + #undef _kmn_static_flag + #else // How MSVC sepcifies function level attributes adn deprecation + #define _kmn_and + #define _kmn_tag_fn(a) __declspec(a) + #define _kmn_deprecated_flag deprecated + #endif + #define _kmn_export_flag dllexport + #define _kmn_import_flag dllimport + #define _kmn_static_flag +#else + typedef char const * km_kbp_path_name; +#endif + +#if defined KMN_KBP_STATIC + #define KMN_API _kmn_tag_fn(_kmn_static_flag) + #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_static_flag) +#elif defined KMN_KBP_EXPORTING + #define KMN_API _kmn_tag_fn(_kmn_export_flag) + #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_export_flag) +#else + #define KMN_API _kmn_tag_fn(_kmn_import_flag) + #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_import_flag) +#endif diff --git a/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_consts.h b/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_consts.h new file mode 100644 index 0000000000000000000000000000000000000000..1d0936610b390c01ff64c67fee68cc44aeb37e38 --- /dev/null +++ b/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_consts.h @@ -0,0 +1,9 @@ +#pragma once + +// Defined environment options for KMX processor +#define KM_KBP_KMX_ENV_PLATFORM u"platform" +#define KM_KBP_KMX_ENV_BASELAYOUT u"baseLayout" +#define KM_KBP_KMX_ENV_BASELAYOUTALT u"baseLayoutAlt" +#define KM_KBP_KMX_ENV_SIMULATEALTGR u"simulateAltgr" +#define KM_KBP_KMX_ENV_CAPSLOCK u"capsLock" +#define KM_KBP_KMX_ENV_BASELAYOUTGIVESCTRLRALTFORRALT u"baseLayoutGivesCtrlRAltForRAlt" diff --git a/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_vkeys.h b/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_vkeys.h new file mode 100644 index 0000000000000000000000000000000000000000..b958a5afd42e0d6a3752a0322c642bb3d049015c --- /dev/null +++ b/common/engine/keyboardprocessor/include/keyman/keyboardprocessor_vkeys.h @@ -0,0 +1,304 @@ +/* + Copyright: © 2018 SIL International. + Description: API declarations for modifier keys, handy access masks and + Keyman VKEY names. These follow the same keytop->code + associations as the Windows API. This is a separate header to + maintain readability of the primary API header. + Create Date: 17 Oct 2018 + Authors: Tim Eves (TSE) + History: 17 Oct 2018 - TSE - Moved & refactored km_kbp_modifier_state + from keyboardprocessor.h. + - Added VKey and mask definitions. + 6 Oct 2018 - TSE - Move into keyman folder. + +*/ +enum km_kbp_modifier_state { + KM_KBP_MODIFIER_LCTRL = 1 << 0, + KM_KBP_MODIFIER_RCTRL = 1 << 1, + KM_KBP_MODIFIER_LALT = 1 << 2, + KM_KBP_MODIFIER_RALT = 1 << 3, + KM_KBP_MODIFIER_SHIFT = 1 << 4, + KM_KBP_MODIFIER_CTRL = 1 << 5, + KM_KBP_MODIFIER_ALT = 1 << 6 + /* + KM_KBP_MODIFIER_CAPS = 1 << 7, + KM_KBP_MODIFIER_NOCAPS = 1 << 8, + KM_KBP_MODIFIER_NUMLOCK = 1 << 9, + KM_KBP_MODIFIER_NONUMLOCK = 1 << 10, + KM_KBP_MODIFIER_SCROLLOCK = 1 << 11, + KM_KBP_MODIFIER_NOSCROLLOCK = 1 << 12, + KM_KBP_MODIFIER_VIRTUALKEY = 1 << 13, + */ +}; + + +enum km_kbp_modifier_mask { + KM_KBP_MODIFIER_MASK_ALL = 0x7f, + KM_KBP_MODIFIER_MASK_ALT_GR_SIM = KM_KBP_MODIFIER_LCTRL|KM_KBP_MODIFIER_LALT, + KM_KBP_MODIFIER_MASK_CHIRAL = 0x1f, + KM_KBP_MODIFIER_MASK_IS_CHIRAL = 0x0f, + KM_KBP_MODIFIER_MASK_NON_CHIRAL = 0x7f +/* KM_KBP_MODIFIER_MASK_CAPS = 0x0300, + KM_KBP_MODIFIER_MASK_NUMLOCK = 0x0C00, + KM_KBP_MODIFIER_MASK_SCROLLLOCK = 0x3000,*/ +}; + +// These are Windows API VKEYs, using Keyman VKEY names. +enum km_kpb_virtual_key { + KM_KBP_VKEY__00, + KM_KBP_VKEY_LBUTTON, + KM_KBP_VKEY_RBUTTON, + KM_KBP_VKEY_CANCEL, + KM_KBP_VKEY_MBUTTON, + KM_KBP_VKEY__05, + KM_KBP_VKEY__06, + KM_KBP_VKEY__07, + KM_KBP_VKEY_BKSP, + KM_KBP_VKEY_TAB, + KM_KBP_VKEY__0A, + KM_KBP_VKEY__0B, + KM_KBP_VKEY_KP5, + KM_KBP_VKEY_ENTER, + KM_KBP_VKEY__0E, + KM_KBP_VKEY__0F, + KM_KBP_VKEY_SHIFT, + KM_KBP_VKEY_CONTROL, + KM_KBP_VKEY_ALT, + KM_KBP_VKEY_PAUSE, + KM_KBP_VKEY_CAPS, + KM_KBP_VKEY__15, + KM_KBP_VKEY__16, + KM_KBP_VKEY__17, + KM_KBP_VKEY__18, + KM_KBP_VKEY__19, + KM_KBP_VKEY__1A, + KM_KBP_VKEY_ESC, + KM_KBP_VKEY__1C, + KM_KBP_VKEY__1D, + KM_KBP_VKEY__1E, + KM_KBP_VKEY__1F, + KM_KBP_VKEY_SPACE, + KM_KBP_VKEY_PGUP, + KM_KBP_VKEY_PGDN, + KM_KBP_VKEY_END, + KM_KBP_VKEY_HOME, + KM_KBP_VKEY_LEFT, + KM_KBP_VKEY_UP, + KM_KBP_VKEY_RIGHT, + KM_KBP_VKEY_DOWN, + KM_KBP_VKEY_SEL, + KM_KBP_VKEY_PRINT, + KM_KBP_VKEY_EXEC, + KM_KBP_VKEY_PRTSCN, + KM_KBP_VKEY_INS, + KM_KBP_VKEY_DEL, + KM_KBP_VKEY_HELP, + KM_KBP_VKEY_0, + KM_KBP_VKEY_1, + KM_KBP_VKEY_2, + KM_KBP_VKEY_3, + KM_KBP_VKEY_4, + KM_KBP_VKEY_5, + KM_KBP_VKEY_6, + KM_KBP_VKEY_7, + KM_KBP_VKEY_8, + KM_KBP_VKEY_9, + KM_KBP_VKEY__3A, + KM_KBP_VKEY__3B, + KM_KBP_VKEY__3C, + KM_KBP_VKEY__3D, + KM_KBP_VKEY__3E, + KM_KBP_VKEY__3F, + KM_KBP_VKEY__40, + KM_KBP_VKEY_A, + KM_KBP_VKEY_B, + KM_KBP_VKEY_C, + KM_KBP_VKEY_D, + KM_KBP_VKEY_E, + KM_KBP_VKEY_F, + KM_KBP_VKEY_G, + KM_KBP_VKEY_H, + KM_KBP_VKEY_I, + KM_KBP_VKEY_J, + KM_KBP_VKEY_K, + KM_KBP_VKEY_L, + KM_KBP_VKEY_M, + KM_KBP_VKEY_N, + KM_KBP_VKEY_O, + KM_KBP_VKEY_P, + KM_KBP_VKEY_Q, + KM_KBP_VKEY_R, + KM_KBP_VKEY_S, + KM_KBP_VKEY_T, + KM_KBP_VKEY_U, + KM_KBP_VKEY_V, + KM_KBP_VKEY_W, + KM_KBP_VKEY_X, + KM_KBP_VKEY_Y, + KM_KBP_VKEY_Z, + KM_KBP_VKEY__5B, + KM_KBP_VKEY__5C, + KM_KBP_VKEY__5D, + KM_KBP_VKEY__5E, + KM_KBP_VKEY__5F, + KM_KBP_VKEY_NP0, + KM_KBP_VKEY_NP1, + KM_KBP_VKEY_NP2, + KM_KBP_VKEY_NP3, + KM_KBP_VKEY_NP4, + KM_KBP_VKEY_NP5, + KM_KBP_VKEY_NP6, + KM_KBP_VKEY_NP7, + KM_KBP_VKEY_NP8, + KM_KBP_VKEY_NP9, + KM_KBP_VKEY_NPSTAR, + KM_KBP_VKEY_NPPLUS, + KM_KBP_VKEY_SEPARATOR, + KM_KBP_VKEY_NPMINUS, + KM_KBP_VKEY_NPDOT, + KM_KBP_VKEY_NPSLASH, + KM_KBP_VKEY_F1, + KM_KBP_VKEY_F2, + KM_KBP_VKEY_F3, + KM_KBP_VKEY_F4, + KM_KBP_VKEY_F5, + KM_KBP_VKEY_F6, + KM_KBP_VKEY_F7, + KM_KBP_VKEY_F8, + KM_KBP_VKEY_F9, + KM_KBP_VKEY_F10, + KM_KBP_VKEY_F11, + KM_KBP_VKEY_F12, + KM_KBP_VKEY_F13, + KM_KBP_VKEY_F14, + KM_KBP_VKEY_F15, + KM_KBP_VKEY_F16, + KM_KBP_VKEY_F17, + KM_KBP_VKEY_F18, + KM_KBP_VKEY_F19, + KM_KBP_VKEY_F20, + KM_KBP_VKEY_F21, + KM_KBP_VKEY_F22, + KM_KBP_VKEY_F23, + KM_KBP_VKEY_F24, + KM_KBP_VKEY__88, + KM_KBP_VKEY__89, + KM_KBP_VKEY__8A, + KM_KBP_VKEY__8B, + KM_KBP_VKEY__8C, + KM_KBP_VKEY__8D, + KM_KBP_VKEY__8E, + KM_KBP_VKEY__8F, + KM_KBP_VKEY_NUMLOCK, + KM_KBP_VKEY_SCROLL, + KM_KBP_VKEY__92, + KM_KBP_VKEY__93, + KM_KBP_VKEY__94, + KM_KBP_VKEY__95, + KM_KBP_VKEY__96, + KM_KBP_VKEY__97, + KM_KBP_VKEY__98, + KM_KBP_VKEY__99, + KM_KBP_VKEY__9A, + KM_KBP_VKEY__9B, + KM_KBP_VKEY__9C, + KM_KBP_VKEY__9D, + KM_KBP_VKEY__9E, + KM_KBP_VKEY__9F, + KM_KBP_VKEY__A0, + KM_KBP_VKEY__A1, + KM_KBP_VKEY__A2, + KM_KBP_VKEY__A3, + KM_KBP_VKEY__A4, + KM_KBP_VKEY__A5, + KM_KBP_VKEY__A6, + KM_KBP_VKEY__A7, + KM_KBP_VKEY__A8, + KM_KBP_VKEY__A9, + KM_KBP_VKEY__AA, + KM_KBP_VKEY__AB, + KM_KBP_VKEY__AC, + KM_KBP_VKEY__AD, + KM_KBP_VKEY__AE, + KM_KBP_VKEY__AF, + KM_KBP_VKEY__B0, + KM_KBP_VKEY__B1, + KM_KBP_VKEY__B2, + KM_KBP_VKEY__B3, + KM_KBP_VKEY__B4, + KM_KBP_VKEY__B5, + KM_KBP_VKEY__B6, + KM_KBP_VKEY__B7, + KM_KBP_VKEY__B8, + KM_KBP_VKEY__B9, + KM_KBP_VKEY_COLON, + KM_KBP_VKEY_EQUAL, + KM_KBP_VKEY_COMMA, + KM_KBP_VKEY_HYPHEN, + KM_KBP_VKEY_PERIOD, + KM_KBP_VKEY_SLASH, + KM_KBP_VKEY_BKQUOTE, + KM_KBP_VKEY__C1, + KM_KBP_VKEY__C2, + KM_KBP_VKEY__C3, + KM_KBP_VKEY__C4, + KM_KBP_VKEY__C5, + KM_KBP_VKEY__C6, + KM_KBP_VKEY__C7, + KM_KBP_VKEY__C8, + KM_KBP_VKEY__C9, + KM_KBP_VKEY__CA, + KM_KBP_VKEY__CB, + KM_KBP_VKEY__CC, + KM_KBP_VKEY__CD, + KM_KBP_VKEY__CE, + KM_KBP_VKEY__CF, + KM_KBP_VKEY__D0, + KM_KBP_VKEY__D1, + KM_KBP_VKEY__D2, + KM_KBP_VKEY__D3, + KM_KBP_VKEY__D4, + KM_KBP_VKEY__D5, + KM_KBP_VKEY__D6, + KM_KBP_VKEY__D7, + KM_KBP_VKEY__D8, + KM_KBP_VKEY__D9, + KM_KBP_VKEY__DA, + KM_KBP_VKEY_LBRKT, + KM_KBP_VKEY_BKSLASH, + KM_KBP_VKEY_RBRKT, + KM_KBP_VKEY_QUOTE, + KM_KBP_VKEY_oDF, + KM_KBP_VKEY_oE0, + KM_KBP_VKEY_oE1, + KM_KBP_VKEY_oE2, // 102nd key on European layouts + KM_KBP_VKEY_oE3, + KM_KBP_VKEY_oE4, + KM_KBP_VKEY__E5, + KM_KBP_VKEY_oE6, + KM_KBP_VKEY__E7, + KM_KBP_VKEY__E8, + KM_KBP_VKEY_oE9, + KM_KBP_VKEY_oEA, + KM_KBP_VKEY_oEB, + KM_KBP_VKEY_oEC, + KM_KBP_VKEY_oED, + KM_KBP_VKEY_oEE, + KM_KBP_VKEY_oEF, + KM_KBP_VKEY_oF0, + KM_KBP_VKEY_oF1, + KM_KBP_VKEY_oF2, + KM_KBP_VKEY_oF3, + KM_KBP_VKEY_oF4, + KM_KBP_VKEY_oF5, + KM_KBP_VKEY__F6, + KM_KBP_VKEY__F7, + KM_KBP_VKEY__F8, + KM_KBP_VKEY__F9, + KM_KBP_VKEY__FA, + KM_KBP_VKEY__FB, + KM_KBP_VKEY__FC, + KM_KBP_VKEY__FD, + KM_KBP_VKEY__FE, + KM_KBP_VKEY__FF, +}; diff --git a/common/engine/keyboardprocessor/include/keyman/meson.build b/common/engine/keyboardprocessor/include/keyman/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..5e5f127fef9073487de6bc7ad37b24685a10b748 --- /dev/null +++ b/common/engine/keyboardprocessor/include/keyman/meson.build @@ -0,0 +1,25 @@ +# Copyright: © 2018 SIL International. +# Description: Cross platform build script to generate installable API header +# files with versioning information templated in. +# Create Date: 2 Oct 2018 +# Authors: Tim Eves (TSE) +# History: 6 Oct 2018 - TSE - Move into keyman folder. +# + +ver = meson.project_version().split('.') + +cfg = configuration_data() +cfg.set('lib_curr', ver[0]) +cfg.set('lib_age', ver[1]) +cfg.set('lib_rev', ver[2]) + +configure_file( + configuration: cfg, + input: 'keyboardprocessor.h.in', + output: 'keyboardprocessor.h', +) + +install_headers(join_paths(meson.current_build_dir(), 'keyboardprocessor.h'), + 'keyboardprocessor_vkeys.h', + 'keyboardprocessor_bits.h', + subdir: 'keyman') diff --git a/common/engine/keyboardprocessor/include/meson.build b/common/engine/keyboardprocessor/include/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..b6211db0f220397cadb866e62c098135b2ddcbdb --- /dev/null +++ b/common/engine/keyboardprocessor/include/meson.build @@ -0,0 +1,10 @@ +# Copyright: © 2018 SIL International. +# Description: Cross platform build script to generate installable API header +# files with versioning information templated in. +# Create Date: 2 Oct 2018 +# Authors: Tim Eves (TSE) +# History: 6 Oct 2018 - TSE - Move into keyman folder. +# + +inc = include_directories('.', is_system: true) +subdir('keyman') diff --git a/common/engine/keyboardprocessor/meson.build b/common/engine/keyboardprocessor/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..414578ed9f69a67e2043551b6d35bdfc8e81e504 --- /dev/null +++ b/common/engine/keyboardprocessor/meson.build @@ -0,0 +1,26 @@ +# Copyright: © 2018 SIL International. +# Description: Cross platform build script to compile libkmnkbp, documentation +# and tests. +# Create Date: 2 Oct 2018 +# Authors: Tim Eves (TSE) +# + +project('keyboardprocessor', 'cpp', 'c', + version: '0.0.0', + license: 'MIT', + default_options : ['buildtype=release', + 'cpp_std=c++14']) + +compiler = meson.get_compiler('cpp') + +if compiler.get_id() == 'msvc' + add_global_arguments('/source-charset:utf-8', language: ['c', 'cpp']) +endif + +py = import('python3') +python = py.find_python() + +subdir('doc') +subdir('include') +subdir('src') +subdir('tests') diff --git a/common/engine/keyboardprocessor/python/keyman/keyboardprocessor/_api.py b/common/engine/keyboardprocessor/python/keyman/keyboardprocessor/_api.py new file mode 100644 index 0000000000000000000000000000000000000000..0f2f59bd2c960599eae5ea1a0445d64871aaa326 --- /dev/null +++ b/common/engine/keyboardprocessor/python/keyman/keyboardprocessor/_api.py @@ -0,0 +1,573 @@ +# Copyright: © 2018 SIL International. +# Description: Lowlevel C style python API. Intended to be wrapped by a more +# Pythonic higher level API. +# Create Date: 18 Oct 2018 +# Authors: Tim Eves (TSE) +# + +import ctypes +import ctypes.util +import operator +import os +from enum import auto, IntEnum, IntFlag +from ctypes import (c_uint8, + c_uint16, c_uint32, + c_size_t, + c_void_p, c_char_p, + Structure, Union, POINTER, CFUNCTYPE) +from typing import Any, Tuple + +CP = c_uint16 +USV = c_uint32 +VirtualKey = c_uint16 + +libpath = os.environ.get('PYKMNKBD_LIBRARY_PATH', + ctypes.util.find_library("kmnkbp0")) + +libkbp = ctypes.cdll.LoadLibrary(libpath) + + +# Error handling +# ============== +class StatusCode(IntEnum): + OK = 0 + NO_MEM = auto() + IO_ERROR = auto() + INVALID_ARGUMENT = auto() + KEY_ERROR = auto() + OS_ERROR = 0x80000000 + + +Status = c_uint32 + + +def __map_oserror(code: Status) -> str: + code = StatusCode.OS_ERROR ^ code + msg = '{0!s}: OS error code (' + code + '): ' + os.strerror(code) + return msg, lambda m: OSError(code, m) + + +__exceptions_map = [ + (None, '{0!s}: Success'), + (MemoryError, '{0!s}: memory allocation failed.'), + (RuntimeError, '{0!s}: IO Error: {1!s}'), + (ValueError, '{0!s}: Invalid argument passed.'), + (LookupError, '{0!s}: Item does not exist in: {1!s}'), + (OSError, __map_oserror)] + + +def status_code(code: Status, func, args): + if code == StatusCode.OK: return args + exc, msg = __exceptions_map[code] + if callable(msg): msg = msg(code) + raise exc(msg.format(libkbp._name, *args)) + + +def null_check(code, func, args): + if code is not None: return args + raise KeyError(args[1] + ': Not found in collection') + + +class Dir(IntFlag): + IN = auto() + OUT = auto() + OPT = auto() + + +def __method(iface: str, method: str, result, + *args: Tuple[Any, Dir, str], **kwds): + proto = CFUNCTYPE(result, *map(operator.itemgetter(0), args)) + params = tuple(a[1:] for a in args) + c_name = iface+'_'+method if iface else method + f = proto(('km_kbp_' + c_name, libkbp), params) + if 'errcheck' in kwds: f.errcheck = kwds.get('errcheck') + globals()[c_name] = f + + +# Context processing +# ================== +Context_p = c_void_p + + +class ContextType(IntEnum): + END = 0 + CHAR = auto() + MARKER = auto() + + +class ContextItem(Structure): + class __ContextValue(Union): + _fields_ = (('character', USV), + ('marker', c_uint32)) + _anonymous_ = ('value',) + _fields_ = (('type', c_uint8), + ('value', __ContextValue)) + + +__method('context_items', 'from_utf16', Status, + (c_void_p, Dir.IN, 'text'), + (POINTER(POINTER(ContextItem)), Dir.OUT, 'context_items'), + errcheck=status_code) + +__method('context_items', 'to_utf16', c_size_t, + (POINTER(ContextItem), Dir.IN, 'context_items'), + (c_void_p, Dir.IN | Dir.OPT, 'buffer'), + (c_size_t, Dir.IN | Dir.OPT, 'buffer_size')) + +__method('context_items', 'dispose', None, + (POINTER(ContextItem), Dir.IN, 'context_items')) + +__method('context', 'set', Status, + (Context_p, Dir.IN, 'context'), + (POINTER(ContextItem), Dir.IN, 'context_items'), + errcheck=status_code) + +__method('context', 'get', POINTER(ContextItem), + (Context_p, Dir.IN, 'context')) + +__method('context', 'clear', None, (Context_p, Dir.IN, 'context')) + +__method('context', 'length', c_size_t, (Context_p, Dir.IN, 'context')) + +__method('context', 'append', Status, + (Context_p, Dir.IN, 'context'), + (POINTER(ContextItem), Dir.IN, 'context_items'), + errcheck=status_code) + +__method('context', 'shrink', Status, + (Context_p, Dir.IN, 'context'), + (c_size_t, Dir.IN, 'num'), + (POINTER(ContextItem), Dir.IN, 'prefix'), + errcheck=status_code) + + +class ActionItem(Structure): + class __ActionItem(Union): + _fields_ = (('marker', c_size_t), + ('option', c_char_p), + ('character', USV), + ('vkey', VirtualKey)) + _anonymous_ = ('data',) + _fields_ = (('type', c_uint8), + ('reserved', c_uint8*3), + ('data', __ActionItem)) + + +class ActionType(IntEnum): + END = 0 # Marks end of action items list. + CHAR = 1 + MARKER = 2 # correlates to kmn's "deadkey" markers + ALERT = 3 + BACK = 4 + PERSIST_OPT = 5 + RESET_OPT = 6 + VKEYDOWN = 7 + VKEYUP = 8 + VSHIFTDOWN = 9 + VSHIFTUP = 10 + MAX_TYPE_ID = auto() + + +# Option processing +# ================= +OptionSet_p = c_void_p + + +class OptionScope(IntEnum): + UNKNOWN = auto() + KEYBOARD = auto() + ENVIRONMENT = auto() + + +class Option(Structure): + _fields_ = (('key', c_char_p), + ('value', c_char_p), + ('scope', c_uint8)) + + +Option.END = Option(None, None) + +__method('options_set', 'size', c_size_t, (OptionSet_p, Dir.IN, 'opts')) + +__method('options_set', 'lookup', POINTER(Option), + (OptionSet_p, Dir.IN, 'opts'), + (c_char_p, Dir.IN, 'key'), + errcheck=null_check) + +__method('options_set', 'update', Status, + (OptionSet_p, Dir.IN, 'opts'), + (POINTER(Option), Dir.IN, 'new_opts'), + errcheck=status_code) + +__method('options_set', 'to_json', Status, + (OptionSet_p, Dir.IN, 'opts'), + (c_char_p, Dir.IN | Dir.OPT, 'buffer'), + (c_size_t, Dir.IN | Dir.OUT, 'space'), + errcheck=status_code) + + +# Keyboards +# ========= +Keyboard_p = c_void_p + + +class KeyboardAttrs(Structure): + _fields_ = (('version_string', c_char_p), + ('id', c_char_p), + ('folder_path', c_char_p), + ('default_options', OptionSet_p)) + + +__method('keyboard', 'load', Status, + (c_char_p, Dir.IN, 'path'), + (POINTER(Keyboard_p), Dir.OUT, 'kb'), + errcheck=status_code) + +__method('keyboard', 'dispose', None, (Keyboard_p, Dir.IN, 'kb')) + +__method('keyboard', 'get_attrs', POINTER(KeyboardAttrs), + (Keyboard_p, Dir.IN, 'keyboard')) + + +# State processing +# ================ +State_p = c_void_p + +__method('state', 'create', Status, + (Keyboard_p, Dir.IN, 'keyboard'), + (POINTER(Option), Dir.IN, 'env',), + (POINTER(State_p), Dir.OUT, 'out'), + errcheck=status_code) + +__method('state', 'clone', Status, + (State_p, Dir.IN, 'state'), + (POINTER(State_p), Dir.OUT, 'out'), + errcheck=status_code) + +__method('state', 'dispose', None, (State_p, Dir.IN, 'state')) + +__method('state', 'context', Context_p, (State_p, Dir.IN, 'state')) + +__method('state', 'options', OptionSet_p, (State_p, Dir.IN, 'state')) + +__method('state', 'action_items', POINTER(ActionItem), + (State_p, Dir.IN, 'state'), + (POINTER(c_size_t), Dir.OUT, 'num_items')) + +__method('state', 'to_json', Status, + (State_p, Dir.IN, 'state'), + (c_char_p, Dir.IN | Dir.OPT, 'buffer'), + (c_size_t, Dir.IN | Dir.OUT, 'space'), + errcheck=status_code) + + +# Processor +# ========= +class Attributes(Structure): + _fields_ = (('max_context', c_size_t), + ('current', c_uint16), + ('revision', c_uint16), + ('age', c_uint16), + ('technology', c_uint16), + ('vendor', c_char_p)) + + +class Tech(IntFlag): + UNSPECIFIED = 0 + KMN = 1 + LDML = 2 + + +__method(None, 'get_engine_attrs', POINTER(Attributes)) + +__method(None, 'process_event', Status, + (State_p, Dir.IN, 'state'), + (VirtualKey, Dir.IN, 'vkey'), + (c_uint16, Dir.IN, 'modifier_state')) + + +class Modifier(IntFlag): + LCTRL = 1 << 0 + RCTRL = 1 << 1 + LALT = 1 << 2 + RALT = 1 << 3 + SHIFT = 1 << 4 + CTRL = 1 << 5 + ALT = 1 << 6 + CAPS = 1 << 7 + NOCAPS = 1 << 8 + NUMLOCK = 1 << 9 + NONUMLOCK = 1 << 10 + SCROLLOCK = 1 << 11 + NOSCROLLOCK = 1 << 12 + VIRTUALKEY = 1 << 13 + + +class ModifierMask(IntFlag): + ALL = 0x7f + ALT_GR_SIM = Modifier.LCTRL | Modifier.LALT + CHIRAL = 0x1f + IS_CHIRAL = 0x0f + NON_CHIRAL = 0x7f + CAPS = 0x0300 + NUMLOCK = 0x0C00 + SCROLLLOCK = 0x3000 + + +class VKey(IntEnum): + _00 = auto() + LBUTTON = auto() + RBUTTON = auto() + CANCEL = auto() + MBUTTON = auto() + _05 = auto() + _06 = auto() + _07 = auto() + BKSP = auto() + KTAB = auto() + _0A = auto() + _0B = auto() + KP5 = auto() + ENTER = auto() + _0E = auto() + _0F = auto() + SHIFT = auto() + CONTROL = auto() + ALT = auto() + PAUSE = auto() + CAPS = auto() + _15 = auto() + _16 = auto() + _17 = auto() + _18 = auto() + _19 = auto() + _1A = auto() + ESC = auto() + _1C = auto() + _1D = auto() + _1E = auto() + _1F = auto() + SPACE = auto() + PGUP = auto() + PGDN = auto() + END = auto() + HOME = auto() + LEFT = auto() + UP = auto() + RIGHT = auto() + DOWN = auto() + SEL = auto() + PRINT = auto() + EXEC = auto() + PRTSCN = auto() + INS = auto() + DEL = auto() + HELP = auto() + K0 = auto() + K1 = auto() + K2 = auto() + K3 = auto() + K4 = auto() + K5 = auto() + K6 = auto() + K7 = auto() + K8 = auto() + K9 = auto() + _3A = auto() + _3B = auto() + _3C = auto() + _3D = auto() + _3E = auto() + _3F = auto() + _40 = auto() + KA = auto() + KB = auto() + KC = auto() + KD = auto() + KE = auto() + KF = auto() + KG = auto() + KH = auto() + KI = auto() + KJ = auto() + KK = auto() + KL = auto() + KM = auto() + KN = auto() + KO = auto() + KP = auto() + KQ = auto() + KR = auto() + KS = auto() + KT = auto() + KU = auto() + KV = auto() + KW = auto() + KX = auto() + KY = auto() + KZ = auto() + _5B = auto() + _5C = auto() + _5D = auto() + _5E = auto() + _5F = auto() + NP0 = auto() + NP1 = auto() + NP2 = auto() + NP3 = auto() + NP4 = auto() + NP5 = auto() + NP6 = auto() + NP7 = auto() + NP8 = auto() + NP9 = auto() + NPSTAR = auto() + NPPLUS = auto() + SEPARATOR = auto() + NPMINUS = auto() + NPDOT = auto() + NPSLASH = auto() + F1 = auto() + F2 = auto() + F3 = auto() + F4 = auto() + F5 = auto() + F6 = auto() + F7 = auto() + F8 = auto() + F9 = auto() + F10 = auto() + F11 = auto() + F12 = auto() + F13 = auto() + F14 = auto() + F15 = auto() + F16 = auto() + F17 = auto() + F18 = auto() + F19 = auto() + F20 = auto() + F21 = auto() + F22 = auto() + F23 = auto() + F24 = auto() + _88 = auto() + _89 = auto() + _8A = auto() + _8B = auto() + _8C = auto() + _8D = auto() + _8E = auto() + _8F = auto() + NUMLOCK = auto() + SCROLL = auto() + _92 = auto() + _93 = auto() + _94 = auto() + _95 = auto() + _96 = auto() + _97 = auto() + _98 = auto() + _99 = auto() + _9A = auto() + _9B = auto() + _9C = auto() + _9D = auto() + _9E = auto() + _9F = auto() + _A0 = auto() + _A1 = auto() + _A2 = auto() + _A3 = auto() + _A4 = auto() + _A5 = auto() + _A6 = auto() + _A7 = auto() + _A8 = auto() + _A9 = auto() + _AA = auto() + _AB = auto() + _AC = auto() + _AD = auto() + _AE = auto() + _AF = auto() + _B0 = auto() + _B1 = auto() + _B2 = auto() + _B3 = auto() + _B4 = auto() + _B5 = auto() + _B6 = auto() + _B7 = auto() + _B8 = auto() + _B9 = auto() + COLON = auto() + EQUAL = auto() + COMMA = auto() + HYPHEN = auto() + PERIOD = auto() + SLASH = auto() + BKQUOTE = auto() + _C1 = auto() + _C2 = auto() + _C3 = auto() + _C4 = auto() + _C5 = auto() + _C6 = auto() + _C7 = auto() + _C8 = auto() + _C9 = auto() + _CA = auto() + _CB = auto() + _CC = auto() + _CD = auto() + _CE = auto() + _CF = auto() + _D0 = auto() + _D1 = auto() + _D2 = auto() + _D3 = auto() + _D4 = auto() + _D5 = auto() + _D6 = auto() + _D7 = auto() + _D8 = auto() + _D9 = auto() + _DA = auto() + LBRKT = auto() + BKSLASH = auto() + RBRKT = auto() + QUOTE = auto() + oDF = auto() + oE0 = auto() + oE1 = auto() + oE2 = auto() + oE3 = auto() + oE4 = auto() + _E5 = auto() + oE6 = auto() + _E7 = auto() + _E8 = auto() + oE9 = auto() + oEA = auto() + oEB = auto() + oEC = auto() + oED = auto() + oEE = auto() + oEF = auto() + oF0 = auto() + oF1 = auto() + oF2 = auto() + oF3 = auto() + oF4 = auto() + oF5 = auto() + _F6 = auto() + _F7 = auto() + _F8 = auto() + _F9 = auto() + _FA = auto() + _FB = auto() + _FC = auto() + _FD = auto() + _FE = auto() + _FF = auto() diff --git a/common/engine/keyboardprocessor/python/keyman/keyboardprocessor/api.py b/common/engine/keyboardprocessor/python/keyman/keyboardprocessor/api.py new file mode 100644 index 0000000000000000000000000000000000000000..6fcedf18e54ee497c7ac87a3dfee90ef57c0ba6b --- /dev/null +++ b/common/engine/keyboardprocessor/python/keyman/keyboardprocessor/api.py @@ -0,0 +1,118 @@ +import pathlib +from typing import NamedTuple, NewType, Tuple, List, Union +from enum import Enum +from collections.abc import Sequence, Mapping + +USV = int +Marker = int +VirtualKey = int + + +class Context(Sequence): + Item = Union[USV, Marker] + + def __init__(initial: str): + pass + + def __del__(self): + pass + + def __str__(self): + pass + + def set(self, ctxt: List[Item]): + self.clear() + self.apped(ctxt) + + def clear(self): + pass + + def __getitem__(self, key: int) -> Item: + pass + + def __len__(self): + pass + + def append(self, ctxt: List[Item]): + pass + + def delete(self, remove_n: int, prefix: List[Item]): + pass + + +Option = Tuple[str, str] + + +class OptionSet(Mapping): + def __getitem__(self, key: str) -> Option: + pass + + def __iter__(self): + pass + + def __len__(self): + pass + + def __str__(self): + pass + + +class Keyboard(NamedTuple('__kb_attrs', version=str, id=str, folder_path=pathlib.Path, default_options=OptionSet)): + def __new__(cls, _): + return super(Keyboard, cls).__new__(cls, *[None]*4) + + def __init__(self, kb_path: pathlib.Path): + pass + + def __del__(self): + pass + + +class Action: + VKeyDown = NewType('Action.VirtualKey', VirtualKey) + # VKeyUp = VirtualKey + # VShiftDown = VirtualKey + # VShiftUp = VirtualKey + # Char = int + # Marker = int + # Bell = NewType('Bell', None) + # Back = NewType('Back', None) + # PersistOpt = str + # ResetOpt = str + + +ActionList = List[Action] + + +class State: + def __init__(self, kb: Keyboard, env: OptionSet): + pass + + def __del__(self): + pass + + @property + def flags(self) -> int: + pass + + @property + def context(self) -> Context: + pass + + @property + def environment(self) -> OptionSet: + pass + + @property + def options(self) -> OptionSet: + pass + + def indentify_option_src(opt: Option): + pass + + +def process_event(vk: VirtualKey, + modifier_state, + state: State, + acts: ActionList): + pass diff --git a/common/engine/keyboardprocessor/src/context.hpp b/common/engine/keyboardprocessor/src/context.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b2b7fc99a1d9b7c279e34d7541563b69f91c33e7 --- /dev/null +++ b/common/engine/keyboardprocessor/src/context.hpp @@ -0,0 +1,34 @@ +/* + Copyright: © 2018 SIL International. + Description: Internal context class and adaptor class for the API. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 2 Oct 2018 - TSE - Refactored out of km_kbp_context_api.cpp +*/ + +#pragma once +#include +#include +#include + +// Forward declarations +class json; + +namespace km { +namespace kbp +{ + +// This will likely be replaced with a class implementing a more space +// efficient data structure such as a ring buffer or bounded queue. +using context = std::list; + +} // namespace kbp +} // namespace km + +json & operator << (json &, km::kbp::context const &); +json & operator << (json &, km_kbp_context_item const &); + + +struct km_kbp_context : public km::kbp::context +{ +}; diff --git a/common/engine/keyboardprocessor/src/json.cpp b/common/engine/keyboardprocessor/src/json.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cec8dd1eb0f563a68d3ea93873ed20643e30376f --- /dev/null +++ b/common/engine/keyboardprocessor/src/json.cpp @@ -0,0 +1,123 @@ +/* + Copyright: © 2011,2018 SIL International. + Description: JSON pretty printer for dumping debug/diagnostic data structure. + Create Date: 15 Dec 2011 + Authors: Tim Eves (TSE) + History: 28 Sep 2018 - TSE - Imported from graphite2 project. + - TSE - Refactored to use C++ std::ostream. + 25 Oct 2018 - TSE - Relicensed under the MIT license for + inclusion in the Keyman project. +*/ + +#include +#include +#include "json.hpp" + +#if defined(_MSC_VER) +#define FORMAT_INTMAX "%lli" +#define FORMAT_UINTMAX "%llu" +#else +#define FORMAT_INTMAX "%ji" +#define FORMAT_UINTMAX "%ju" +#endif + +namespace +{ + enum + { + seq = ',', + obj='}', member=':', empty_obj='{', + arr=']', empty_arr='[' + }; +} + +const std::nullptr_t json::null = nullptr; + +inline +void json::context(const char current) throw() +{ + _stream << *_context; + indent(); + *_context = current; +} + + +void json::indent(const int d) throw() +{ + if (*_context == member || (_flatten && _flatten < _context)) + _stream.put(' '); + else + _stream << std::endl << std::setw(4*int(_context - _contexts + d)) << ""; +} + + +inline +void json::push_context(const char prefix, const char suffix) throw() +{ + assert(_context - _contexts < ptrdiff_t(sizeof _contexts)); + + if (_context == _contexts) + *_context = suffix; + else + context(suffix); + *++_context = prefix; +} + + +void json::pop_context() throw() +{ + assert(_context > _contexts); + + if (*_context == seq) indent(-1); + else _stream.put(*_context); + + _stream.put(*--_context); + if (_context == _contexts) _stream << std::endl; + _stream.flush(); + + if (_flatten >= _context) _flatten = 0; + *_context = seq; +} + + +// These four functions cannot be inlined as pointers to these +// functions are needed for operator << (_context_t) to work. +void json::flat(json & j) throw() { if (!j._flatten) j._flatten = j._context; } +void json::close(json & j) throw() { j.pop_context(); } +void json::object(json & j) throw() { j.push_context('{', '}'); } +void json::array(json & j) throw() { j.push_context('[', ']'); } +void json::item(json & j) throw() +{ + while (j._context > j._contexts+1 && j._context[-1] != arr) + j.pop_context(); +} + + +json & json::operator << (json::string s) throw() +{ + const char ctxt = _context[-1] == obj ? *_context == member ? seq : member : seq; + context(ctxt); + _stream << '"' << s << '"'; + if (ctxt == member) _stream.put(' '); + + return *this; +} + +json & json::operator << (json::number f) throw() +{ + context(seq); + if (std::numeric_limits::infinity() == f) + _stream << "Infinity"; + else if (-std::numeric_limits::infinity() == f) + _stream << "-Infinity"; + else if (std::numeric_limits::quiet_NaN() == f || + std::numeric_limits::signaling_NaN() == f) + _stream << "NaN"; + else + _stream << f; + return *this; +} +json & json::operator << (json::integer d) throw() { context(seq); _stream << intmax_t(d); return *this; } +json & json::operator << (json::integer_u d) throw(){ context(seq); _stream << uintmax_t(d); return *this; } +json & json::operator << (json::boolean b) throw() { context(seq); _stream << (b ? "true" : "false"); return *this; } +json & json::operator << (std::nullptr_t) throw() { context(seq); _stream << "null"; return *this; } diff --git a/common/engine/keyboardprocessor/src/json.hpp b/common/engine/keyboardprocessor/src/json.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0a34a5822848889e49f1386dc13c725e0c00781b --- /dev/null +++ b/common/engine/keyboardprocessor/src/json.hpp @@ -0,0 +1,156 @@ +/* + Copyright: © 2011,2018 SIL International. + Description: JSON pretty printer for dumping debug/diagnostic internal + state information. + Create Date: 15 Dec 2011 + Authors: Tim Eves (TSE) + History: 28 Sep 2018 - TSE - Imported from graphite2 project. + - TSE - Refactored to use C++ std::ostream. + 25 Oct 2018 - TSE - Relicensed under the MIT license for + inclusion in the Keyman project. +*/ +#pragma once + +#include +#include +#include +#include +#include + +class json +{ + // Prevent copying + json(const json &); + json & operator = (const json &); + + typedef void (*_context_t)(json &); + + std::ostream& _stream; + char _contexts[128], // context stack + * _context, // current context (top of stack) + * _flatten; // if !0 points to context above which + // pretty printed output should occur. + std::vector _env; + + void context(const char current) throw(); + void indent(const int d=0) throw(); + void push_context(const char, const char) throw(); + void pop_context() throw(); + +public: + class closer; + + using string = const char *; + using number = double; + enum class integer : std::intmax_t {}; + enum class integer_u : std::uintmax_t {}; + using boolean = bool; + static const std::nullptr_t null; + + void setenv(unsigned int index, void *val) { _env.reserve(index + 1); if (index >= _env.size()) _env.insert(_env.end(), _env.size() - index + 1, 0); _env[index] = val; } + void *getenv(unsigned int index) const { return _env[index]; } + const std::vector &getenvs() const { return _env; } + + static void flat(json &) throw(); + static void close(json &) throw(); + static void object(json &) throw(); + static void array(json &) throw(); + static void item(json &) throw(); + + json(std::ostream& stream) throw(); + ~json() throw (); + + auto & stream() const throw(); + + json & operator << (string) throw(); + json & operator << (number) throw(); + json & operator << (integer) throw(); + json & operator << (integer_u) throw(); + json & operator << (boolean) throw(); + json & operator << (std::nullptr_t) throw(); + json & operator << (_context_t) throw(); + + operator bool() const throw(); + bool good() const throw(); + bool eof() const throw(); +}; + +class json::closer +{ + // Prevent copying. + closer(const closer &); + closer & operator = (const closer &); + + json * const _j; +public: + closer(json * const j) : _j(j) {} + ~closer() throw() { if (_j) *_j << close; } +}; + +inline +json::json(std::ostream& s) throw() +: _stream(s), _context(_contexts), _flatten(0) +{ + if (_stream.good()) + _stream.flush(); +} + + +inline +json::~json() throw () +{ + while (_context > _contexts) pop_context(); +} + +inline +auto & json::stream() const throw() { return _stream; } + + +inline +json & json::operator << (json::_context_t ctxt) throw() +{ + ctxt(*this); + return *this; +} + +inline +json & operator << (json & j, std::string const & s) throw() { return j << json::string(s.c_str()); } + +inline +json & operator << (json & j, signed char d) throw() { return j << json::integer(d); } + +inline +json & operator << (json & j, unsigned char d) throw() { return j << json::integer_u(d); } + +inline +json & operator << (json & j, short int d) throw() { return j << json::integer(d); } + +inline +json & operator << (json & j, unsigned short int d) throw() { return j << json::integer_u(d); } + +inline +json & operator << (json & j, int d) throw() { return j << json::integer(d); } + +inline +json & operator << (json & j, unsigned int d) throw() { return j << json::integer_u(d); } + +inline +json & operator << (json & j, long int d) throw() { return j << json::integer(d); } + +inline +json & operator << (json & j, unsigned long int d) throw() { return j << json::integer_u(d); } + +inline +json & operator << (json & j, long long int d) throw() { return j << json::integer(d); } + +inline +json & operator << (json & j, unsigned long long int d) throw() { return j << json::integer_u(d); } + +inline +json::operator bool() const throw() { return good(); } + +inline +bool json::good() const throw() { return _stream.good(); } + +inline +bool json::eof() const throw() { return _stream.eof(); } diff --git a/common/engine/keyboardprocessor/src/keyboard.cpp b/common/engine/keyboardprocessor/src/keyboard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0f449399927febb1b3754e1e67f458ca443430e4 --- /dev/null +++ b/common/engine/keyboardprocessor/src/keyboard.cpp @@ -0,0 +1,76 @@ +/* + Copyright: © 2018 SIL International. + Description: Internal keyboard class and adaptor class for the API. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 7 Oct 2018 - TSE - Refactored out of km_kbp_keyboard_api.cpp +*/ + +#include +#include "keyboard.hpp" +#include "json.hpp" +#include "processor.hpp" + +using namespace km::kbp; + +std::string utf16_to_utf8(std::u16string utf16_string); + +keyboard::keyboard(std::filesystem::path const & path) +: _keyboard_id(path.stem().u16string()), + _version_string(u"3.145"), + _folder_path(path.parent_path()), + _default_opts {KM_KBP_OPTIONS_END} +{ + version_string = _version_string.c_str(); + id = _keyboard_id.c_str(); + folder_path = _folder_path.native().c_str(); + + if (path.extension() == ".kmx" || + path.extension() == ".KMX") { // Some legacy packages may include upper-case file extensions + _processor = new kmx_processor(this); + } + else if (path.extension() == ".mock") { + _processor = new mock_processor(this); + } + else { + _processor = new null_processor(this); + } + + default_options = _default_opts.data(); +} + +json & km::kbp::operator << (json & j, km::kbp::keyboard const & kb) +{ + j << json::object + << "id" << utf16_to_utf8(kb.id) + << "folder" << kb._folder_path.string() + << "version" << utf16_to_utf8(kb.version_string) + << "rules" << json::array << json::close; + + return j << json::close; +} + +/* + This function exists because of a bug in Visual Studio 2015 and 2017: + https://social.msdn.microsoft.com/Forums/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error?forum=vcgeneral + https://stackoverflow.com/a/35103224/1836776 +*/ + +#if _MSC_VER >= 1900 /* VS 2015 */ && _MSC_VER <= 1916 /* VS 2017 19.16 */ + +std::string utf16_to_utf8(std::u16string utf16_string) +{ + std::wstring_convert, int16_t> convert; + auto p = reinterpret_cast(utf16_string.data()); + return convert.to_bytes(p, p + utf16_string.size()); +} + +#else + +std::string utf16_to_utf8(std::u16string utf16_string) +{ + std::wstring_convert, char16_t> convert; + return convert.to_bytes(utf16_string); +} + +#endif diff --git a/common/engine/keyboardprocessor/src/keyboard.hpp b/common/engine/keyboardprocessor/src/keyboard.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1692bdf4b22b71e5a49d1b2db996f0b0b32be506 --- /dev/null +++ b/common/engine/keyboardprocessor/src/keyboard.hpp @@ -0,0 +1,55 @@ +/* + Copyright: © 2018 SIL International. + Description: Internal keyboard class and adaptor class for the API. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 2 Oct 2018 - TSE - Refactored out of km_kbp_keyboard_api.cpp +*/ + +#pragma once + +#include +#include + +#include + +#include "option.hpp" +#include "processor.hpp" + +namespace std { + namespace filesystem = std::experimental::filesystem; +} + +// Forward declarations +class json; + +namespace km { +namespace kbp +{ + + class keyboard : public km_kbp_keyboard_attrs + { + std::u16string const _keyboard_id; + std::u16string const _version_string; + std::filesystem::path const _folder_path; + std::vector _default_opts; + abstract_processor *_processor; + public: + keyboard(std::filesystem::path const &); + ~keyboard() { + delete _processor; + } + + friend json & operator << (json &, km::kbp::keyboard const &); + + std::vector *default_opts() { return & _default_opts; } + + kbp::abstract_processor const & processor() const noexcept { return *_processor; } + }; + + json & operator << (json &, km::kbp::keyboard const &); + +} // namespace kbp +} // namespace km + +struct km_kbp_keyboard : public km::kbp::keyboard {}; diff --git a/common/engine/keyboardprocessor/src/km_kbp_context_api.cpp b/common/engine/keyboardprocessor/src/km_kbp_context_api.cpp new file mode 100644 index 0000000000000000000000000000000000000000..334a2fa9874a4fff4d7ea188cf6a9cf93da71807 --- /dev/null +++ b/common/engine/keyboardprocessor/src/km_kbp_context_api.cpp @@ -0,0 +1,264 @@ +/* + Copyright: © 2018 SIL International. + Description: Implementation of the context API functions using internal + data structures and functions. + Create Date: 27 Sep 2018 + Authors: Tim Eves (TSE) + History: 27 Sep 2018 - TSE - Initial implementation. + 5 Oct 2018 - TSE - Refactor out adaptor and internal classes + into context.hpp +*/ +#include +#include +#include +#include + +#include + +#include "context.hpp" +#include "json.hpp" +#include "utfcodec.hpp" + +namespace { + template + km_kbp_status + _context_items_from(typename U::codeunit_t const *text, + km_kbp_context_item **out_ptr) + { + assert(text); assert(out_ptr); + if (!text || !out_ptr) return KM_KBP_STATUS_INVALID_ARGUMENT; + + *out_ptr = nullptr; + try + { + std::vector res; + + for (auto i = typename U::const_sentinal_iterator(text); i; ++i) + { + if(i.error()) return KM_KBP_STATUS_INVALID_UTF; + res.emplace_back(km_kbp_context_item {KM_KBP_CT_CHAR, {0,}, {*i}}); + } + // Terminate the context_items array. + res.emplace_back(km_kbp_context_item KM_KBP_CONTEXT_ITEM_END); + + *out_ptr = new km_kbp_context_item[res.size()]; + std::move(res.begin(), res.end(), *out_ptr); + } + catch(std::bad_alloc) + { + return KM_KBP_STATUS_NO_MEM; + } + + return KM_KBP_STATUS_OK; + } + + template + km_kbp_status _context_items_to(km_kbp_context_item const *ci, + typename U::codeunit_t *buf, + size_t * sz_ptr) + { + assert(ci); assert(sz_ptr); + if (!ci || !sz_ptr) return KM_KBP_STATUS_INVALID_ARGUMENT; + auto const buf_size = *sz_ptr; + + if (buf && buf_size > 0) + { + auto i = typename U::iterator(buf); + auto const e = decltype(i)(buf + buf_size - 1); + + for (;i != e && ci->type != KM_KBP_CT_END; ++ci) + { + if (ci->type == KM_KBP_CT_CHAR) + { + *i = ci->character; ++i; + } + } + *i = 0; // null terminate the UTF16 string. + + *sz_ptr = buf_size - (e - i); + + return ci->type == KM_KBP_CT_END + ? KM_KBP_STATUS_OK + : KM_KBP_STATUS_INSUFFICENT_BUFFER; + } + else + { + auto n = 0; + typename U::codeunit_t cps[4/sizeof(typename U::codeunit_t)]; + + do + { + if (ci->type == KM_KBP_CT_CHAR) + { + int8_t l; + U::codec::put(cps, ci->character, l); + n += l; + } + } + while(ci++->type != KM_KBP_CT_END); + + *sz_ptr = n+1; + return KM_KBP_STATUS_OK; + } + } +} + +km_kbp_status +km_kbp_context_items_from_utf16(km_kbp_cp const *text, + km_kbp_context_item **out_ptr) +{ + return _context_items_from(reinterpret_cast(text), out_ptr); +} + + +km_kbp_status +km_kbp_context_items_from_utf8(char const *text, + km_kbp_context_item **out_ptr) +{ + return _context_items_from(reinterpret_cast(text), out_ptr); +} + + +km_kbp_status km_kbp_context_items_to_utf8(km_kbp_context_item const *ci, + char *buf, size_t * sz_ptr) +{ + return _context_items_to(ci, + reinterpret_cast(buf), + sz_ptr); +} + + +km_kbp_status km_kbp_context_items_to_utf16(km_kbp_context_item const *ci, + km_kbp_cp *buf, size_t * sz_ptr) +{ + return _context_items_to(ci, + reinterpret_cast(buf), + sz_ptr); +} + + +void km_kbp_context_items_dispose(km_kbp_context_item *ci) +{ + delete [] ci; +} + + +km_kbp_status km_kbp_context_set(km_kbp_context *ctxt, km_kbp_context_item const *ci) +{ + km_kbp_context_clear(ctxt); + return km_kbp_context_append(ctxt, ci); +} + + +km_kbp_status km_kbp_context_get(km_kbp_context const *ctxt, + km_kbp_context_item **out_ptr) +{ + assert(ctxt); assert(out_ptr); + if (!ctxt || !out_ptr) return KM_KBP_STATUS_INVALID_ARGUMENT; + + try + { + *out_ptr = new km_kbp_context_item[ctxt->size() + 1]; + } + catch (std::bad_alloc) + { + return KM_KBP_STATUS_NO_MEM; + } + std::copy(ctxt->begin(), ctxt->end(), *out_ptr); + (*out_ptr)[ctxt->size()].type = KM_KBP_CT_END; + + return KM_KBP_STATUS_OK; +} + + +void km_kbp_context_clear(km_kbp_context *ctxt) +{ + assert(ctxt); + if (ctxt) + { + ctxt->clear(); + } +} + + +size_t km_kbp_context_length(km_kbp_context *ctxt) +{ + assert(ctxt); + return ctxt ? ctxt->size() : 0; +} + + +km_kbp_status km_kbp_context_append(km_kbp_context *ctxt, + km_kbp_context_item const *ci) +{ + assert(ctxt); assert(ci); + if (!ctxt || !ci) return KM_KBP_STATUS_INVALID_ARGUMENT; + + try + { + for (;ci->type != KM_KBP_CT_END; ++ci) + { + ctxt->emplace_back(*ci); + } + } catch(std::bad_alloc) { + return KM_KBP_STATUS_NO_MEM; + } + + return KM_KBP_STATUS_OK; +} + + +km_kbp_status km_kbp_context_shrink(km_kbp_context *ctxt, size_t num, + km_kbp_context_item const * ci) +{ + assert(ctxt); + if (!ctxt) return KM_KBP_STATUS_INVALID_ARGUMENT; + + try + { + ctxt->resize(ctxt->size() - std::min(num, ctxt->size())); + + if (ci) + { + auto const ip = ctxt->begin(); + while(num-- && ci->type != KM_KBP_CT_END) + { + ctxt->emplace(ip, *ci); + ci++; + } + } + } catch(std::bad_alloc) { + return KM_KBP_STATUS_NO_MEM; + } + + return KM_KBP_STATUS_OK; +} + + +json & operator << (json & j, km::kbp::context const & ctxt) { + j << json::array; + for (auto & i: ctxt) j << i; + return j << json::close; +} + +json & operator << (json & j, km_kbp_context_item const & i) +{ + utf8::codeunit_t cps[5] = {0,}; + int8_t l; + + switch (i.type) + { + case KM_KBP_CT_CHAR: + utf8::codec::put(cps, i.character, l); + j << json::string(&cps[0]); + break; + case KM_KBP_CT_MARKER: + j << json::integer_u(i.marker); + break; + default: + j << json::flat << json::object + << "invalid type" << i.type + << json::close; + } + return j; +} diff --git a/common/engine/keyboardprocessor/src/km_kbp_keyboard_api.cpp b/common/engine/keyboardprocessor/src/km_kbp_keyboard_api.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8067e73c3a74415269923f61151ff96fe8ae75c4 --- /dev/null +++ b/common/engine/keyboardprocessor/src/km_kbp_keyboard_api.cpp @@ -0,0 +1,74 @@ +/* + Copyright: © 2018 SIL International. + Description: Implementation of the keyboard API functions using internal + data structures and functions. + Create Date: 1 Oct 2018 + Authors: Tim Eves (TSE) + History: 1 Sep 2018 - TSE - Initial implementation. + 5 Oct 2018 - TSE - Refactor out adaptor and internal classes + into keyboard.hpp +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "keyboard.hpp" +#include "option.hpp" + + + +km_kbp_status +km_kbp_keyboard_load(km_kbp_path_name kb_path, + km_kbp_keyboard **keyboard) +{ + assert(keyboard); + if (!keyboard) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + //auto stat = std::filesystem::status(kb_path); + // + // if (stat.type() != std::filesystem::file_type::regular) + // return KM_KBP_STATUS_INVALID_ARGUMENT; + + try + { + km::kbp::keyboard *kp = new km::kbp::keyboard(kb_path); + km_kbp_status status = kp->processor().validate(); + if (status != KM_KBP_STATUS_OK) { + delete kp; + return status; + } + *keyboard = static_cast(kp); + } + catch (std::bad_alloc) + { + return KM_KBP_STATUS_NO_MEM; + } + return KM_KBP_STATUS_OK; +} + +void +km_kbp_keyboard_dispose(km_kbp_keyboard *keyboard) +{ + delete keyboard; +} + +km_kbp_status +km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, + km_kbp_keyboard_attrs const **out) +{ + assert(keyboard); assert(out); + if (!keyboard || !out) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + *out = keyboard; + return KM_KBP_STATUS_OK; +} diff --git a/common/engine/keyboardprocessor/src/km_kbp_options_api.cpp b/common/engine/keyboardprocessor/src/km_kbp_options_api.cpp new file mode 100644 index 0000000000000000000000000000000000000000..944ba29544e00d84d59a4729693bf9e1704a691e --- /dev/null +++ b/common/engine/keyboardprocessor/src/km_kbp_options_api.cpp @@ -0,0 +1,140 @@ +/* + Copyright: © 2018 SIL International. + Description: Implementation of the options API functions using internal + data structures and functions. + Create Date: 27 Sep 2018 + Authors: Tim Eves (TSE) + History: 27 Sep 2018 - TSE - Initial implementation. + 5 Oct 2018 - TSE - Refactor out adaptor and internal classes + into option.hpp +*/ +#include +#include +#include +#include +#include +#include +#include + +#include "option.hpp" +#include "json.hpp" + +size_t +km_kbp_options_list_size(km_kbp_option_item const *opts) +{ + assert(opts); + if (!opts) return 0; + + auto n = 0; + while (opts->key) ++n; + + return n; +} + + +km_kbp_status +km_kbp_options_lookup(km_kbp_state const *state, + uint8_t scope, km_kbp_cp const *key, + km_kbp_cp const **value_out) +{ + assert(state); assert(key); assert(value_out); + if (!state || !key || !value_out) return KM_KBP_STATUS_INVALID_ARGUMENT; + + if (scope == KM_KBP_OPT_UNKNOWN || scope > KM_KBP_OPT_MAX_SCOPES) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + auto opts = km_kbp_state_options(const_cast(state)); + + // Copy the internal value to our new buffer + km_kbp_cp const *internal_value = opts->lookup(km_kbp_option_scope(scope), key); + if (!internal_value) + { + return KM_KBP_STATUS_KEY_ERROR; + } + std::u16string const &value = internal_value; + + km_kbp_cp *valuep; + try + { + valuep = new km_kbp_cp[value.size() + 1]; + } + catch (std::bad_alloc) + { + return KM_KBP_STATUS_NO_MEM; + } + std::copy(value.begin(), value.end(), valuep); + valuep[value.size()] = u'\0'; + + *value_out = valuep; + + return KM_KBP_STATUS_OK; +} + + +km_kbp_status +km_kbp_options_update(km_kbp_state *state, km_kbp_option_item const *opt) +{ + assert(state); assert(opt); + if (!state|| !opt) return KM_KBP_STATUS_INVALID_ARGUMENT; + + auto opts = km_kbp_state_options(state); + + try + { + for (;opt->key; ++opt) + { + if (opt->scope == KM_KBP_OPT_UNKNOWN || opt->scope > KM_KBP_OPT_MAX_SCOPES) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + if (!opts->assign(state, km_kbp_option_scope(opt->scope), opt->key, opt->value)) + return KM_KBP_STATUS_KEY_ERROR; + } + } + catch (std::bad_alloc) + { + return KM_KBP_STATUS_NO_MEM; + } + + return KM_KBP_STATUS_OK; +} + +// This function doesn't need to use the json pretty printer for such a simple +// list of key:value pairs but it's a good introduction to it. +km_kbp_status +km_kbp_options_to_json(km_kbp_options const *opts, char *buf, size_t *space) +{ + assert(opts); assert(space); + if (!opts || !space) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + std::stringstream _buf; + json jo(_buf); + + try + { + jo << *opts; + } + catch (std::bad_alloc) + { + *space = 0; + return KM_KBP_STATUS_NO_MEM; + } + + // Fetch the finished doc and copy it to the buffer if there enough space. + auto const doc = _buf.str(); + if (buf && *space > doc.size()) + { + doc.copy(buf, *space); + buf[doc.size()] = 0; + } + + // Return space needed/used. + *space = doc.size()+1; + return KM_KBP_STATUS_OK; +} + +void +km_kbp_cp_dispose(km_kbp_cp const *cp) +{ + delete [] cp; +} diff --git a/common/engine/keyboardprocessor/src/km_kbp_processevent_api.cpp b/common/engine/keyboardprocessor/src/km_kbp_processevent_api.cpp new file mode 100644 index 0000000000000000000000000000000000000000..da9b0490e605323b76b0de38207c1750d9f41118 --- /dev/null +++ b/common/engine/keyboardprocessor/src/km_kbp_processevent_api.cpp @@ -0,0 +1,29 @@ +/* + Copyright: © 2018 SIL International. + Description: This is a wrapper for the process_event API. + TODO: Add a mecahnism to trigger output of PERSIST_OPT & + RESET_OPT actions items, options support and context matching. + Create Date: 17 Oct 2018 + Authors: Tim Eves (TSE) + History: 17 Oct 2018 - TSE - Initial implementation. +*/ + +#include +#include "processor.hpp" +#include "state.hpp" + +km_kbp_status +km_kbp_process_event(km_kbp_state *state, + km_kbp_virtual_key vk, uint16_t modifier_state) +{ + km::kbp::keyboard const & k = state->keyboard(); + return const_cast(k.processor()).process_event(state, vk, modifier_state); +} + + +km_kbp_attr const * +km_kbp_get_engine_attrs(km_kbp_state const *state) +{ + return state->keyboard().processor().get_attrs(); +} + diff --git a/common/engine/keyboardprocessor/src/km_kbp_state_api.cpp b/common/engine/keyboardprocessor/src/km_kbp_state_api.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7e8dd0ba80636cf0ac11343322205b2abe7d8189 --- /dev/null +++ b/common/engine/keyboardprocessor/src/km_kbp_state_api.cpp @@ -0,0 +1,219 @@ +/* + Copyright: © 2018 SIL International. + Description: Implementation of the state API functions using internal + data structures and functions. + Create Date: 5 Oct 2018 + Authors: Tim Eves (TSE) + History: 5 Oct 2018 - TSE - Initial implementation. + 18 Oct 2018 - TSE - Refactor out adaptor and internal classes + into state.hpp +*/ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "context.hpp" +#include "option.hpp" +#include "keyboard.hpp" +#include "state.hpp" + +using namespace km::kbp; + +km_kbp_status km_kbp_state_create(km_kbp_keyboard const * keyboard, + km_kbp_option_item const *env, + km_kbp_state ** out) +{ + assert(keyboard); assert(env); assert(out); + if (!keyboard || !env || !out) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + try + { + *out = new km_kbp_state(static_cast(*keyboard), + env); + + } + catch (std::bad_alloc) + { + return KM_KBP_STATUS_NO_MEM; + } + return KM_KBP_STATUS_OK; +} + + +km_kbp_status km_kbp_state_clone(km_kbp_state const *state, + km_kbp_state ** out) +{ + assert(state); assert(out); + if (!state || !out) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + *out = new km_kbp_state(*state); + return KM_KBP_STATUS_OK; +} + + +void km_kbp_state_dispose(km_kbp_state *state) +{ + delete state; +} + + +km_kbp_context *km_kbp_state_context(km_kbp_state *state) +{ + assert(state); + if (!state) return nullptr; + + return static_cast(&state->context()); +} + + +km_kbp_options *km_kbp_state_options(km_kbp_state *state) +{ + assert(state); + if (!state) return nullptr; + + return static_cast(&state->options()); +} + + +km_kbp_action_item const * km_kbp_state_action_items(km_kbp_state const *state, + size_t *num_items) +{ + assert(state); + if (!state) return nullptr; + + if (num_items) + *num_items = state->actions.size(); + + // Process events will ensure that the actions vector is always well + // teminated + return state->actions.data(); +} + +namespace { + char const * action_item_name_lut[] = { + "", + "character", + "marker", + "alert", + "back", + "persist", + "reset", + "vkeydown", + "vkeyup", + "vshiftdown", + "vshiftup" + }; + + constexpr char const * const scope_names_lut[] = { + u8"unspecified", + u8"keyboard", + u8"environment" + }; +} + + +json & operator << (json & j, km_kbp_action_item const &act) +{ + j << json::flat << json::object; + if (act.type >= KM_KBP_IT_MAX_TYPE_ID) + { + j << "invalid" << json::null << json::close; + return j; + } + + j << action_item_name_lut[act.type]; + switch (act.type) + { + case KM_KBP_IT_END: + case KM_KBP_IT_ALERT: + j << json::null; + break; + case KM_KBP_IT_CHAR: + case KM_KBP_IT_MARKER: + j << km_kbp_context_item {act.type, {0,}, {act.character}}; // TODO: is act.type correct here? it may map okay but this is bad practice to mix constants across types. Similarly using act.character instead of act.type + break; + case KM_KBP_IT_BACK: + j << json::null; // act.erased; + break; + case KM_KBP_IT_PERSIST_OPT: + j << json::object + << scope_names_lut[act.option->scope] + << json::flat << json::object + << act.option->key << act.option->value + << json::close + << json::close; + break; + break; + } + j << json::close; + + return j; +} + + +json & operator << (json & j, std::vector const & acts) +{ + j << json::array; + for (auto & act: acts) + { + if (act.type != KM_KBP_IT_END) + { + j << act; + } + } + j << json::close; + return j; +} + + +km_kbp_status km_kbp_state_to_json(km_kbp_state const *state, + char *buf, + size_t *space) +{ + assert(state); + if (!state) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + std::stringstream _buf; + json jo(_buf); + + try + { + // Pretty print the document. + jo << json::object + << "$schema" << "keyman/keyboardprocessor/doc/introspection.schema" + << "keyboard" << state->keyboard() + << "options" << state->options() + << "context" << state->context() + << "actions" << state->actions + << json::close; + } + catch (std::bad_alloc) + { + *space = 0; + return KM_KBP_STATUS_NO_MEM; + } + + // Fetch the finished doc and copy it to the buffer if there enough space. + auto const doc = _buf.str(); + if (buf && *space > doc.size()) + { + std::copy(doc.begin(), doc.end(), buf); + buf[doc.size()] = 0; + } + + // Return space needed/used. + *space = doc.size()+1; + return KM_KBP_STATUS_OK; + +} diff --git a/common/engine/keyboardprocessor/src/kmx/.gitignore b/common/engine/keyboardprocessor/src/kmx/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..69ffc835cd58b37bb2f58058d770d2cf77c5ca15 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/.gitignore @@ -0,0 +1 @@ +keymanversion_build.h diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_actions.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_actions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5182154978bbd7c72102877a461c4ac2c12f9fa5 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_actions.cpp @@ -0,0 +1,62 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include + +using namespace km::kbp::kmx; + +void KMX_Actions::ResetQueue() +{ + QueueSize = 0; +} + +KMX_Actions::KMX_Actions(KMX_Context *context) +{ + m_context = context; + ResetQueue(); +} + +KMX_BOOL KMX_Actions::QueueAction(int ItemType, KMX_DWORD dwData) +{ + if (QueueSize > MAXACTIONQUEUE - 1) + { + DebugLog("App::QueueAction: queue size exceeded"); + return FALSE; + } + + Queue[QueueSize].ItemType = ItemType; + Queue[QueueSize].dwData = dwData; + + QueueSize++; + + int result = TRUE; + + switch(ItemType) + { + case QIT_VKEYDOWN: + break; + + case QIT_DEADKEY: + m_context->Add(UC_SENTINEL); + m_context->Add(CODE_DEADKEY); + m_context->Add((KMX_WORD) dwData); + break; + + case QIT_CHAR: + m_context->Add((KMX_WORD) dwData); + break; + + case QIT_BACK: + if(dwData == BK_BACKSPACE) // User pressed backspace so delete deadkeys + while(m_context->CharIsDeadkey()) m_context->Delete(); + + m_context->Delete(); + + if(dwData == BK_BACKSPACE) // User pressed backspace so delete deadkeys + while(m_context->CharIsDeadkey()) m_context->Delete(); + break; + } + + return result; +} diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_actions.h b/common/engine/keyboardprocessor/src/kmx/kmx_actions.h new file mode 100644 index 0000000000000000000000000000000000000000..82908d3dd06cbd7b23dd8120253101e6b9ebcdbd --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_actions.h @@ -0,0 +1,62 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ + +#pragma once + +#include "kmx_base.h" + +namespace km { +namespace kbp { +namespace kmx { + +#define MAXACTIONQUEUE 1024 + +typedef struct +{ + int ItemType; + KMX_DWORD dwData; +} KMX_Action; + +// QueueAction ItemTypes +#define QIT_VKEYDOWN 0 +#define QIT_VKEYUP 1 +#define QIT_VSHIFTDOWN 2 +#define QIT_VSHIFTUP 3 +#define QIT_CHAR 4 +#define QIT_DEADKEY 5 +#define QIT_BELL 6 +#define QIT_BACK 7 +#define QIT_CAPSLOCK 8 +#define QIT_INVALIDATECONTEXT 9 + +#define QIT_MAX 9 + +#define QVK_EXTENDED 0x00010000 // Flag for QIT_VKEYDOWN to indicate an extended key +#define QVK_KEYMASK 0x0000FFFF +#define QVK_FLAGMASK 0xFFFF0000 + +class KMX_Actions +{ +private: + KMX_Context *m_context; + KMX_Action Queue[MAXACTIONQUEUE]; //TODO turn into List + int QueueSize; + +public: + KMX_Actions(KMX_Context *context); + + /* Queue functions */ + + void ResetQueue(); + KMX_BOOL IsQueueEmpty() { return QueueSize == 0; } + KMX_BOOL QueueAction(int ItemType, KMX_DWORD dwData); + int Length() { return QueueSize; } + KMX_Action Get(int index) { return Queue[index]; } +}; + +} // namespace kmx +} // namespace kbp +} // namespace km + diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_base.h b/common/engine/keyboardprocessor/src/kmx/kmx_base.h new file mode 100644 index 0000000000000000000000000000000000000000..a47ba536218e5f0d9919403a6a923c31d37c018b --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_base.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include + +#if defined(_WIN32) || defined(_WIN64) +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#endif + +#if __x86_64__ +/* 64-bit, g++ */ +#define KMX_64BIT +#endif + +#if defined(_WIN64) && !defined(USE_64) +/* 64-bit, Windows */ +#define KMX_64BIT +#endif + +typedef uint32_t KMX_DWORD; +typedef int32_t KMX_BOOL; +typedef uint8_t KMX_BYTE; +typedef uint16_t KMX_WORD; + +typedef km_kbp_cp KMX_WCHAR; // wc, 16-bit UNICODE character +typedef KMX_WCHAR *PKMX_WCHAR; + +typedef char KMX_CHAR; +typedef KMX_CHAR *PKMX_CHAR; + +typedef uint32_t KMX_UINT; + +typedef KMX_BYTE *PKMX_BYTE; +typedef KMX_WORD *PKMX_WORD; +typedef KMX_DWORD *PKMX_DWORD; + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +namespace km { +namespace kbp { +namespace kmx { + +typedef struct tagSTORE +{ + KMX_DWORD dwSystemID; + PKMX_WCHAR dpName; + PKMX_WCHAR dpString; +} STORE, *LPSTORE; + + +typedef struct tagKEY +{ + KMX_WCHAR Key; + KMX_DWORD Line; + KMX_DWORD ShiftFlags; + PKMX_WCHAR dpOutput; + PKMX_WCHAR dpContext; +} KEY, *LPKEY; + + +typedef struct tagGROUP +{ + PKMX_WCHAR dpName; + LPKEY dpKeyArray; // [LPKEY] address of first item in key array + PKMX_WCHAR dpMatch; + PKMX_WCHAR dpNoMatch; + KMX_DWORD cxKeyArray; // in array entries + KMX_BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not +} GROUP, *LPGROUP; + + +typedef struct tagKEYBOARD +{ + KMX_DWORD dwIdentifier; // Keyman compiled keyboard id + + KMX_DWORD dwFileVersion; // Version of the file - Keyman 4.0 is 0x0400 + + KMX_DWORD dwCheckSum; // As stored in keyboard + KMX_DWORD xxkbdlayout; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts + KMX_DWORD IsRegistered; // layout id, from same key + KMX_DWORD version; // keyboard version + + KMX_DWORD cxStoreArray; // in array entries + KMX_DWORD cxGroupArray; // in array entries + + LPSTORE dpStoreArray; // [LPSTORE] address of first item in store array, from start of file + LPGROUP dpGroupArray; // [LPGROUP] address of first item in group array, from start of file + + KMX_DWORD StartGroup[2]; // index of starting groups [2 of them] + // Ansi=0, Unicode=1 + + KMX_DWORD dwFlags; // Flags for the keyboard file + + KMX_DWORD dwHotKey; // standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) +} KEYBOARD, *LPKEYBOARD; + +typedef struct tagINTKEYBOARDOPTIONS +{ + PKMX_WCHAR Value; + PKMX_WCHAR OriginalStore; +} INTKEYBOARDOPTIONS, *LPINTKEYBOARDOPTIONS; + +// The members of this structure, from first through to IMDLLs, must match KEYBOARDINFO from keymanapi.h +typedef struct tagINTKEYBOARDINFO +{ + KMX_DWORD KeymanID; + char Name[256]; + LPKEYBOARD Keyboard; + LPINTKEYBOARDOPTIONS KeyboardOptions; +} INTKEYBOARDINFO, *LPINTKEYBOARDINFO; + +typedef struct tagKMSTATE +{ + KMX_BOOL StopOutput; + int LoopTimes; + KMX_BOOL isExtended; + KMX_WORD vkey; // I934 + KMX_WCHAR charCode; // I4582 +} KMSTATE; +// I3616 +enum ProcessStringReturn { psrPostMessages, psrCheckMatches }; + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_capslock.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_capslock.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8d509d3757c48f548ad878ea86aa881dc04b7739 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_capslock.cpp @@ -0,0 +1,57 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include + +using namespace km::kbp::kmx; + +void KMX_Processor::ResetCapsLock(void) +{ + DebugLog("ResetCapsLock: enter"); + + if(m_keyboard.Keyboard->dwFlags & KF_CAPSALWAYSOFF) + { + DebugLog("ResetCapsLock: caps lock should be always off"); + if(m_environment.capsLock()) + { + DebugLog("ResetCapsLock: caps lock is on, switching off caps lock"); + m_actions.QueueAction(QIT_CAPSLOCK, 0); + } + } + DebugLog("ResetCapsLock: exit"); +} + + +void KMX_Processor::KeyCapsLockPress(KMX_BOOL FIsUp) +{ + if(m_keyboard.Keyboard->dwFlags & KF_CAPSONONLY) + { + if(FIsUp && !m_environment.capsLock()) + { + m_actions.QueueAction(QIT_CAPSLOCK, 1); + } + } + else if(m_keyboard.Keyboard->dwFlags & KF_CAPSALWAYSOFF) + { + if(!FIsUp && m_environment.capsLock()) + { + m_actions.QueueAction(QIT_CAPSLOCK, 0); + } + } +} + + +void KMX_Processor::KeyShiftPress(KMX_BOOL FIsUp) +{ + if(!m_environment.capsLock()) return; + + if(m_keyboard.Keyboard->dwFlags & KF_SHIFTFREESCAPS) + { + if(!FIsUp) + { + m_actions.QueueAction(QIT_CAPSLOCK, 0); + } + } +} + diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_consts.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_consts.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4e549df995542e9e12c217f808dbdec91531d9fb --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_consts.cpp @@ -0,0 +1,398 @@ + +#include "kmx_processor.h" + +namespace km { +namespace kbp { +namespace kmx { + +const struct char_to_vkey s_char_to_vkey[] = { + {KM_KBP_VKEY_SPACE, 0, 0}, // + {'1', 1, 0}, // ! + {KM_KBP_VKEY_QUOTE, 1, 0}, // " + {'3', 1, 0}, // # + {'4', 1, 0}, // $ + {'5', 1, 0}, // % + {'7', 1, 0}, // & + {KM_KBP_VKEY_QUOTE, 0, 0}, // ' + {'9', 1, 0}, // ( + {'0', 1, 0}, // ) + {'8', 1, 0}, // * + {KM_KBP_VKEY_EQUAL, 1, 0}, // + + {KM_KBP_VKEY_COMMA, 0, 0}, // , + {KM_KBP_VKEY_HYPHEN, 0, 0}, // - + {KM_KBP_VKEY_PERIOD, 0, 0}, // . + {KM_KBP_VKEY_SLASH, 0, 0}, // / + {'0', 0, 0}, + {'1', 0, 0}, + {'2', 0, 0}, + {'3', 0, 0}, + {'4', 0, 0}, + {'5', 0, 0}, + {'6', 0, 0}, + {'7', 0, 0}, + {'8', 0, 0}, + {'9', 0, 0}, + {KM_KBP_VKEY_COLON, 1, 0}, // : + {KM_KBP_VKEY_COLON, 0, 0}, // ; + {KM_KBP_VKEY_COMMA, 1, 0}, // < + {KM_KBP_VKEY_EQUAL, 0, 0}, // = + {KM_KBP_VKEY_PERIOD, 1, 0}, // > + {KM_KBP_VKEY_SLASH, 1, 0}, // ? + {'2', 1, 0}, // @ + {'A', 1, 1}, + {'B', 1, 1}, + {'C', 1, 1}, + {'D', 1, 1}, + {'E', 1, 1}, + {'F', 1, 1}, + {'G', 1, 1}, + {'H', 1, 1}, + {'I', 1, 1}, + {'J', 1, 1}, + {'K', 1, 1}, + {'L', 1, 1}, + {'M', 1, 1}, + {'N', 1, 1}, + {'O', 1, 1}, + {'P', 1, 1}, + {'Q', 1, 1}, + {'R', 1, 1}, + {'S', 1, 1}, + {'T', 1, 1}, + {'U', 1, 1}, + {'V', 1, 1}, + {'W', 1, 1}, + {'X', 1, 1}, + {'Y', 1, 1}, + {'Z', 1, 1}, + {KM_KBP_VKEY_LBRKT, 0, 0}, + {KM_KBP_VKEY_BKSLASH, 0, 0}, + {KM_KBP_VKEY_RBRKT, 0, 0}, + {'6', 1, 0}, + {KM_KBP_VKEY_HYPHEN, 1, 0}, + {KM_KBP_VKEY_BKQUOTE, 0, 0}, + {'A', 0, 1}, + {'B', 0, 1}, + {'C', 0, 1}, + {'D', 0, 1}, + {'E', 0, 1}, + {'F', 0, 1}, + {'G', 0, 1}, + {'H', 0, 1}, + {'I', 0, 1}, + {'J', 0, 1}, + {'K', 0, 1}, + {'L', 0, 1}, + {'M', 0, 1}, + {'N', 0, 1}, + {'O', 0, 1}, + {'P', 0, 1}, + {'Q', 0, 1}, + {'R', 0, 1}, + {'S', 0, 1}, + {'T', 0, 1}, + {'U', 0, 1}, + {'V', 0, 1}, + {'W', 0, 1}, + {'X', 0, 1}, + {'Y', 0, 1}, + {'Z', 0, 1}, + {KM_KBP_VKEY_LBRKT, 1, 0}, + {KM_KBP_VKEY_BKSLASH, 1, 0}, + {KM_KBP_VKEY_RBRKT, 1, 0}, + {KM_KBP_VKEY_BKQUOTE, 1, 0}, + {0, 0, 0} +}; + +const char *s_key_names[] = { + // Key Codes + "K_?00", // &H0 + "K_LBUTTON", // &H1 + "K_RBUTTON", // &H2 + "K_CANCEL", // &H3 + "K_MBUTTON", // &H4 + "K_?05", // &H5 + "K_?06", // &H6 + "K_?07", // &H7 + "K_BKSP", // &H8 + "K_TAB", // &H9 + "K_?0A", // &HA + "K_?0B", // &HB + "K_KP5", // &HC + "K_ENTER", // &HD + "K_?0E", // &HE + "K_?0F", // &HF + "K_SHIFT", // &H10 + "K_CONTROL", // &H11 + "K_ALT", // &H12 + "K_PAUSE", // &H13 + "K_CAPS", // &H14 + "K_KANJI?15", // &H15 + "K_KANJI?16", // &H16 + "K_KANJI?17", // &H17 + "K_KANJI?18", // &H18 + "K_KANJI?19", // &H19 + "K_?1A", // &H1A + "K_ESC", // &H1B + "K_KANJI?1C", // &H1C + "K_KANJI?1D", // &H1D + "K_KANJI?1E", // &H1E + "K_KANJI?1F", // &H1F + "K_SPACE", // &H20 + "K_PGUP", // &H21 + "K_PGDN", // &H22 + "K_END", // &H23 + "K_HOME", // &H24 + "K_LEFT", // &H25 + "K_UP", // &H26 + "K_RIGHT", // &H27 + "K_DOWN", // &H28 + "K_SEL", // &H29 + "K_PRINT", // &H2A + "K_EXEC", // &H2B + "K_PRTSCN", // &H2C + "K_INS", // &H2D + "K_DEL", // &H2E + "K_HELP", // &H2F + "K_0", // &H30 + "K_1", // &H31 + "K_2", // &H32 + "K_3", // &H33 + "K_4", // &H34 + "K_5", // &H35 + "K_6", // &H36 + "K_7", // &H37 + "K_8", // &H38 + "K_9", // &H39 + "K_?3A", // &H3A + "K_?3B", // &H3B + "K_?3C", // &H3C + "K_?3D", // &H3D + "K_?3E", // &H3E + "K_?3F", // &H3F + "K_?40", // &H40 + + "K_A", // &H41 + "K_B", // &H42 + "K_C", // &H43 + "K_D", // &H44 + "K_E", // &H45 + "K_F", // &H46 + "K_G", // &H47 + "K_H", // &H48 + "K_I", // &H49 + "K_J", // &H4A + "K_K", // &H4B + "K_L", // &H4C + "K_M", // &H4D + "K_N", // &H4E + "K_O", // &H4F + "K_P", // &H50 + "K_Q", // &H51 + "K_R", // &H52 + "K_S", // &H53 + "K_T", // &H54 + "K_U", // &H55 + "K_V", // &H56 + "K_W", // &H57 + "K_X", // &H58 + "K_Y", // &H59 + "K_Z", // &H5A + "K_?5B", // &H5B + "K_?5C", // &H5C + "K_?5D", // &H5D + "K_?5E", // &H5E + "K_?5F", // &H5F + "K_NP0", // &H60 + "K_NP1", // &H61 + "K_NP2", // &H62 + "K_NP3", // &H63 + "K_NP4", // &H64 + "K_NP5", // &H65 + "K_NP6", // &H66 + "K_NP7", // &H67 + "K_NP8", // &H68 + "K_NP9", // &H69 + "K_NPSTAR", // &H6A + "K_NPPLUS", // &H6B + "K_SEPARATOR", // &H6C + "K_NPMINUS", // &H6D + "K_NPDOT", // &H6E + "K_NPSLASH", // &H6F + "K_F1", // &H70 + "K_F2", // &H71 + "K_F3", // &H72 + "K_F4", // &H73 + "K_F5", // &H74 + "K_F6", // &H75 + "K_F7", // &H76 + "K_F8", // &H77 + "K_F9", // &H78 + "K_F10", // &H79 + "K_F11", // &H7A + "K_F12", // &H7B + "K_F13", // &H7C + "K_F14", // &H7D + "K_F15", // &H7E + "K_F16", // &H7F + "K_F17", // &H80 + "K_F18", // &H81 + "K_F19", // &H82 + "K_F20", // &H83 + "K_F21", // &H84 + "K_F22", // &H85 + "K_F23", // &H86 + "K_F24", // &H87 + + "K_?88", // &H88 + "K_?89", // &H89 + "K_?8A", // &H8A + "K_?8B", // &H8B + "K_?8C", // &H8C + "K_?8D", // &H8D + "K_?8E", // &H8E + "K_?8F", // &H8F + + "K_NUMLOCK", // &H90 + "K_SCROLL", // &H91 + + "K_?92", // &H92 + "K_?93", // &H93 + "K_?94", // &H94 + "K_?95", // &H95 + "K_?96", // &H96 + "K_?97", // &H97 + "K_?98", // &H98 + "K_?99", // &H99 + "K_?9A", // &H9A + "K_?9B", // &H9B + "K_?9C", // &H9C + "K_?9D", // &H9D + "K_?9E", // &H9E + "K_?9F", // &H9F + "K_?A0", // &HA0 + "K_?A1", // &HA1 + "K_?A2", // &HA2 + "K_?A3", // &HA3 + "K_?A4", // &HA4 + "K_?A5", // &HA5 + "K_?A6", // &HA6 + "K_?A7", // &HA7 + "K_?A8", // &HA8 + "K_?A9", // &HA9 + "K_?AA", // &HAA + "K_?AB", // &HAB + "K_?AC", // &HAC + "K_?AD", // &HAD + "K_?AE", // &HAE + "K_?AF", // &HAF + "K_?B0", // &HB0 + "K_?B1", // &HB1 + "K_?B2", // &HB2 + "K_?B3", // &HB3 + "K_?B4", // &HB4 + "K_?B5", // &HB5 + "K_?B6", // &HB6 + "K_?B7", // &HB7 + "K_?B8", // &HB8 + "K_?B9", // &HB9 + + "K_COLON", // &HBA + "K_EQUAL", // &HBB + "K_COMMA", // &HBC + "K_HYPHEN", // &HBD + "K_PERIOD", // &HBE + "K_SLASH", // &HBF + "K_BKQUOTE", // &HC0 + + "K_?C1", // &HC1 + "K_?C2", // &HC2 + "K_?C3", // &HC3 + "K_?C4", // &HC4 + "K_?C5", // &HC5 + "K_?C6", // &HC6 + "K_?C7", // &HC7 + "K_?C8", // &HC8 + "K_?C9", // &HC9 + "K_?CA", // &HCA + "K_?CB", // &HCB + "K_?CC", // &HCC + "K_?CD", // &HCD + "K_?CE", // &HCE + "K_?CF", // &HCF + "K_?D0", // &HD0 + "K_?D1", // &HD1 + "K_?D2", // &HD2 + "K_?D3", // &HD3 + "K_?D4", // &HD4 + "K_?D5", // &HD5 + "K_?D6", // &HD6 + "K_?D7", // &HD7 + "K_?D8", // &HD8 + "K_?D9", // &HD9 + "K_?DA", // &HDA + + "K_LBRKT", // &HDB + "K_BKSLASH", // &HDC + "K_RBRKT", // &HDD + "K_QUOTE", // &HDE + "K_oDF", // &HDF + "K_oE0", // &HE0 + "K_oE1", // &HE1 + "K_oE2", // &HE2 + "K_oE3", // &HE3 + "K_oE4", // &HE4 + + "K_?E5", // &HE5 + + "K_oE6", // &HE6 + + "K_?E7", // &HE7 + "K_?E8", // &HE8 + + "K_oE9", // &HE9 + "K_oEA", // &HEA + "K_oEB", // &HEB + "K_oEC", // &HEC + "K_oED", // &HED + "K_oEE", // &HEE + "K_oEF", // &HEF + "K_oF0", // &HF0 + "K_oF1", // &HF1 + "K_oF2", // &HF2 + "K_oF3", // &HF3 + "K_oF4", // &HF4 + "K_oF5", // &HF5 + + "K_?F6", // &HF6 + "K_?F7", // &HF7 + "K_?F8", // &HF8 + "K_?F9", // &HF9 + "K_?FA", // &HFA + "K_?FB", // &HFB + "K_?FC", // &HFC + "K_?FD", // &HFD + "K_?FE", // &HFE + "K_?FF" // &HFF +}; + +const struct modifier_names s_modifier_names[] = { + {"LCTRL", 0x0001}, // Left Control flag + {"RCTRL", 0x0002}, // Right Control flag + {"LALT", 0x0004}, // Left Alt flag + {"RALT", 0x0008}, // Right Alt flag + {"SHIFT", 0x0010}, // Either shift flag + {"CTRL-do-not-use", 0x0020}, // Either ctrl flag -- don't use this for inputs + {"ALT-do-not-use", 0x0040}, // Either alt flag -- don't use this for inputs + {"CAPS", 0x0100}, // Caps lock on + {"NCAPS", 0x0200}, // Caps lock NOT on + {"NUMLOCK", 0x0400}, // Num lock on + {"NNUMLOCK", 0x0800}, // Num lock NOT on + {"SCROLL", 0x1000}, // Scroll lock on + {"NSCROLL", 0x2000}, // Scroll lock NOT on + {NULL, 0} +}; + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_context.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_context.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0aa7fb77c5728e0f0bc676f85b2447ec664401be --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_context.cpp @@ -0,0 +1,107 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include + +using namespace km::kbp::kmx; + +/* KMX_Context */ + +KMX_Context::KMX_Context() +{ + Reset(); +} + +void KMX_Context::Add(KMX_WCHAR ch) +{ + if(pos == MAXCONTEXT - 1) + { + memmove(CurContext, &CurContext[1], MAXCONTEXT*2 - 2); pos--; + } + + CurContext[pos++] = ch; + CurContext[pos] = 0; + + DebugLog("KMX_Context: Add(%x) [%d]: %s", ch, pos, Debug_UnicodeString(CurContext)); +} + + +KMX_WCHAR *KMX_Context::Buf(int n) +{ + KMX_WCHAR *p; + + for(p = (KMX_WCHAR *) u16chr(CurContext, 0); n > 0 && p > CurContext; p = decxstr(p), n--); + + if(n > 0) return NULL; + return p; +} + +void KMX_Context::Delete() +{ + if (CharIsDeadkey()) { + pos -= 2; + } else if (CharIsSurrogatePair()) { + pos--; + } + + if(pos > 0) pos--; + CurContext[pos] = 0; +} + +void KMX_Context::Reset() +{ + pos = 0; + CurContext[0] = 0; +} + +void KMX_Context::Get(KMX_WCHAR *buf, int bufsize) +{ + for(KMX_WCHAR *p = CurContext; *p && bufsize > 0; p++, bufsize--) + { + *buf = *p; buf++; + if(*p >= 0xD800 && *p <= 0xDBFF) { *buf = *(++p); bufsize--; buf++; } + } + *buf = 0; +} + +void KMX_Context::CopyFrom(KMX_Context *source) // I3575 +{ + DebugLog("KMX_Context::CopyFrom source=%s; before copy, dest=%s", Debug_UnicodeString(source->CurContext, 0), Debug_UnicodeString(CurContext, 1)); + u16cpy(CurContext, /*_countof(CurContext),*/ source->CurContext); + pos = source->pos; +} + + +void KMX_Context::Set(const KMX_WCHAR *buf) +{ + const KMX_WCHAR *p; + KMX_WCHAR *q; + for(p = buf, q = CurContext; *p && (int)(q-CurContext) < MAXCONTEXT - 1; p++, q++) + { + *q = *p; + if(*p >= 0xD800 && *p <= 0xDBFF) { *(++q) = *(++p); } + } + *q = 0; + pos = (int)(q-CurContext); + CurContext[MAXCONTEXT-1] = 0; +} + +KMX_BOOL KMX_Context::CharIsDeadkey() +{ + if(pos < 3) + return FALSE; + return CurContext[pos-3] == UC_SENTINEL && + CurContext[pos-2] == CODE_DEADKEY; +} + +KMX_BOOL KMX_Context::CharIsSurrogatePair() +{ + if (pos < 2) + return FALSE; + + return Uni_IsSurrogate1(CurContext[pos - 2]) && + Uni_IsSurrogate2(CurContext[pos - 1]); +} + + diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_context.h b/common/engine/keyboardprocessor/src/kmx/kmx_context.h new file mode 100644 index 0000000000000000000000000000000000000000..6aa978ce3651e804a117759117cd41ef71234f17 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_context.h @@ -0,0 +1,37 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ + +#pragma once + +#include "kmx_base.h" + +#define MAXCONTEXT 64 + +namespace km { +namespace kbp { +namespace kmx { + +class KMX_Context +{ +private: + KMX_WCHAR CurContext[MAXCONTEXT]; + int pos; + +public: + KMX_Context(); + void CopyFrom(KMX_Context *source); + void Add(KMX_WCHAR ch); + void Delete(); + void Reset(); + void Get(KMX_WCHAR *buf, int bufsize); + void Set(const KMX_WCHAR *buf); + KMX_WCHAR *Buf(int n); + KMX_BOOL CharIsDeadkey(); + KMX_BOOL CharIsSurrogatePair(); +}; + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_debug.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_debug.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a16dee4c26c7a28c5d84b1b9d231f9396691668e --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_debug.cpp @@ -0,0 +1,158 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include "kmx_processor.h" +#include +#include + +using namespace km::kbp::kmx; + +#define TAB "\t" +#define NL "\n" + +#include + +#ifdef _MSC_VER +#define _USE_WINDOWS +#endif + +#ifdef _USE_WINDOWS +#define DECLSPEC_IMPORT __declspec(dllimport) +#define WINBASEAPI DECLSPEC_IMPORT +#define VOID void +#define WINAPI __stdcall + +extern "C" +__declspec(dllimport) +void +__stdcall +OutputDebugStringA( + char *lpOutputString +); + +#else + #include +#endif + +// simulation of Windows GetTickCount() +unsigned long +GetTickCount() +{ + using namespace std::chrono; + return (unsigned long) duration_cast(steady_clock::now().time_since_epoch()).count(); +} + +int km::kbp::kmx::DebugLog_1(const char *file, int line, const char *function, const char *fmt, ...) +{ + char fmtbuf[256]; + + va_list vars; + va_start(vars, fmt); + vsnprintf(fmtbuf, sizeof(fmtbuf) / sizeof(fmtbuf[0]), fmt, vars); // I2248 // I3547 + fmtbuf[255] = 0; + va_end(vars); + + if(g_debug_KeymanLog) { + if(g_debug_ToConsole) { // I3951 + char windowinfo[1024]; + sprintf(windowinfo, + "%ld" TAB //"TickCount" TAB + "%s:%d" TAB //"SourceFile" TAB + "%s" TAB //"Function" + "%s" NL, //"Message" + + GetTickCount(), //"TickCount" TAB + file, line, //"SourceFile" TAB + function, //"Function" TAB + fmtbuf); //"Message" +#ifdef _USE_WINDOWS + std::cout << windowinfo << std::endl; // OutputDebugStringA(windowinfo); +#else + syslog(LOG_DEBUG, "%s", windowinfo); +#endif + } + } + + return 0; +} + +const char *km::kbp::kmx::Debug_ModifierName(KMX_UINT modifiers) { +#ifdef _MSC_VER + __declspec(thread) +#endif + static char buf[256]; + buf[0] = 0; + for(int i = 0; s_modifier_names[i].name; i++) + if (modifiers & s_modifier_names[i].modifier) { + strcat(buf, " "); + strcat(buf, s_modifier_names[i].name); + } + + if (*buf) return buf + 1; + return "Unmodified"; +} + +const char *km::kbp::kmx::Debug_VirtualKey(KMX_WORD vk) { +#ifdef _MSC_VER + __declspec(thread) +#endif + static char buf[256]; + if (!ShouldDebug()) { + return ""; + } + + if (vk < 256) { + sprintf(buf, "['%s' 0x%x]", s_key_names[vk], vk); + } + else { + sprintf(buf, "[0x%x]", vk); + } + return buf; +} + +const char *km::kbp::kmx::Debug_UnicodeString(PKMX_WCHAR s, int x) { + if (!ShouldDebug()) { + return ""; + } +#ifdef _MSC_VER + __declspec(thread) +#endif + static char bufout[2][128 * 7]; + KMX_WCHAR *p; + char *q; + bufout[x][0] = 0; + for (p = s, q = bufout[x]; *p && (p - s < 128); p++) + { + sprintf(q, "U+%4.4X ", *p); q = strchr(q, 0); + } + //WideCharToMultiByte(CP_ACP, 0, buf, -1, bufout, 128, NULL, NULL); + return bufout[x]; +} + +const char *km::kbp::kmx::Debug_UnicodeString(std::u16string s, int x) { + if (!ShouldDebug()) { + return ""; + } +#ifdef _MSC_VER + __declspec(thread) +#endif + static char bufout[2][128 * 7]; + auto p = s.begin(); + char *q; + bufout[x][0] = 0; + for (q = bufout[x]; (int)(q-bufout[x]) < (128*7) && p != s.end(); p++) + { + sprintf(q, "U+%4.4X ", *p); q = strchr(q, 0); + } + return bufout[x]; +} + +void km::kbp::kmx::write_console(KMX_BOOL error, const wchar_t *fmt, ...) { + if (!g_silent || error) { + va_list vars; + va_start(vars, fmt); + vwprintf(fmt, vars); + va_end(vars); + } +} diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_environment.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_environment.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75ae0f87f35ca4cf2a3ec550e27fcd2438543efe --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_environment.cpp @@ -0,0 +1,69 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include +#include +#include +#include + +using namespace km::kbp::kmx; + +namespace { + km_kbp_cp const + *DEFAULT_PLATFORM = u"windows hardware desktop native", + *DEFAULT_BASELAYOUT = u"kbdus.dll", + *DEFAULT_BASELAYOUTALT = u"en-US", + *DEFAULT_SIMULATEALTGR = u"0", + *DEFAULT_CAPSLOCK = u"0", + *DEFAULT_BASELAYOUTGIVESCTRLRALTFORRALT = u"0"; +} + +KMX_Environment::KMX_Environment() { +} + +void KMX_Environment::InitOption(std::vector *default_env, km_kbp_cp const *key, km_kbp_cp const *default_value) { + km_kbp_option_item opt = { (km_kbp_cp*) key, (km_kbp_cp*) default_value, KM_KBP_OPT_ENVIRONMENT }; + default_env->emplace_back(opt); + Load(key, default_value); +} + +void KMX_Environment::Init(std::vector *default_env) { + InitOption(default_env, KM_KBP_KMX_ENV_PLATFORM, DEFAULT_PLATFORM); + InitOption(default_env, KM_KBP_KMX_ENV_BASELAYOUT, DEFAULT_BASELAYOUT); + InitOption(default_env, KM_KBP_KMX_ENV_BASELAYOUTALT, DEFAULT_BASELAYOUTALT); + InitOption(default_env, KM_KBP_KMX_ENV_SIMULATEALTGR, DEFAULT_SIMULATEALTGR); + InitOption(default_env, KM_KBP_KMX_ENV_CAPSLOCK, DEFAULT_CAPSLOCK); + InitOption(default_env, KM_KBP_KMX_ENV_BASELAYOUTGIVESCTRLRALTFORRALT, DEFAULT_BASELAYOUTGIVESCTRLRALTFORRALT); + + // TODO refactor this and the keyboard option terminator into state.cpp and keyboard.cpp respectively + km_kbp_option_item opt = KM_KBP_OPTIONS_END; + default_env->emplace_back(opt); +} + +void KMX_Environment::Load(std::u16string const & key, std::u16string const & value) { + assert(!key.empty()); + + if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_PLATFORM)) { + _platform = value; + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_BASELAYOUT)) { + _baseLayout = value; + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_BASELAYOUTALT)) { + _baseLayoutAlt = value; + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_SIMULATEALTGR)) { + _simulateAltGr = value == u"1"; + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_CAPSLOCK)) { + _capsLock = value == u"1"; + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_BASELAYOUTGIVESCTRLRALTFORRALT)) { + _baseLayoutGivesCtrlRAltForRAlt = value == u"1"; + } + else { + // Unsupported key + assert(false); + } +} diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_environment.h b/common/engine/keyboardprocessor/src/kmx/kmx_environment.h new file mode 100644 index 0000000000000000000000000000000000000000..34148d86509e6865e2c5375589005b68aa97ae83 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_environment.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include "kmx_base.h" + +namespace km { +namespace kbp { +namespace kmx { + +class KMX_Environment { +private: + KMX_BOOL _simulateAltGr, _baseLayoutGivesCtrlRAltForRAlt; + std::u16string _baseLayout, _baseLayoutAlt; + KMX_BOOL _capsLock; + std::u16string _platform; + void InitOption( + std::vector * default_env, + km_kbp_cp const * key, + km_kbp_cp const * default_value); +public: + KMX_Environment(); + void Load(std::u16string const & key, std::u16string const & value); + void Init(std::vector *default_env); + + KMX_BOOL capsLock() const noexcept { return _capsLock; } + KMX_BOOL simulateAltGr() const noexcept { return _simulateAltGr; } + KMX_BOOL baseLayoutGivesCtrlRAltForRAlt() const noexcept { return _baseLayoutGivesCtrlRAltForRAlt; } + const std::u16string & baseLayout() const noexcept { return _baseLayout; } + const std::u16string & baseLayoutAlt() const noexcept { return _baseLayoutAlt; } + const std::u16string & platform() const noexcept { return _platform; } +}; + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_file.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_file.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4c9715e3882af9b72a7ef72d94cb16828f6237e0 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_file.cpp @@ -0,0 +1,348 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include "kmx_processor.h" + +using namespace km::kbp::kmx; + +#if defined(_WIN32) || defined(_WIN64) +#include +#endif + +KMX_BOOL KMX_Processor::Load(km_kbp_path_name KeyboardName) +{ + if(!LoadKeyboard(KeyboardName, &m_keyboard.Keyboard)) return FALSE; // I5136 + + return TRUE; +} + +const unsigned long CRCTable[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 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, + * and returns the updated value. + */ + +unsigned long CalculateBufferCRC(unsigned long count, KMX_BYTE *p) +{ + unsigned long temp1; + unsigned long temp2; + unsigned long crc = 0xFFFFFFFFL; + + while (count-- != 0) + { + temp1 = ( crc >> 8 ) & 0x00FFFFFFL; + temp2 = CRCTable[((int) crc ^ *p++) & 0xff]; + crc = temp1 ^ temp2; + } + + return crc; +} + +KMX_BOOL KMX_Processor::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKeyboard) +{ + long sz; + PKMX_BYTE buf; + FILE *fp; + LPKEYBOARD kbp; + PKMX_BYTE filebase; + + DebugLog("Loading file %ws", fileName); + if(!fileName || !lpKeyboard) + { + DebugLog("Bad Filename"); + return FALSE; + } + +#if defined(_WIN32) || defined(_WIN64) + fp = _wfsopen(fileName, L"rb", _SH_DENYWR); +#else + fp = fopen(fileName, "rb"); +#endif + if(fp == NULL) + { + DebugLog("Could not open file"); + return FALSE; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + DebugLog("Could not fseek file"); + return FALSE; + } + + sz = ftell(fp); + if (sz < 0) { + fclose(fp); + return FALSE; + } + + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + DebugLog("Could not fseek(set) file"); + return FALSE; + } + +#ifdef KMX_64BIT + buf = new KMX_BYTE[sz*3]; +#else + buf = new KMX_BYTE[sz]; +#endif + + if(!buf) + { + fclose(fp); + DebugLog("Not allocmem"); + return FALSE; + } +#ifdef KMX_64BIT + filebase = buf + sz*2; +#else + filebase = buf; +#endif + + if (fread(filebase, 1, sz, fp) < (size_t) sz) { + fclose(fp); + DebugLog("Could not read file"); + return FALSE; + } + + fclose(fp); + + if(*PKMX_DWORD(filebase) != KMX_DWORD(FILEID_COMPILED)) + { + delete buf; + DebugLog("Invalid file - signature is invalid"); + return FALSE; + } + + if(!VerifyKeyboard(filebase, sz)) return FALSE; + +#ifdef KMX_64BIT + kbp = CopyKeyboard(buf, filebase); +#else + kbp = FixupKeyboard(buf, filebase, sz); +#endif + + if(!kbp) return FALSE; + + if(kbp->dwIdentifier != FILEID_COMPILED) { delete buf; DebugLog("errNotFileID"); return FALSE; } + + *lpKeyboard = kbp; + + return TRUE; +} + +PKMX_WCHAR KMX_Processor::StringOffset(PKMX_BYTE base, KMX_DWORD offset) +{ + if(offset == 0) return NULL; + return (PKMX_WCHAR)(base + offset); +} + +#ifdef KMX_64BIT + +/** + CopyKeyboard will copy the data read into bufp from x86-sized structures into x64-sized structures starting at base + * We know the base is dwFileSize * 3 + * After this function finishes, we still need to keep the original data +*/ +LPKEYBOARD KMX_Processor::CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) +{ + PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) base; + + /* Copy keyboard structure */ + + LPKEYBOARD kbp = (LPKEYBOARD) bufp; + bufp += sizeof(KEYBOARD); + + kbp->dwIdentifier = ckbp->dwIdentifier; + kbp->dwFileVersion = ckbp->dwFileVersion; + kbp->dwCheckSum = ckbp->dwCheckSum; + kbp->xxkbdlayout = ckbp->KeyboardID; + kbp->IsRegistered = ckbp->IsRegistered; + kbp->version = ckbp->version; + kbp->cxStoreArray = ckbp->cxStoreArray; + kbp->cxGroupArray = ckbp->cxGroupArray; + kbp->StartGroup[0] = ckbp->StartGroup[0]; + kbp->StartGroup[1] = ckbp->StartGroup[1]; + kbp->dwFlags = ckbp->dwFlags; + kbp->dwHotKey = ckbp->dwHotKey; + + kbp->dpStoreArray = (LPSTORE) bufp; + bufp += sizeof(STORE) * kbp->cxStoreArray; + + kbp->dpGroupArray = (LPGROUP) bufp; + bufp += sizeof(GROUP) * kbp->cxGroupArray; + + PCOMP_STORE csp; + LPSTORE sp; + KMX_DWORD i; + + for( + csp = (PCOMP_STORE)(base + ckbp->dpStoreArray), sp = kbp->dpStoreArray, i = 0; + i < kbp->cxStoreArray; + i++, sp++, csp++) + { + sp->dwSystemID = csp->dwSystemID; + sp->dpName = StringOffset(base, csp->dpName); + sp->dpString = StringOffset(base, csp->dpString); + } + + PCOMP_GROUP cgp; + LPGROUP gp; + + for( + cgp = (PCOMP_GROUP)(base + ckbp->dpGroupArray), gp = kbp->dpGroupArray, i = 0; + i < kbp->cxGroupArray; + i++, gp++, cgp++) + { + gp->dpName = (PKMX_WCHAR)(base + cgp->dpName); + gp->dpKeyArray = (LPKEY) bufp; + gp->cxKeyArray = cgp->cxKeyArray; + bufp += sizeof(KEY) * gp->cxKeyArray; + gp->dpMatch = StringOffset(base, cgp->dpMatch); + gp->dpNoMatch = StringOffset(base, cgp->dpNoMatch); + gp->fUsingKeys = cgp->fUsingKeys; + + PCOMP_KEY ckp; + LPKEY kp; + KMX_DWORD j; + + for( + ckp = (PCOMP_KEY)(base + cgp->dpKeyArray), kp = gp->dpKeyArray, j = 0; + j < gp->cxKeyArray; + j++, kp++, ckp++) + { + kp->Key = ckp->Key; + kp->Line = ckp->Line; + kp->ShiftFlags = ckp->ShiftFlags; + kp->dpOutput = StringOffset(base, ckp->dpOutput); + kp->dpContext = StringOffset(base, ckp->dpContext); + } + } + + return kbp; +} + +#else + +LPKEYBOARD KMX_Processor::FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWORD dwFileSize) +{ + KMX_DWORD i, j; + PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) base; + PCOMP_GROUP cgp; + PCOMP_STORE csp; + PCOMP_KEY ckp; + LPKEYBOARD kbp = (LPKEYBOARD) bufp; + LPSTORE sp; + LPGROUP gp; + LPKEY kp; + + kbp->dpStoreArray = (LPSTORE) (base + ckbp->dpStoreArray); + kbp->dpGroupArray = (LPGROUP) (base + ckbp->dpGroupArray); + + for(sp = kbp->dpStoreArray, csp = (PCOMP_STORE) sp, i = 0; i < kbp->cxStoreArray; i++, sp++, csp++) + { + sp->dpName = StringOffset(base, csp->dpName); + sp->dpString = StringOffset(base, csp->dpString); + } + + for(gp = kbp->dpGroupArray, cgp = (PCOMP_GROUP) gp, i = 0; i < kbp->cxGroupArray; i++, gp++, cgp++) + { + gp->dpName = StringOffset(base, cgp->dpName); + gp->dpKeyArray = (LPKEY) (base + cgp->dpKeyArray); + if(cgp->dpMatch != NULL) gp->dpMatch = (PKMX_WCHAR) (base + cgp->dpMatch); + if(cgp->dpNoMatch != NULL) gp->dpNoMatch = (PKMX_WCHAR) (base + cgp->dpNoMatch); + + for(kp = gp->dpKeyArray, ckp = (PCOMP_KEY) kp, j = 0; j < gp->cxKeyArray; j++, kp++, ckp++) + { + kp->dpOutput = (PKMX_WCHAR) (base + ckp->dpOutput); + kp->dpContext = (PKMX_WCHAR) (base + ckp->dpContext); + } + } + + return kbp; +} + +#endif + +KMX_BOOL KMX_Processor::VerifyChecksum(PKMX_BYTE buf, KMX_DWORD sz) +{ + KMX_DWORD tempcs; + PCOMP_KEYBOARD ckbp; + + ckbp = (PCOMP_KEYBOARD) buf; + + tempcs = ckbp->dwCheckSum; + ckbp->dwCheckSum = 0; + + return tempcs == CalculateBufferCRC(sz, buf); +} + +KMX_BOOL KMX_Processor::VerifyKeyboard(PKMX_BYTE filebase, KMX_DWORD sz) +{ + KMX_DWORD i; + PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) filebase; + PCOMP_STORE csp; + + /* Check file version */ + + if(ckbp->dwFileVersion < VERSION_MIN || + ckbp->dwFileVersion > VERSION_MAX) + { + /* Old or new version -- identify the desired program version */ + if(VerifyChecksum(filebase, sz)) + { + for(csp = (PCOMP_STORE)(filebase + ckbp->dpStoreArray), i = 0; i < ckbp->cxStoreArray; i++, csp++) + if(csp->dwSystemID == TSS_COMPILEDVERSION) + { + if(csp->dpString == 0) + DebugLog("errWrongFileVersion:NULL"); + else + DebugLog("errWrongFileVersion:%10.10ls", StringOffset(filebase, csp->dpString)); + return FALSE; + } + } + DebugLog("errWrongFileVersion"); + return FALSE; + } + + if(!VerifyChecksum(filebase, sz)) { DebugLog("errBadChecksum"); return FALSE; } + + return TRUE; +} diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_file.h b/common/engine/keyboardprocessor/src/kmx/kmx_file.h new file mode 100644 index 0000000000000000000000000000000000000000..d271bc2e08fba947ebd664358c212978616e425f --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_file.h @@ -0,0 +1,275 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ + +#pragma once + +#include "kmx_base.h" + +namespace km { +namespace kbp { +namespace kmx { + +/* */ + +#define KEYMAN_LAYOUT_DEFAULT 0x000005FE + +#define KEYMANID_NONKEYMAN 0xFFFFFFFF +#define KEYMANID_IGNORE 0xFFFFFFFE +#define KEYMANID_INVALID 0xFFFFFFFD + +/* Shift flags for hotkeys (version 1.0) */ + +#define SHIFTFLAG 0x2000 +#define CTRLFLAG 0x4000 +#define ALTFLAG 0x8000 + +/* Miscellaneous flags and defines */ + +#define MAXGROUPS 128 + +/* File version identifiers */ + +#define VERSION_30 0x00000300 +#define VERSION_31 0x00000301 +#define VERSION_32 0x00000302 +#define VERSION_40 0x00000400 +#define VERSION_50 0x00000500 +#define VERSION_501 0x00000501 +#define VERSION_60 0x00000600 +#define VERSION_70 0x00000700 +#define VERSION_80 0x00000800 +#define VERSION_90 0x00000900 +#define VERSION_100 0x00000A00 + +#define VERSION_MIN VERSION_50 +#define VERSION_MAX VERSION_100 + +// +// Backspace types +// + +#define BK_DEFAULT 0 +#define BK_DEADKEY 1 +#define BK_BACKSPACE 2 + +// Different begin types +#define BEGIN_ANSI 0 +#define BEGIN_UNICODE 1 + +#define TSS_NONE 0 +#define TSS_BITMAP 1 +#define TSS_COPYRIGHT 2 +#define TSS_HOTKEY 3 +#define TSS_LANGUAGE 4 +#define TSS_LAYOUT 5 +#define TSS_MESSAGE 6 +#define TSS_NAME 7 +#define TSS_VERSION 8 +#define TSS_CAPSONONLY 9 +#define TSS_CAPSALWAYSOFF 10 +#define TSS_SHIFTFREESCAPS 11 +#define TSS_LANGUAGENAME 12 + +#define TSS_CALLDEFINITION 13 +#define TSS_CALLDEFINITION_LOADFAILED 14 + +#define TSS_ETHNOLOGUECODE 15 + +#define TSS_DEBUG_LINE 16 + +#define TSS_MNEMONIC 17 + +#define TSS_INCLUDECODES 18 + +#define TSS_OLDCHARPOSMATCHING 19 + +#define TSS_COMPILEDVERSION 20 +#define TSS_KEYMANCOPYRIGHT 21 + +#define TSS_CUSTOMKEYMANEDITION 22 +#define TSS_CUSTOMKEYMANEDITIONNAME 23 + +/* Keyman 7.0 system stores */ + +#define TSS__KEYMAN_60_MAX 23 + +#define TSS_VISUALKEYBOARD 24 +#define TSS_KMW_RTL 25 +#define TSS_KMW_HELPFILE 26 +#define TSS_KMW_HELPTEXT 27 +#define TSS_KMW_EMBEDJS 28 + +#define TSS_WINDOWSLANGUAGES 29 + +#define TSS__KEYMAN_70_MAX 29 + +/* Keyman 8.0 system stores */ + +#define TSS_COMPARISON 30 + +/* Keyman 9.0 system stores */ + +#define TSS_PLATFORM 31 +#define TSS_BASELAYOUT 32 +#define TSS_LAYER 33 + +#define TSS_PLATFORM_NOMATCH 0x8001 // Reserved for internal use - after platform statement is run, set to either TSS_PLATFORM_NOMATCH or TSS_PLATFORM_MATCH +#define TSS_PLATFORM_MATCH 0x8002 // Reserved for internal use - as the result will never change for the lifetime of the process. + +#define TSS_VKDICTIONARY 34 // Dictionary of virtual key names for v9 dynamic layouts +#define TSS_LAYOUTFILE 35 // Keyman 9 layer-based JSON OSK +#define TSS_KEYBOARDVERSION 36 // &keyboardversion system store // I4140 +#define TSS_KMW_EMBEDCSS 37 + +#define TSS_TARGETS 38 + +#define TSS__MAX 38 + +/* wm_keyman_control_internal message control codes */ + +#define KMCI_SELECTKEYBOARD 3 // I3933 +#define KMCI_SELECTKEYBOARD_TSF 4 // I3933 +#define KMCI_GETACTIVEKEYBOARD 5 // I3933 +#define KMCI_SETFOREGROUND 6 // I3933 +#define KMCI_SELECTKEYBOARD_BACKGROUND 7 // I4271 +#define KMCI_SELECTKEYBOARD_BACKGROUND_TSF 8 // I4271 + +#define FILEID_COMPILED 0x5354584B + +#define SZMAX_LANGUAGENAME 80 +#define SZMAX_KEYBOARDNAME 80 +#define SZMAX_COPYRIGHT 256 +#define SZMAX_MESSAGE 1024 + +#define UC_SENTINEL 0xFFFF +#define UC_SENTINEL_EXTENDEDEND 0x10 // was ((CODE_LASTCODE)+1)... what was I thinking? + +/* + * VK__MAX defines the highest virtual key code defined in the system = 0xFF. Custom VK codes start at 256 + */ +#define VK__MAX 255 + +#define CODE_ANY 0x01 +#define CODE_INDEX 0x02 +#define CODE_CONTEXT 0x03 +#define CODE_NUL 0x04 +#define CODE_USE 0x05 +#define CODE_RETURN 0x06 +#define CODE_BEEP 0x07 +#define CODE_DEADKEY 0x08 +// 0x09 = bkspace.-- we don't need to keep this separate though with UC_SENTINEL +#define CODE_EXTENDED 0x0A +//#define CODE_EXTENDEDEND 0x0B deprecated +#define CODE_SWITCH 0x0C +#define CODE_KEY 0x0D +#define CODE_CLEARCONTEXT 0x0E +#define CODE_CALL 0x0F +// UC_SENTINEL_EXTENDEDEND 0x10 +#define CODE_CONTEXTEX 0x11 + +#define CODE_NOTANY 0x12 + +#define CODE_KEYMAN70_LASTCODE 0x12 + +#define CODE_SETOPT 0x13 +#define CODE_IFOPT 0x14 +#define CODE_SAVEOPT 0x15 +#define CODE_RESETOPT 0x16 + +#define CODE_KEYMAN80_LASTCODE 0x16 + +/* Keyman 9.0 codes */ + +#define CODE_IFSYSTEMSTORE 0x17 +#define CODE_SETSYSTEMSTORE 0x18 + +#define CODE_LASTCODE 0x18 + +#define KF_SHIFTFREESCAPS 0x0001 +#define KF_CAPSONONLY 0x0002 +#define KF_CAPSALWAYSOFF 0x0004 +#define KF_LOGICALLAYOUT 0x0008 +#define KF_AUTOMATICVERSION 0x0010 + +#define HK_ALT 0x00010000 +#define HK_CTRL 0x00020000 +#define HK_SHIFT 0x00040000 + +#define LCTRLFLAG 0x0001 // Left Control flag +#define RCTRLFLAG 0x0002 // Right Control flag +#define LALTFLAG 0x0004 // Left Alt flag +#define RALTFLAG 0x0008 // Right Alt flag +#define K_SHIFTFLAG 0x0010 // Either shift flag +#define K_CTRLFLAG 0x0020 // Either ctrl flag +#define K_ALTFLAG 0x0040 // Either alt flag +#define CAPITALFLAG 0x0100 // Caps lock on +#define NOTCAPITALFLAG 0x0200 // Caps lock NOT on +#define NUMLOCKFLAG 0x0400 // Num lock on +#define NOTNUMLOCKFLAG 0x0800 // Num lock NOT on +#define SCROLLFLAG 0x1000 // Scroll lock on +#define NOTSCROLLFLAG 0x2000 // Scroll lock NOT on +#define ISVIRTUALKEY 0x4000 // It is a Virtual Key Sequence +#define VIRTUALCHARKEY 0x8000 // Keyman 6.0: Virtual Key Cap Sequence NOT YET + +#define K_MODIFIERFLAG 0x007F +#define K_NOTMODIFIERFLAG 0xFF00 // I4548 + +struct COMP_STORE { + KMX_DWORD dwSystemID; + KMX_DWORD dpName; + KMX_DWORD dpString; + }; + +struct COMP_KEY { + KMX_WORD Key; + KMX_DWORD Line; + KMX_DWORD ShiftFlags; + KMX_DWORD dpOutput; + KMX_DWORD dpContext; + }; + +struct COMP_GROUP { + KMX_DWORD dpName; + KMX_DWORD dpKeyArray; // [LPKEY] address of first item in key array + KMX_DWORD dpMatch; + KMX_DWORD dpNoMatch; + KMX_DWORD cxKeyArray; // in array entries + KMX_BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not + }; + +struct COMP_KEYBOARD { + KMX_DWORD dwIdentifier; // 0000 Keyman compiled keyboard id + + KMX_DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400 + + KMX_DWORD dwCheckSum; // 0008 As stored in keyboard + KMX_DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts + KMX_DWORD IsRegistered; // 0010 + KMX_DWORD version; // 0014 keyboard version + + KMX_DWORD cxStoreArray; // 0018 in array entries + KMX_DWORD cxGroupArray; // 001C in array entries + + KMX_DWORD dpStoreArray; // 0020 [LPSTORE] address of first item in store array + KMX_DWORD dpGroupArray; // 0024 [LPGROUP] address of first item in group array + + KMX_DWORD StartGroup[2]; // 0028 index of starting groups [2 of them] + + KMX_DWORD dwFlags; // 0030 Flags for the keyboard file + + KMX_DWORD dwHotKey; // 0034 standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) + + KMX_DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file + KMX_DWORD dwBitmapSize; // 003C size in bytes of the bitmaps + }; + +typedef COMP_KEYBOARD *PCOMP_KEYBOARD; +typedef COMP_STORE *PCOMP_STORE; +typedef COMP_KEY *PCOMP_KEY; +typedef COMP_GROUP *PCOMP_GROUP; + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_modifiers.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_modifiers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aafc0ab27b425a41f6864b69f5fa7d212cbfe0fa --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_modifiers.cpp @@ -0,0 +1,140 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include "kmx_processor.h" + +using namespace km::kbp::kmx; + +#define MAX_RSHIFT 24 +#define MAX_KSHIFT 18 + +static KMX_DWORD legalKeyStates[MAX_KSHIFT] = { + 0, LCTRLFLAG, RCTRLFLAG, LALTFLAG, LALTFLAG|LCTRLFLAG, LALTFLAG|RCTRLFLAG, RALTFLAG, RALTFLAG|LCTRLFLAG, RALTFLAG|RCTRLFLAG, + K_SHIFTFLAG, K_SHIFTFLAG|LCTRLFLAG, K_SHIFTFLAG|RCTRLFLAG, K_SHIFTFLAG|LALTFLAG, K_SHIFTFLAG|LALTFLAG|LCTRLFLAG, K_SHIFTFLAG|LALTFLAG|RCTRLFLAG, K_SHIFTFLAG|RALTFLAG, K_SHIFTFLAG|RALTFLAG|LCTRLFLAG, K_SHIFTFLAG|RALTFLAG|RCTRLFLAG +}; + +static KMX_DWORD legalRuleStates[MAX_RSHIFT] = { + 0, LCTRLFLAG, RCTRLFLAG, LALTFLAG, LALTFLAG|LCTRLFLAG, LALTFLAG|RCTRLFLAG, RALTFLAG, RALTFLAG|LCTRLFLAG, RALTFLAG|RCTRLFLAG, + K_SHIFTFLAG, K_SHIFTFLAG|LCTRLFLAG, K_SHIFTFLAG|RCTRLFLAG, K_SHIFTFLAG|LALTFLAG, K_SHIFTFLAG|LALTFLAG|LCTRLFLAG, K_SHIFTFLAG|LALTFLAG|RCTRLFLAG, K_SHIFTFLAG|RALTFLAG, K_SHIFTFLAG|RALTFLAG|LCTRLFLAG, K_SHIFTFLAG|RALTFLAG|RCTRLFLAG, + K_CTRLFLAG, K_ALTFLAG, K_CTRLFLAG|K_ALTFLAG, K_SHIFTFLAG|K_CTRLFLAG, K_SHIFTFLAG|K_ALTFLAG, K_SHIFTFLAG|K_CTRLFLAG|K_ALTFLAG +}; + +// Simulate RALT -> LCtrl+RAlt -> columns marked with * +// +// Truth table: matching key states +// 1 = yes match always +// 2 = RAlt mapped as LCtrl+RAlt by keyboard driver +// 4 = Simulate RAlt with Ctrl+Alt +// +// x = key states, y = rule states +// CAPITAL LETTERS = RIGHT KEY, LOWER CASE LETTERS = LEFT KEY +static KMX_BYTE states[MAX_RSHIFT][MAX_KSHIFT] = { + // Simulate RAlt ==> * * + // 0 c C a ac aC A Ac AC s sc sC sa sac saC sA sAc sAC <-- Key states + /* 0 */ { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* c */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* C */ { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* a */ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* ac */ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* aC */ { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* A */ { 0, 0, 0, 0, 4, 4, 1, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* Ac */ { 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Note: RALT will also trigger LCtrl+RAlt because we cannot distinguish reliably through TSF??? + /* AC */ { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* s */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* sc */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, + /* sC */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, + /* sa */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, + /* sac */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }, + /* saC */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, + /* sA */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 1, 6, 4 }, + /* sAc */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 }, + /* sAC */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, + + /* Either Ctrl/Alt rules */ + + // 0 c C a ac aC A Ac AC s sc sC sa sac saC sA sAc sAC <-- Key states + /* C */ { 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* A */ { 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* CA */ { 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* SC */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, + /* SA */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0 }, + /* SCA */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1 } + + /* ^ rule states */ +}; + + +/* +* KMX_BOOL IsEquivalentShift( KMX_UINT rshift, KMX_UINT kshift ); +* +* Parameters: rshift Rule shift state flag set to compare. +* kshift Current shift state flag set to compare. +* +* Returns: TRUE if the shift states are equivalent. +* +* IsEquivalentShift will compare rshift and kshift and check the generic +* K_CTRLFLAG and K_ALTFLAG as well as specific keys correctly. +*/ + +KMX_BOOL KMX_Processor::IsEquivalentShift(KMX_UINT rshift, KMX_UINT kshift) { + // + // The rule shift must have ISVIRTUALKEY bit set for virt.keys + // + + if(rshift == 0) return FALSE; + + // + // Test CAPS-specific rules + // + + if( (rshift & NOTCAPITALFLAG) && (kshift & CAPITALFLAG) ) return FALSE; + if( (rshift & CAPITALFLAG) && !(kshift & CAPITALFLAG) ) return FALSE; + + // + // Ignore CAPS, NUM and SCROLL + // + + rshift &= K_MODIFIERFLAG; + kshift &= K_MODIFIERFLAG; + + // + // Find rule state index and key state index + // If not found, it's not a supported combination, e.g. LCTRL|RCTRL + // TODO: unwind these loops if you're feeling optimistic + // + + int i; + + for(i = 0; i < MAX_RSHIFT; i++) { + if(rshift == legalRuleStates[i]) { + rshift = i; + break; + } + } + if(i == MAX_RSHIFT) return FALSE; + + for(i = 0; i < MAX_KSHIFT; i++) { + if(kshift == legalKeyStates[i]) { + kshift = i; + break; + } + } + if(i == MAX_KSHIFT) return FALSE; + + // + // Time of truth: is it a valid state? + // + + switch(states[rshift][kshift]) { + case 0: return FALSE; + case 1: return TRUE; + case 2: return m_environment.baseLayoutGivesCtrlRAltForRAlt(); // This state is used when TSF gives us a bogus RALT instead of LCtrl+RAlt + case 4: return m_environment.simulateAltGr(); // I4583 + case 6: return m_environment.baseLayoutGivesCtrlRAltForRAlt() || m_environment.simulateAltGr(); // I4583 + } + + return FALSE; // should never happen +} + + diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_options.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_options.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d553fa7c98dd3707d0a7aa8a2c2362f2b33d2bc --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_options.cpp @@ -0,0 +1,184 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include "kmx_processor.h" +#include +#include + +using namespace km::kbp::kmx; + +void KMX_Options::AddOptionsStoresFromXString(PKMX_WCHAR s) { + int idx; + for (; s && *s; s = incxstr(s)) { + if (*s == UC_SENTINEL) { + switch (*(s + 1)) { + case CODE_IFOPT: + case CODE_SETOPT: + case CODE_SAVEOPT: + case CODE_RESETOPT: + idx = *(s + 2) - 1; + if (idx >= 0 && static_cast(idx) < _kp->Keyboard->cxStoreArray && _kp->Keyboard->dpStoreArray[idx].dpName != NULL) { + _kp->KeyboardOptions[idx].OriginalStore = _kp->Keyboard->dpStoreArray[idx].dpString; + } + break; + } + } + } +} + +void KMX_Options::Load(km_kbp_options *options, std::u16string const &key) { + LPSTORE sp; + KMX_DWORD i; + + assert(options != nullptr); + assert(!key.empty()); + + if (options == nullptr || key.empty()) return; + + for (i = 0, sp = _kp->Keyboard->dpStoreArray; i < _kp->Keyboard->cxStoreArray; i++, sp++) { + if (_kp->KeyboardOptions[i].OriginalStore != NULL && sp->dpName != NULL && u16icmp(sp->dpName, key.c_str()) == 0) { + Reset(options, i); + return; + } + } + + // No entry was found. + assert(false); +} + +void KMX_Options::Init(std::vector *opts) { + + opts->clear(); + + _kp->KeyboardOptions = new INTKEYBOARDOPTIONS[_kp->Keyboard->cxStoreArray]; + memset(_kp->KeyboardOptions, 0, sizeof(INTKEYBOARDOPTIONS) * _kp->Keyboard->cxStoreArray); + + // Scan all rules to find options references. + + KMX_DWORD i, j; + LPGROUP gp; + LPKEY kkp; + for (i = 0, gp = _kp->Keyboard->dpGroupArray; i < _kp->Keyboard->cxGroupArray; i++, gp++) { + for (j = 0, kkp = gp->dpKeyArray; j < gp->cxKeyArray; j++, kkp++) { + AddOptionsStoresFromXString(kkp->dpContext); + AddOptionsStoresFromXString(kkp->dpOutput); + } + AddOptionsStoresFromXString(gp->dpMatch); + AddOptionsStoresFromXString(gp->dpNoMatch); + } + + LPINTKEYBOARDOPTIONS ko; + int n = 0; + for (i = 0, ko = _kp->KeyboardOptions; i < _kp->Keyboard->cxStoreArray; i++, ko++) { + if (ko->OriginalStore != NULL) { + n++; + } + } + + if (n == 0) { + km_kbp_option_item opt = KM_KBP_OPTIONS_END; + opts->emplace_back(opt); + return; + } + + // Setup the default options for KPAPI to maintain + + LPSTORE sp; + for (n = 0, i = 0, ko = _kp->KeyboardOptions, sp = _kp->Keyboard->dpStoreArray; i < _kp->Keyboard->cxStoreArray; i++, sp++, ko++) { + if (ko->OriginalStore == NULL) continue; + km_kbp_option_item opt; + opt.key = sp->dpName; + opt.value = sp->dpString; + opt.scope = KM_KBP_OPT_KEYBOARD; + opts->emplace_back(opt); + n++; + } + + km_kbp_option_item opt = KM_KBP_OPTIONS_END; + opts->emplace_back(opt); +} + +KMX_Options::~KMX_Options() +{ + //TODO: move ownership of _kp->KeyboardOptions into this class (so we control lifetime of it here) + if (!_kp || !_kp->Keyboard || !_kp->KeyboardOptions) return; + + for(KMX_DWORD i = 0; i < _kp->Keyboard->cxStoreArray; i++) + if(_kp->KeyboardOptions[i].Value) + { + _kp->Keyboard->dpStoreArray[i].dpString = _kp->KeyboardOptions[i].OriginalStore; + delete _kp->KeyboardOptions[i].Value; + } + delete _kp->KeyboardOptions; + _kp->KeyboardOptions = NULL; +} + +void KMX_Options::Set(int nStoreToSet, int nStoreToRead) +{ + assert(_kp != NULL); + assert(_kp->Keyboard != NULL); + assert(_kp->KeyboardOptions != NULL); + assert(nStoreToSet >= 0); + assert(nStoreToSet < (int) _kp->Keyboard->cxStoreArray); + assert(nStoreToRead >= 0); + assert(nStoreToRead < (int) _kp->Keyboard->cxStoreArray); + + LPSTORE sp = &_kp->Keyboard->dpStoreArray[nStoreToRead]; + if(_kp->KeyboardOptions[nStoreToSet].Value) + { + delete _kp->KeyboardOptions[nStoreToSet].Value; + } + + _kp->KeyboardOptions[nStoreToSet].Value = new KMX_WCHAR[u16len(sp->dpString)+1]; + u16cpy(_kp->KeyboardOptions[nStoreToSet].Value, /*u16len(sp->dpString)+1,*/ sp->dpString); + _kp->Keyboard->dpStoreArray[nStoreToSet].dpString = _kp->KeyboardOptions[nStoreToSet].Value; +} + +void KMX_Options::Reset(km_kbp_options *options, int nStoreToReset) +{ + assert(_kp != NULL); + assert(_kp->Keyboard != NULL); + assert(_kp->KeyboardOptions != NULL); + assert(nStoreToReset >= 0); + assert(nStoreToReset < (int) _kp->Keyboard->cxStoreArray); + + if (_kp->KeyboardOptions[nStoreToReset].Value) + { + _kp->Keyboard->dpStoreArray[nStoreToReset].dpString = _kp->KeyboardOptions[nStoreToReset].OriginalStore; + delete _kp->KeyboardOptions[nStoreToReset].Value; + _kp->KeyboardOptions[nStoreToReset].Value = NULL; + } + + if(_kp->Keyboard->dpStoreArray[nStoreToReset].dpName == NULL) return; + + // Now we need to go back and get any saved value from KPAPI. internal_value is owned by options api + km_kbp_cp const *internal_value = options->lookup(km_kbp_option_scope(KM_KBP_OPT_KEYBOARD), _kp->Keyboard->dpStoreArray[nStoreToReset].dpName); + if(internal_value) { + // Copy the value from KPAPI + _kp->KeyboardOptions[nStoreToReset].Value = new KMX_WCHAR[u16len(internal_value) + 1]; + u16cpy(_kp->KeyboardOptions[nStoreToReset].Value, /*u16len(val) + 1,*/ internal_value); + _kp->Keyboard->dpStoreArray[nStoreToReset].dpString = _kp->KeyboardOptions[nStoreToReset].Value; + } +} + + +void KMX_Options::Save(km_kbp_state *state, int nStoreToSave) +{ + assert(_kp != NULL); + assert(_kp->Keyboard != NULL); + assert(_kp->KeyboardOptions != NULL); + assert(nStoreToSave >= 0); + assert(nStoreToSave < (int)_kp->Keyboard->cxStoreArray); + + if (_kp->Keyboard->dpStoreArray[nStoreToSave].dpName == NULL) return; + + // TSE QUERY: Does this happen here or with an ACTION? Or both? + state->options().assign(state, KM_KBP_OPT_KEYBOARD, _kp->Keyboard->dpStoreArray[nStoreToSave].dpName, _kp->Keyboard->dpStoreArray[nStoreToSave].dpString); + //TODO: GetActions()->QueueAction(QIT_SAVEOPTION, ...); + /*RegistryFullAccess r(HKEY_CURRENT_USER); + if(r.OpenKey(REGSZ_KeymanActiveKeyboards, TRUE) && r.OpenKey(_kp->Name, TRUE) && r.OpenKey(key, TRUE)) + { + r.WriteString(_kp->Keyboard->dpStoreArray[nStoreToSave].dpName, _kp->Keyboard->dpStoreArray[nStoreToSave].dpString); + }*/ +} diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_options.h b/common/engine/keyboardprocessor/src/kmx/kmx_options.h new file mode 100644 index 0000000000000000000000000000000000000000..29df0d9e6b4353294442af6e16db7af67555ba9f --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_options.h @@ -0,0 +1,33 @@ + +#pragma once + +#include +#include +#include +#include "kmx_base.h" + +namespace km { +namespace kbp { +namespace kmx { + +class KMX_Options +{ +private: + LPINTKEYBOARDINFO _kp; + + void AddOptionsStoresFromXString(PKMX_WCHAR s); + +public: + KMX_Options(LPINTKEYBOARDINFO kp) : _kp(kp) {} + ~KMX_Options(); + + void Init(std::vector *opts); + void Load(km_kbp_options *options, std::u16string const &key); + void Set(int nStoreToSet, int nStoreToRead); + void Reset(km_kbp_options *options, int nStoreToReset); + void Save(km_kbp_state *state, int nStoreToSave); +}; + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_processevent.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_processevent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2af29dcd6bc001bee60635addea98c566c7a76d9 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_processevent.cpp @@ -0,0 +1,160 @@ +#include +#include "processor.hpp" +#include "state.hpp" +#include "keyboard.hpp" + +namespace km { + namespace kbp + { + + km_kbp_status kmx_processor::validate() const { + return _valid ? KM_KBP_STATUS_OK : KM_KBP_STATUS_INVALID_KEYBOARD; + } + + kmx_processor::kmx_processor(km_kbp_keyboard_attrs const * kb_) : abstract_processor(kb_) { + km::kbp::keyboard *kb = const_cast(static_cast(kb_)); + + std::filesystem::path p = kb->folder_path; + p /= kb->id; + p.replace_extension(".kmx"); + _valid = (bool) _kmx.Load(p.native().c_str()); + + if (_valid) { + _kmx.GetOptions()->Init(kb->default_opts()); + } + } + + void kmx_processor::init_state(std::vector *default_env) { + _kmx.GetEnvironment()->Init(default_env); + } + + void kmx_processor::update_option(km_kbp_state *state, km_kbp_option_scope scope, std::u16string const & key, std::u16string const & value) { + switch(scope) { + case KM_KBP_OPT_KEYBOARD: + _kmx.GetOptions()->Load(km_kbp_state_options(state), key); + break; + case KM_KBP_OPT_ENVIRONMENT: + _kmx.GetEnvironment()->Load(key, value); + break; + default: + break; + } + } + + km_kbp_status kmx_processor::process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) { + // Construct a context buffer from the items + + std::u16string ctxt; + auto cp = state->context(); + for (auto c = cp.begin(); c != cp.end(); c++) { + switch (c->type) { + case KM_KBP_CT_CHAR: + if (Uni_IsSMP(c->character)) { + ctxt += Uni_UTF32ToSurrogate1(c->character); + ctxt += Uni_UTF32ToSurrogate2(c->character); + } + else { + ctxt += (km_kbp_cp) c->character; + } + break; + case KM_KBP_CT_MARKER: + assert(c->marker > 0); + ctxt += UC_SENTINEL; + ctxt += CODE_DEADKEY; + ctxt += c->marker; + break; + } + } + + _kmx.GetContext()->Set(ctxt.c_str()); + _kmx.GetActions()->ResetQueue(); + state->actions.clear(); + + if (!_kmx.ProcessEvent(state, vk, modifier_state)) { + // We need to output the default keystroke + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_EMIT_KEYSTROKE, {0,}, {0} }); + } + + for (auto i = 0; i < _kmx.GetActions()->Length(); i++) { + auto a = _kmx.GetActions()->Get(i); + switch (a.ItemType) { + case QIT_CAPSLOCK: + //TODO: add Caps Event + //dwData = 0 == off; 1 == on + //state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_CAPSLOCK, {0,}, {0} }); + break; + case QIT_VKEYDOWN: + case QIT_VKEYUP: + case QIT_VSHIFTDOWN: + case QIT_VSHIFTUP: + //TODO: eliminate?? + break; + case QIT_CHAR: + state->context().emplace_back(km_kbp_context_item{ KM_KBP_CT_CHAR, {0,}, {(km_kbp_usv)a.dwData} }); + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_CHAR, {0,}, {(km_kbp_usv)a.dwData} }); + break; + case QIT_DEADKEY: + state->context().emplace_back(km_kbp_context_item{ KM_KBP_CT_MARKER, {0,}, {(km_kbp_usv)a.dwData} }); + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_MARKER, {0,}, {(uintptr_t)a.dwData} }); + break; + case QIT_BELL: + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_ALERT, {0,}, {0} }); + break; + case QIT_BACK: + switch (a.dwData) { + case BK_DEFAULT: + // This only happens if we know we have context to delete. Last item must be a character + assert(!state->context().empty() && state->context().back().type != KM_KBP_IT_MARKER); + if (!state->context().empty()) state->context().pop_back(); + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_BACK, {0,}, {0} }); + break; + case BK_DEADKEY: + // This only happens if we know we have context to delete. Last item must be a deadkey + assert(!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER); + if (!state->context().empty()) state->context().pop_back(); + break; + case BK_BACKSPACE: + // User-initiated backspace. We need to delete deadkeys from context, both sides of the character deleted + while (!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER) state->context().pop_back(); + if (!state->context().empty()) { + state->context().pop_back(); + while (!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER) state->context().pop_back(); + } + // Even if context is empty, we send the backspace event, because we may not + // know the context. + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_BACK, {0,}, {0} }); + break; + default: + assert(false); + } + break; + case QIT_INVALIDATECONTEXT: + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_INVALIDATE_CONTEXT, {0,}, {0} }); + break; + default: + //std::cout << "Unexpected item type " << a.ItemType << ", " << a.dwData << std::endl; + assert(false); + } + } + + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_END, {0,}, {0} }); + + return KM_KBP_STATUS_OK; + } + + constexpr km_kbp_attr const engine_attrs = { + 256, + KM_KBP_LIB_CURRENT, + KM_KBP_LIB_AGE, + KM_KBP_LIB_REVISION, + KM_KBP_TECH_KMX, + "SIL International" + }; + + km_kbp_attr const * kmx_processor::get_attrs() const { + //TODO + return &engine_attrs; + } + + } // namespace kbp +} // namespace km \ No newline at end of file diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_processor.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_processor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c3f16a296cf077e5b4dab277e6508a363cb62138 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_processor.cpp @@ -0,0 +1,662 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include "kmx_processor.h" + +using namespace km::kbp::kmx; + +/* Globals */ + +KMX_BOOL km::kbp::kmx::g_debug_ToConsole = TRUE, km::kbp::kmx::g_debug_KeymanLog = TRUE; +KMX_BOOL km::kbp::kmx::g_silent = FALSE; + +/* +* KMX_Processor +*/ + +KMX_Processor::KMX_Processor() : m_actions(&m_context), m_options(&m_keyboard) { + m_indexStack = new KMX_WORD[GLOBAL_ContextStackSize]; + m_miniContext = new KMX_WCHAR[GLOBAL_ContextStackSize]; +} + +KMX_Processor::~KMX_Processor() { + delete[] m_indexStack; + delete[] m_miniContext; +} + +char VKeyToChar(KMX_UINT modifiers, KMX_UINT vk) { + // We only map SHIFT and UNSHIFTED, and CAPS LOCK + + if ((modifiers & ~(K_SHIFTFLAG | CAPITALFLAG)) != 0) { + return 0; + } + + bool + shifted = (modifiers & K_SHIFTFLAG) == K_SHIFTFLAG, + caps = (modifiers & CAPITALFLAG) == CAPITALFLAG; + + if (vk == KM_KBP_VKEY_SPACE) { + // Override for space because it is the same for + // shifted and unshifted. + return 32; + } + + for (int i = 0; s_char_to_vkey[i].vk; i++) { + if (s_char_to_vkey[i].caps && caps) { + if (s_char_to_vkey[i].vk == vk && s_char_to_vkey[i].shifted == !shifted) return i + 32; + } + else { + if (s_char_to_vkey[i].vk == vk && s_char_to_vkey[i].shifted == shifted) return i + 32; + } + } + return 0; +} + +/* +* KMX_BOOL ProcessEvent(); +* +* Parameters: none +* +* Returns: TRUE if keystroke should be eaten +* +* Called by: FilterFunc +* +* ProcessEvent organizes the messages and gives them to the appropriate routines to +* process, and checks the state of Windows for the keyboard handling. +*/ + +KMX_BOOL KMX_Processor::ProcessEvent(km_kbp_state *state, KMX_UINT vkey, KMX_DWORD modifiers) +{ + LPKEYBOARD kbd = m_keyboard.Keyboard; + + m_kbp_state = state; + + if (m_environment.capsLock()) + modifiers |= CAPITALFLAG; + + m_state.vkey = vkey; + m_state.charCode = VKeyToChar(modifiers, vkey); + m_modifiers = modifiers; + m_state.LoopTimes = 0; + + if (kbd->StartGroup[BEGIN_UNICODE] == (KMX_DWORD) -1) { + DebugLog("Non-Unicode keyboards are not supported."); + return FALSE; + } + + LPGROUP gp = &kbd->dpGroupArray[kbd->StartGroup[BEGIN_UNICODE]]; + + KMX_BOOL fOutputKeystroke = FALSE; + + ProcessGroup(gp, &fOutputKeystroke); + + m_kbp_state = nullptr; + + return !fOutputKeystroke; +} + +/* +* PRIVATE KMX_BOOL ProcessGroup(LPGROUP gp); +* +* Parameters: gp Pointer to group to process inside +* +* Returns: TRUE if messages are to be sent, +* and FALSE if no messages are to be sent. +* +* Called by: ProcessEvent, recursive inside groups +* +* ProcessKey is where the keystroke conversion and output takes place. This routine +* has a lot of crucial code in it! +*/ + +KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) +{ + KMX_DWORD i; + LPKEY kkp = NULL; + PKMX_WCHAR p; + int sdmfI; + + /* + If the number of nested groups goes higher than 50, then break out - this is + a limitation of stack size. This is basically a catch-all for freaky apps that + cause message loopbacks and nasty things like that. Okay, it's really a catch all + for bugs! This means the user's system shouldn't hang. + */ + + LPKEYBOARD kbd = m_keyboard.Keyboard; + + sdmfI = -1; + + for(i = 0; i < kbd->cxGroupArray; i++) + if(gp == &kbd->dpGroupArray[i]) + { + sdmfI = i; + break; + } + + if(++m_state.LoopTimes > 50) + { + DebugLog("Aborting output: m_state.LoopTimes exceeded."); + m_state.StopOutput = TRUE; + return FALSE; + } + + /* + The rule matching loop. + + This loop iterates through all the rules in the group that is currently being + processed. Each rule in a group can be of three different types: + 1. A virtual key rule, where the key to be matched is a virtual key + 2. A normal key rule (WM_CHAR), where the key to be matched is an Ascii char. + 3. A rule in a keyless group, where only the context is matched. + + The loop goes through and checks the rules like that. This loop could be optimized + with standard searching techniques - the ContextMatch may be difficult. + */ + + DebugLog("m_state.vkey: %s shiftFlags: %x; charCode: %X", Debug_VirtualKey(m_state.vkey), m_modifiers, m_state.charCode); // I4582 + + if(gp) + { + for(kkp = gp->dpKeyArray, i=0; i < gp->cxKeyArray; i++, kkp++) + { + if(!ContextMatch(kkp)) continue; + if(!gp->fUsingKeys) + { + if(kkp->dpContext[0] != 0) break; else continue; + } + + //if(kkp->Key == m_state.vkey) + //SendDebugMessageFormat(m_state.msg.hwnd, sdmKeyboard, 0, "kkp->Key: %d kkp->ShiftFlags: %x", + // kkp->Key, kkp->ShiftFlags); + + /* Keyman 6.0: support Virtual Characters */ + if(IsEquivalentShift(kkp->ShiftFlags, m_modifiers)) + { + if(kkp->Key > VK__MAX && kkp->Key == m_state.vkey) break; // I3438 // I4582 + else if(kkp->Key == m_state.vkey) break; // I4169 + } + else if(kkp->ShiftFlags == 0 && kkp->Key == m_state.charCode && m_state.charCode != 0) break; + } + } + + if(!gp || i == gp->cxKeyArray) + { + /* + No rule was found that corresponded to the current state of the context and + keyboard. NoMatch should be checked for everything except virtual keys; and + context should also be kept. + + If the message was a virtual key, then just return without checking NoMatch. + NoMatch shouldn't be used for virtual keys because it will mean that no key + can ever get through that isn't matched - including arrows, func. keys, etc !! + Context is not kept for virtual keys being output. + */ + + DebugLog("No match was found in group %d of %d", sdmfI, m_keyboard.Keyboard->cxGroupArray); + + if(!gp || (m_state.charCode == 0 && gp->fUsingKeys)) // I4585 + // 7.0.241.0: I1133 - Fix mismatched parentheses on m_state.charCode - ie. we don't want to output this letter if !gp->fUsingKeys + { + KMX_BOOL fIsBackspace = m_state.vkey == KM_KBP_VKEY_BKSP && (m_modifiers & (LCTRLFLAG|RCTRLFLAG|LALTFLAG|RALTFLAG)) == 0; // I4128 + + if(fIsBackspace) { // I4838 // I4933 + PKMX_WCHAR pdeletecontext = m_context.Buf(1); // I4933 + if(!pdeletecontext || *pdeletecontext == 0) { // I4933 + m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); + *pOutputKeystroke = TRUE; // I4933 + return FALSE; // I4933 + } + m_actions.QueueAction(QIT_BACK, BK_BACKSPACE); // I4933 + } else { // I4024 // I4128 // I4287 // I4290 + DebugLog(" ... IsLegacy = FALSE; IsTIP = TRUE"); // I4128 + m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); + *pOutputKeystroke = TRUE; + return FALSE; + } + } + else if (gp->dpNoMatch != NULL && *gp->dpNoMatch != 0) + { + /* NoMatch rule found, and is a character key */ + PostString(gp->dpNoMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); + } + else if (m_state.charCode != 0 && m_state.charCode != 0xFFFF && gp->fUsingKeys) + { + /* No rule found, is a character key */ + m_actions.QueueAction(QIT_CHAR, m_state.charCode); + } + + return TRUE; + } + + DebugLog("match found in rule %d", i); + + /* + Save the context that will be used for output when the 'context' keyword is used. + For each deadkey, we need to add 2 characters; look in related stores as well... + */ + + assert(kkp != NULL); + + // 11 Aug 2003 - I25(v6) - mcdurdin - CODE_NUL context support + if(*kkp->dpContext == UC_SENTINEL && *(kkp->dpContext+1) == CODE_NUL) + u16cpy(m_miniContext, /*GLOBAL_ContextStackSize,*/ m_context.Buf(xstrlen_ignoreifopt(kkp->dpContext)-1) /*, GLOBAL_ContextStackSize*/); // I3162 // I3536 + else + u16cpy(m_miniContext, /*GLOBAL_ContextStackSize,*/ m_context.Buf(xstrlen_ignoreifopt(kkp->dpContext)) /*, GLOBAL_ContextStackSize*/); // I3162 // I3536 + + m_miniContext[GLOBAL_ContextStackSize-1] = 0; + + /* + The next section includes several optimizations that make the code a little harder + to read, but are probably worth it in the time that they save. + + If the output string doesn't have a "context" byte at the start, post backspaces + to erase the appropriate number of characters in the application. If it does have + a "context" byte at the start, then the string won't change, and no backspaces are + necessary. You could go one step further with this optimization, in PostAllKeys, + by comparing the starts of the strings to see what is same, and not backspacing + that, but it is probably not necessary. + */ + + p = kkp->dpOutput; + if(*p != UC_SENTINEL || *(p+1) != CODE_CONTEXT) + { + for(p = decxstr((KMX_WCHAR *) u16chr(m_miniContext, 0)); p >= m_miniContext; p = decxstr(p)) + { + if(*p == UC_SENTINEL) + switch(*(p+1)) + { + case CODE_DEADKEY: m_actions.QueueAction(QIT_BACK, BK_DEADKEY); break; + case CODE_NUL: break; // 11 Aug 2003 - I25(v6) - mcdurdin - CODE_NUL context support + } + else + { + m_actions.QueueAction(QIT_BACK, 0); + } + } + p = kkp->dpOutput; + } + else + p = incxstr(p); // otherwise, the "context" entry has to be jumped over + + /* Use PostString to post the rest of the output string. */ + + if(PostString(p, m_keyboard.Keyboard, NULL, pOutputKeystroke) == psrCheckMatches) + { + if(gp->dpMatch && *gp->dpMatch) + { + PostString(gp->dpMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); + } + } + + return TRUE; +} + +/* +* int PostString( PKMX_CHAR str, KMX_BOOL *useMode, LPMSG mp, +* LPKEYBOARD lpkb ); +* +* Parameters: str Pointer to string to send +* useMode Pointer to KMX_BOOL about whether a "use" command was found +* mp Pointer to MSG structure to copy in outputting messages +* lpkb Pointer to global keyboard structure +* +* Returns: 0 to continue, 1 and 2 to return. +* +* Called by: ProcessKey +* +* PostString posts a string of "context", "index", "beep", characters and virtual keys +* to the active application, via the Keyman PostKey buffer. +*/ + +int KMX_Processor::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR endstr, KMX_BOOL *pOutputKeystroke) +{ + PKMX_WCHAR p, q, temp; + LPSTORE s; + int n1, n2; + int i, n, shift; + KMX_BOOL FoundUse = FALSE; + + // TODO: Refactor to use incxstr + for(p = str; *p && (p < endstr || !endstr); p++) + { + if(*p == UC_SENTINEL) + switch(*(++p)) + { + case CODE_EXTENDED: // Start of a virtual key section w/shift codes + p++; + + shift = *p; //(*p<<8) | *(p+1); + m_actions.QueueAction(QIT_VSHIFTDOWN, shift); + + p++; + + m_actions.QueueAction(QIT_VKEYDOWN, *p); + m_actions.QueueAction(QIT_VKEYUP, *p); + + m_actions.QueueAction(QIT_VSHIFTUP, shift); + + p++; // CODE_EXTENDEDEND + ////// CODE_EXTENDEDEND will be incremented by loop + + break; + + case CODE_DEADKEY: // A deadkey to be output + p++; + m_actions.QueueAction(QIT_DEADKEY, *p); + break; + case CODE_BEEP: // Sound an 'iconasterisk' beep + m_actions.QueueAction(QIT_BELL, 0); + break; + case CODE_CONTEXT: // copy the context to the output + for(q = m_miniContext; *q; q++) { + m_actions.QueueAction(QIT_CHAR, *q); + } + break; + case CODE_CONTEXTEX: + p++; + for(q = m_miniContext, i = 0; *q && i < *p-1; i++, q=incxstr(q)); + if(*q) { + m_actions.QueueAction(QIT_CHAR, *q); + if(Uni_IsSurrogate1(*q) && Uni_IsSurrogate2(*(q+1))) { + m_actions.QueueAction(QIT_CHAR, *(q+1)); + } + } + break; + case CODE_RETURN: // stop processing and start PostAllKeys + m_state.StopOutput = TRUE; + return psrPostMessages; + + case CODE_CALL: + p++; + DebugLog("CallDLL not supported [store=%d].\n", *p-1); + FoundUse = TRUE; + break; + case CODE_USE: // use another group + p++; + ProcessGroup(&lpkb->dpGroupArray[*p-1], pOutputKeystroke); + if(m_state.StopOutput) return psrPostMessages; + FoundUse = TRUE; + break; + case CODE_CLEARCONTEXT: + // no longer supported, no-op + break; + case CODE_INDEX: + p++; + s = &m_keyboard.Keyboard->dpStoreArray[*p - 1]; + p++; + + n = m_indexStack[*p - 1]; + for(temp = s->dpString; *temp && n > 0; temp = incxstr(temp), n--); + PostString(temp, lpkb, incxstr(temp), pOutputKeystroke); + break; + case CODE_SETOPT: + p++; + n1 = *p - 1; + p++; + n2 = *p - 1; + GetOptions()->Set(n1, n2); + break; + case CODE_RESETOPT: + p++; + n1 = *p - 1; + GetOptions()->Reset(km_kbp_state_options(m_kbp_state), n1); + break; + case CODE_SAVEOPT: + p++; + n1 = *p - 1; + GetOptions()->Save(m_kbp_state, n1); + break; + case CODE_IFSYSTEMSTORE: + p+=3; + break; + case CODE_SETSYSTEMSTORE: + p+=2; + break; + } + else { + m_actions.QueueAction(QIT_CHAR, *p); + } + } + return FoundUse ? psrPostMessages : psrCheckMatches; +} + + +KMX_BOOL KMX_Processor::IsMatchingBaseLayout(PKMX_WCHAR layoutName) // I3432 +{ + KMX_BOOL bEqual = u16icmp(layoutName, static_cast(m_environment.baseLayout().c_str())) == 0 || // I4583 + u16icmp(layoutName, static_cast(m_environment.baseLayoutAlt().c_str())) == 0; // I4583 + + return bEqual; +} + +KMX_BOOL KMX_Processor::IsMatchingPlatformString(PKMX_WCHAR platform) // I3432 +{ + // TODO retrieve platform string from client environment + // TODO cleanup to use a vector. + PKMX_WCHAR t = new KMX_WCHAR[u16len(m_environment.platform().c_str()) + 1], context = NULL; + u16cpy(t, /*wcslen(s->dpString)+1,*/ m_environment.platform().c_str()); + PKMX_WCHAR p = u16tok(t, u' ', &context); + while (p != NULL) { + if (u16icmp(platform, p) == 0) { + delete t; + return TRUE; + } + p = u16tok(NULL, u' ', &context); + } + + delete t; + return FALSE; +} + +KMX_BOOL KMX_Processor::IsMatchingPlatform(LPSTORE s) // I3432 +{ + PKMX_WCHAR t = new KMX_WCHAR[u16len(s->dpString)+1]; + u16cpy(t, /*wcslen(s->dpString)+1,*/ s->dpString); + PKMX_WCHAR context = NULL; + PKMX_WCHAR platform = u16tok(t, u' ', &context); + while(platform != NULL) + { + if(!IsMatchingPlatformString(platform)) + { + s->dwSystemID = TSS_PLATFORM_NOMATCH; + delete t; + return FALSE; + } + platform = u16tok(NULL, u' ', &context); + } + + s->dwSystemID = TSS_PLATFORM_MATCH; + delete t; + return TRUE; +} + +/* +* KMX_BOOL ContextMatch( LPKEY kkp ); +* +* Parameters: kkp Rule to compare +* +* Returns: 0 on OK, 1 on not equal +* +* Called by: ProcessKey +* +* ContextMatch compares the context of a rule with the current context. +*/ + +KMX_BOOL KMX_Processor::ContextMatch(LPKEY kkp) +{ + KMX_WORD /*i,*/ n; + PKMX_WCHAR p, q, qbuf, temp; + PKMX_WORD indexp; + LPSTORE s, t; + KMX_BOOL bEqual; + + memset(m_indexStack, 0, GLOBAL_ContextStackSize*sizeof(KMX_WORD)); + + p = kkp->dpContext; + + if(*p == 0) + { + return TRUE; + } + + if(*p == UC_SENTINEL && *(p+1) == CODE_NUL) + { + // If context buf is longer than the context, then obviously not start of doc. + if(m_context.Buf(xstrlen_ignoreifopt(p))) return FALSE; + p = incxstr(p); + if(*p == 0) return TRUE; + } + + for(PKMX_WCHAR pp = p; pp && *pp; pp = incxstr(pp)) + { + if(*pp == UC_SENTINEL && *(pp+1) == CODE_IFOPT) + { + s = &m_keyboard.Keyboard->dpStoreArray[(*(pp+2))-1]; + t = &m_keyboard.Keyboard->dpStoreArray[(*(pp+4))-1]; + + bEqual = u16cmp(s->dpString, t->dpString) == 0; + if(*(pp+3) == 1 && bEqual) return FALSE; + if(*(pp+3) == 2 && !bEqual) return FALSE; + } + else if(*pp == UC_SENTINEL && *(pp+1) == CODE_IFSYSTEMSTORE) + { + KMX_DWORD dwSystemID = *(pp+2)-1; + t = &m_keyboard.Keyboard->dpStoreArray[(*(pp+4))-1]; + switch(dwSystemID) + { + case TSS_PLATFORM_MATCH: // Cached platform result - a matching platform + bEqual = TRUE; + break; + case TSS_PLATFORM_NOMATCH: // Cached platform result - not a matching platform + bEqual = FALSE; + break; + case TSS_PLATFORM: + bEqual = IsMatchingPlatform(t); + break; + case TSS_BASELAYOUT: + bEqual = IsMatchingBaseLayout(t->dpString); + break; + default: + { + PKMX_WCHAR ss = GetSystemStore(m_keyboard.Keyboard, dwSystemID); + if(ss == NULL) return FALSE; + bEqual = u16cmp(ss, t->dpString) == 0; + } + } + + if(*(pp+3) == 1 && bEqual) return FALSE; + if(*(pp+3) == 2 && !bEqual) return FALSE; + } + } + + q = qbuf = m_context.Buf(xstrlen_ignoreifopt(p)); + if(!q) + { + return FALSE; // context buf is too short! + } + indexp = m_indexStack; + + for(; *p && *q; p = incxstr(p)) + { + *indexp = 0; + + if(*p == UC_SENTINEL) + { + switch(*(p+1)) + { + case CODE_DEADKEY: + if(*q != UC_SENTINEL || *(q+1) != CODE_DEADKEY || *(q+2) != *(p+2)) + { + return FALSE; + } + break; + case CODE_ANY: + s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; + + temp = xstrchr(s->dpString, q); + + if(temp != NULL) + *indexp = (KMX_WORD) xstrpos(temp, s->dpString); + + else + return FALSE; + break; + case CODE_NOTANY: + s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; + + if((temp = xstrchr(s->dpString, q)) != NULL) + return FALSE; + + break; + case CODE_INDEX: + s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; + *indexp = n = m_indexStack[(*(p+3))-1]; + + for(temp = s->dpString; *temp && n > 0; temp = incxstr(temp), n--); + if(n != 0) return FALSE; + if(xchrcmp(temp, q) != 0) return FALSE; + break; + case CODE_CONTEXTEX: + // only the nth character + for(n = *(p+2) - 1, temp = qbuf; temp < q && n > 0; n--, temp = incxstr(temp)); + if(n == 0) + if(xchrcmp(temp, q) != 0) return FALSE; + break; + case CODE_IFOPT: + case CODE_IFSYSTEMSTORE: + indexp++; + continue; // don't increment q + default: + return FALSE; + } + } + else if(xchrcmp(p, q) != 0) + { + return FALSE; + } + indexp++; + q = incxstr(q); + } + + while(*p == UC_SENTINEL && (*(p+1) == CODE_IFOPT || *(p+1) == CODE_IFSYSTEMSTORE)) p = incxstr(p); // already tested + + return *p == *q; /*at least one must ==0 at this point*/ +} + +KMX_Actions *KMX_Processor::GetActions() { + return &m_actions; +} + +KMX_Context *KMX_Processor::GetContext() { + return &m_context; +} + +KMX_Options *KMX_Processor::GetOptions() { + return &m_options; +} + +KMX_Environment *KMX_Processor::GetEnvironment() { + return &m_environment; +} + +LPINTKEYBOARDINFO KMX_Processor::GetKeyboard() { + return &m_keyboard; +} + + +KMX_BOOL KMX_Processor::ReleaseKeyboardMemory(LPKEYBOARD kbd) +{ + if (!kbd) return TRUE; + delete kbd; + return TRUE; +} + +PKMX_WCHAR KMX_Processor::GetSystemStore(LPKEYBOARD kb, KMX_DWORD SystemID) +{ + for (KMX_DWORD i = 0; i < kb->cxStoreArray; i++) + if (kb->dpStoreArray[i].dwSystemID == SystemID) return kb->dpStoreArray[i].dpString; + + return NULL; +} diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_processor.h b/common/engine/keyboardprocessor/src/kmx/kmx_processor.h new file mode 100644 index 0000000000000000000000000000000000000000..a29522d6e5042e0ef800d8ec1107a62854af5530 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_processor.h @@ -0,0 +1,139 @@ + +#pragma once + +#include +#include +#include +#include +#include "kmx_base.h" +#include "kmx_file.h" +#include "kmx_context.h" +#include "kmx_actions.h" +#include "kmx_xstring.h" +#include "kmx_options.h" +#include "kmx_environment.h" + +/***************************************************************************/ + +namespace km { +namespace kbp { +namespace kmx { + + +/* Utility */ + +#define GLOBAL_ContextStackSize 80 + +class KMX_Processor { +private: + PKMX_WORD m_indexStack; + PKMX_WCHAR m_miniContext; + KMSTATE m_state; + km_kbp_state *m_kbp_state; + + kmx::KMX_Actions m_actions; + kmx::KMX_Context m_context; + kmx::KMX_Options m_options; + kmx::KMX_Environment m_environment; + + INTKEYBOARDINFO m_keyboard = { 0 }; + KMX_DWORD m_modifiers = 0; + + /* File loading */ + LPKEYBOARD FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWORD dwFileSize); + KMX_BOOL LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKeyboard); + KMX_BOOL VerifyKeyboard(PKMX_BYTE filebase, KMX_DWORD sz); + KMX_BOOL VerifyChecksum(PKMX_BYTE buf, KMX_DWORD sz); + PKMX_WCHAR StringOffset(PKMX_BYTE base, KMX_DWORD offset); +#ifdef KMX_64BIT + LPKEYBOARD CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base); +#endif + + KMX_BOOL ReleaseKeyboardMemory(LPKEYBOARD kbd); + + /* Keystroke Processing */ + + KMX_BOOL ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke); + KMX_BOOL ContextMatch(LPKEY kkp); + int PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR endstr, KMX_BOOL *pOutputKeystroke); + + /* Platform tests */ + + KMX_BOOL IsMatchingBaseLayout(PKMX_WCHAR layoutName); + KMX_BOOL IsMatchingPlatformString(PKMX_WCHAR platform); + KMX_BOOL IsMatchingPlatform(LPSTORE s); + + /* Utility functions */ + + PKMX_WCHAR GetSystemStore(LPKEYBOARD kb, KMX_DWORD SystemID); + + /* Caps Lock and modifier management */ + + void ResetCapsLock(void); + void KeyCapsLockPress(KMX_BOOL FIsUp); + void KeyShiftPress(KMX_BOOL FIsUp); + + KMX_BOOL IsEquivalentShift(KMX_UINT rshift, KMX_UINT kshift); + +public: + KMX_Processor(); + ~KMX_Processor(); + + KMX_BOOL Load(km_kbp_path_name keyboardName); + KMX_BOOL ProcessEvent(km_kbp_state *state, KMX_UINT vkey, KMX_DWORD modifiers); // returns FALSE on error or key not matched + + KMX_Actions *GetActions(); + KMX_Context *GetContext(); + KMX_Options *GetOptions(); + KMX_Environment *GetEnvironment(); + LPINTKEYBOARDINFO GetKeyboard(); +}; + +/* Global Constants */ + +struct char_to_vkey { + km_kbp_virtual_key vk; + bool shifted, caps; +}; + +struct modifier_names { + const char *name; + uint16_t modifier; +}; + +extern const struct char_to_vkey s_char_to_vkey[]; +extern const char *s_key_names[]; +extern const struct modifier_names s_modifier_names[]; + +/* Debugging */ + +extern KMX_BOOL g_debug_ToConsole, g_debug_KeymanLog, g_silent; + +#ifdef _MSC_VER +#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0) +#define console_error(msg,...) write_console(TRUE, (msg), __VA_ARGS__) +#define console_log(msg,...) write_console(FALSE, (msg), __VA_ARGS__) +#else +#define DebugLog(msg,...) (ShouldDebug() ? DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0) +#define console_error(msg,...) write_console(TRUE, (msg), ##__VA_ARGS__) +#define console_log(msg,...) write_console(FALSE, (msg), ##__VA_ARGS__) +#endif + +int DebugLog_1(const char *file, int line, const char *function, const char *fmt, ...); +const char *Debug_VirtualKey(KMX_WORD vk); +const char *Debug_UnicodeString(PKMX_WCHAR s, int x = 0); +const char *Debug_UnicodeString(std::u16string s, int x = 0); +const char *Debug_ModifierName(KMX_UINT modifiers); + + //inline KMX_BOOL ShouldDebug(); + +inline KMX_BOOL ShouldDebug() { + return TRUE; // g_debug_KeymanLog; +} + + +void write_console(KMX_BOOL error, const wchar_t *fmt, ...); + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_xstring.cpp b/common/engine/keyboardprocessor/src/kmx/kmx_xstring.cpp new file mode 100644 index 0000000000000000000000000000000000000000..45d874315bae7ef1dcb5425ae595fb6da6de348a --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_xstring.cpp @@ -0,0 +1,258 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include +#include +#include +#include +#include "kmx_processor.h" + +std::string utf16_to_utf8(std::u16string utf16_string); // defined in keyboard.cpp + +using namespace km::kbp::kmx; + +const km_kbp_cp *km::kbp::kmx::u16chr(const km_kbp_cp *p, km_kbp_cp ch) { + while (*p) { + if (*p == ch) return p; + p++; + } + return ch == 0 ? p : NULL; +} + +const km_kbp_cp *km::kbp::kmx::u16cpy(km_kbp_cp *dst, const km_kbp_cp *src) { + km_kbp_cp *o = dst; + while (*src) { + *dst++ = *src++; + } + *dst = 0; + return o; +} + +size_t km::kbp::kmx::u16len(const km_kbp_cp *p) { + int i = 0; + while (*p) { + p++; + i++; + } + return i; +} + +int km::kbp::kmx::u16cmp(const km_kbp_cp *p, const km_kbp_cp *q) { + while (*p && *q) { + if (*p != *q) return *p - *q; + p++; + q++; + } + return *p - *q; +} + +int km::kbp::kmx::u16icmp(const km_kbp_cp *p, const km_kbp_cp *q) { + while (*p && *q) { + if (toupper(*p) != toupper(*q)) return *p - *q; + p++; + q++; + } + return *p - *q; +} + +int km::kbp::kmx::u16ncmp(const km_kbp_cp *p, const km_kbp_cp *q, size_t count) { + while (*p && *q && count) { + if (*p != *q) return *p - *q; + p++; + q++; + count--; + } + if (count) + return *p - *q; + return 0; +} + +km_kbp_cp *km::kbp::kmx::u16tok(km_kbp_cp *p, km_kbp_cp ch, km_kbp_cp **ctx) { + if (!p) { + p = *ctx; + if (!p) return NULL; + } + + km_kbp_cp *q = p; + while (*q && *q != ch) { + q++; + } + if (*q) { + *q = 0; + q++; + while (*q == ch) q++; + *ctx = q; + } + else { + *ctx = NULL; + } + return p; +} + +/* +* int xstrlen( PKMX_BYTE p ); +* +* Parameters: p Pointer to string to get length of +* +* Returns: length of string +* +* Called by: various functions +* +* xstrlen calculates the length of a string, ignoring some special chars. +*/ + +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; + } + + 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 != 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; + } +} + +PKMX_WCHAR km::kbp::kmx::decxstr(PKMX_WCHAR p) +{ + p--; + if(*p == UC_SENTINEL_EXTENDEDEND) + { + int n = 0; + while(*p != UC_SENTINEL && n < 10) { p--; n++; } + return p; + } + + 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-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; + } + } + else if(*(p-3) == UC_SENTINEL) + { + switch(*(p-2)) + { + case CODE_INDEX: + case CODE_SETOPT: + case CODE_SETSYSTEMSTORE: + return p-3; + } + } + else if(*(p-4) == UC_SENTINEL) + { + switch(*(p-3)) + { + case CODE_IFOPT: + case CODE_IFSYSTEMSTORE: // I3432 + return p-4; + } + } + return p; +} + +int km::kbp::kmx::xstrlen_ignoreifopt(PKMX_WCHAR p) +{ + int i; + for(i = 0; *p; i++, p=incxstr(p)) + { + if(*p == UC_SENTINEL && (*(p+1) == CODE_IFOPT || *(p+1) == CODE_IFSYSTEMSTORE)) i--; // I3432 + } + return i; +} + +int km::kbp::kmx::xstrlen(PKMX_WCHAR p) +{ + int i; + for(i = 0; *p; i++, p=incxstr(p)); + return i; +} + +int km::kbp::kmx::xstrpos(PKMX_WCHAR p1, PKMX_WCHAR p) +{ + int i; + for(i = 0; p < p1; p = incxstr(p), i++); + return i; +} + +PKMX_WCHAR km::kbp::kmx::xstrchr(PKMX_WCHAR buf, PKMX_WCHAR chr) +{ + for(PKMX_WCHAR q = incxstr(buf); *buf; buf = q, q = incxstr(buf)) + if(!u16ncmp(buf, chr, (int)(q-buf))) + return buf; + return NULL; +} + +int km::kbp::kmx::xchrcmp(PKMX_WCHAR ch1, PKMX_WCHAR ch2) +{ + PKMX_WCHAR nch1 = incxstr(ch1); + if(nch1 == ch1) return *ch2 - *ch1; /* comparing *ch2 to nul */ + return u16ncmp(ch1, ch2, (int)(nch1-ch1)); +} + +PKMX_WCHAR km::kbp::kmx::strtowstr(PKMX_CHAR in) +{ + km_kbp_cp *result; + +#if _MSC_VER >= 1900 /* VS 2015 */ && _MSC_VER <= 1916 /* VS 2017 19.16 */ + std::wstring_convert, int16_t> convert; +#else + std::wstring_convert, char16_t> convert; +#endif + auto s = convert.from_bytes(in, strchr(in, 0)); + result = new KMX_WCHAR[s.length() + 1]; +#if _MSC_VER >= 1900 /* VS 2015 */ && _MSC_VER <= 1916 /* VS 2017 19.16 */ + s.copy(reinterpret_cast(result), s.length()); +#else + s.copy(result, s.length()); +#endif + result[s.length()] = 0; + return result; +} + + +PKMX_CHAR km::kbp::kmx::wstrtostr(PKMX_WCHAR in) +{ + PKMX_CHAR result; + + auto s = ::utf16_to_utf8(in); + result = new char[s.length() + 1]; + s.copy(result, s.length()); + result[s.length()] = 0; + return result; +} diff --git a/common/engine/keyboardprocessor/src/kmx/kmx_xstring.h b/common/engine/keyboardprocessor/src/kmx/kmx_xstring.h new file mode 100644 index 0000000000000000000000000000000000000000..3c425e16bec360b5f0220cc9d248ab918dc0da44 --- /dev/null +++ b/common/engine/keyboardprocessor/src/kmx/kmx_xstring.h @@ -0,0 +1,39 @@ +#pragma once + +#include "kmx_base.h" + +namespace km { +namespace kbp { +namespace kmx { + +#define Uni_IsSurrogate1(ch) ((ch) >= 0xD800 && (ch) <= 0xDBFF) +#define Uni_IsSurrogate2(ch) ((ch) >= 0xDC00 && (ch) <= 0xDFFF) +#define Uni_IsSMP(ch) ((ch) >= 0x10000) + +#define Uni_SurrogateToUTF32(ch, cl) (((ch) - 0xD800) * 0x400 + ((cl) - 0xDC00) + 0x10000) + +#define Uni_UTF32ToSurrogate1(ch) (((ch) - 0x10000) / 0x400 + 0xD800) +#define Uni_UTF32ToSurrogate2(ch) (((ch) - 0x10000) % 0x400 + 0xDC00) + +PKMX_WCHAR incxstr(PKMX_WCHAR p); +PKMX_WCHAR decxstr(PKMX_WCHAR p); +int xstrlen(PKMX_WCHAR p); +int xstrlen_ignoreifopt(PKMX_WCHAR p); +int xstrpos(PKMX_WCHAR p1, PKMX_WCHAR p); +PKMX_WCHAR xstrchr(PKMX_WCHAR buf, PKMX_WCHAR chr); +int xchrcmp(PKMX_WCHAR ch1, PKMX_WCHAR ch2); + +PKMX_CHAR wstrtostr(PKMX_WCHAR in); +PKMX_WCHAR strtowstr(PKMX_CHAR in); + +const km_kbp_cp *u16chr(const km_kbp_cp *p, km_kbp_cp ch); +const km_kbp_cp *u16cpy(km_kbp_cp *dst, const km_kbp_cp *src); // TODO: add buffer size, also u16ncpy version +size_t u16len(const km_kbp_cp *p); +int u16cmp(const km_kbp_cp *p, const km_kbp_cp *q); +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); + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/meson.build b/common/engine/keyboardprocessor/src/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..f9bf8adbc80e9ab3958adced58d76462c0f7b811 --- /dev/null +++ b/common/engine/keyboardprocessor/src/meson.build @@ -0,0 +1,88 @@ +# Copyright: © 2018 SIL International. +# Description: Cross platform build script to compile libkmnkbp. +# Create Date: 2 Oct 2018 +# Authors: Tim Eves (TSE) +# + +defns = ['-DKMN_KBP_EXPORTING'] + +if compiler.get_id() == 'gcc' or compiler.get_id() == 'clang' + warns = [ + '-Wall', + '-Wctor-dtor-privacy', + '-Wdouble-promotion', + '-Wendif-labels', + '-Wextra', + '-Wno-unknown-pragmas', + '-Wno-missing-field-initializers', + '-Wnon-virtual-dtor', + '-Wshadow' + ] + flags = [ + '-fvisibility=hidden', + '-fvisibility-inlines-hidden' + ] + links = ['-lstdc++fs'] + + if compiler.get_id() == 'clang' + warns += [ + '-Wimplicit-fallthrough', + '-Wno-double-promotion', + '-Wshorten-64-to-32' + ] + endif + + if host_machine.system() == 'darwin' + warns += ['-Wno-ctor-dtor-privacy', '-Wno-non-virtual-dtor '] + endif +endif + +if compiler.get_id() == 'msvc' + warns = [] + flags = [] + defns += [ + '-D_SCL_SECURE_NO_WARNINGS', + '-D_CRT_SECURE_NO_WARNINGS', + '-DUNICODE' + ] + links = [] +endif + +lib = library('kmnkbp0', + 'option.cpp', + 'keyboard.cpp', + 'state.cpp', + 'km_kbp_context_api.cpp', + 'km_kbp_keyboard_api.cpp', + 'km_kbp_options_api.cpp', + 'km_kbp_state_api.cpp', + 'km_kbp_processevent_api.cpp', + 'json.cpp', + 'mock/mock_processor.cpp', + 'kmx/kmx_consts.cpp', + 'kmx/kmx_processevent.cpp', + 'kmx/kmx_actions.cpp', + 'kmx/kmx_capslock.cpp', + 'kmx/kmx_context.cpp', + 'kmx/kmx_debug.cpp', + 'kmx/kmx_environment.cpp', + 'kmx/kmx_file.cpp', + 'kmx/kmx_modifiers.cpp', + 'kmx/kmx_options.cpp', + 'kmx/kmx_processor.cpp', + 'kmx/kmx_xstring.cpp', + 'utfcodec.cpp', + cpp_args: defns + warns + flags, + link_args: links, + version: meson.project_version(), + include_directories: inc, + install: true) + +kmnkbp = declare_dependency(link_with: lib, include_directories: inc) + +pkg = import('pkgconfig') +pkg.generate( + name: 'keyman_kmn_processor', + version: meson.project_version(), + description: 'Keyman processor for KMN keyboards.', + libraries: lib) diff --git a/common/engine/keyboardprocessor/src/mock/mock_processor.cpp b/common/engine/keyboardprocessor/src/mock/mock_processor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7033e20c7eb5a33cfe1eb2c8c22fefd0cf4a98a7 --- /dev/null +++ b/common/engine/keyboardprocessor/src/mock/mock_processor.cpp @@ -0,0 +1,133 @@ +/* + Copyright: © 2018 SIL International. + Description: This is a test implementation of the keyboard processor API to + enable testing API clients against a basic keyboard and give + them something to link against and load. + TODO: Add a mecahnism to trigger output of PERSIST_OPT & + RESET_OPT actions items, options support and context matching. + Create Date: 17 Oct 2018 + Authors: Tim Eves (TSE) + History: 17 Oct 2018 - TSE - Initial implementation. +*/ + +#include +#include "processor.hpp" +#include "state.hpp" + +namespace +{ + + // An table of VKEY to character sequence mappings, one per shift state. + // An empty string inidicates no mapping. + constexpr char const table[2][256][4] = { + { + /*00-0F*/"","","","","","","","","","\t","","","","\015","","", + /*10-1F*/"","","","","","","","","","","","\033","","","","", + /*20-2F*/" ","","","","","","","","","","","","","","","", + /*30-3F*/"0","1","2","3","4","5","6","7","8","9","","","","","","", + /*40-4F*/"","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o", + /*50-5F*/"p","q","r","s","t","u","v","w","x","y","z","","","","","", + /*60-6F*/"0","1","2","3","4","5","6","7","8","9","*","+","","-",".","/", + /*70-7F*/"f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","f11","f12","f13","f14","f15","f16", + /*80-8F*/"f17","f18","f19","f20","f24","","","","","","","","","","","", + /*90-9F*/"","","","","","","","","","","","","","","","", + /*A0-AF*/"","","","","","","","","","","","","","","","", + /*B0-BF*/"","","","","","","","","","",";","=",",","-",".","/", + /*C0-CF*/"`","","","","","","","","","","","","","","","", + /*D0-DF*/"","","","","","","","","","","","[","\\","]","'","", + /*E0-EF*/"","","","","","","","","","","","","","","","", + /*F0-FF*/"","","","","","","","","","","","","","","","", + }, + { + /*00-0F*/"","","","","","","","","","\t","","","","\015","","", + /*10-1F*/"","","","","","","","","","","","\033","","","","", + /*20-2F*/" ","","","","","","","","","","","","","","","", + /*30-3F*/")","!","@","#","$","%","^","&","*","(","","","","","","", + /*40-4F*/"","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O", + /*50-5F*/"P","Q","R","S","T","U","V","W","X","Y","Z","","","","","", + /*60-6F*/"","","","","","","","","","","","","","","","", + /*70-7F*/"","","","","","","","","","","","","","","","", + /*80-8F*/"","","","","","","","","","","","","","","","", + /*90-9F*/"","","","","","","","","","","","","","","","", + /*A0-AF*/"","","","","","","","","","","","","","","","", + /*B0-BF*/"","","","","","","","","","",":","+","<","_",">","?", + /*C0-CF*/"~","","","","","","","","","","","","","","","", + /*D0-DF*/"","","","","","","","","","","","{","|","}","\"","", + /*E0-EF*/"","","","","","","","","","","","","","","","", + /*F0-FF*/"","","","","","","","","","","","","","","","", + } + }; + + + constexpr km_kbp_attr const engine_attrs = { + 256, + KM_KBP_LIB_CURRENT, + KM_KBP_LIB_AGE, + KM_KBP_LIB_REVISION, + KM_KBP_TECH_MOCK, + "SIL International" + }; + +} + +namespace km { + namespace kbp + { + + km_kbp_status mock_processor::process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) { + try + { + state->actions.clear(); + + switch (vk) + { + case KM_KBP_VKEY_BKSP: + state->context().pop_back(); + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_BACK, {0,}, {1} }); + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_END, {0,}, {0} }); + break; + + default: + { + auto shift_state = bool(modifier_state & KM_KBP_MODIFIER_SHIFT); + // Only process further one of the shift states has something to output. + if (table[0][vk][0] || table[1][vk][0]) + { + auto char_seq = table[shift_state][vk]; + + for (auto c = char_seq; *c; ++c) + { + km_kbp_usv usv = *c; + state->context().emplace_back(km_kbp_context_item{ KM_KBP_CT_CHAR,{0,},{usv} }); + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_CHAR, {0,}, {usv} }); + } + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_END, {0,}, {0} }); + + return KM_KBP_STATUS_OK; + } + + // Both shift states output nothing, generate an alert. + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_ALERT, {0,}, {0} }); + state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_END, {0,}, {0} }); + break; + } + } + } + catch (std::bad_alloc) + { + state->actions.clear(); + return KM_KBP_STATUS_NO_MEM; + } + + return KM_KBP_STATUS_OK; + + } + + km_kbp_attr const * mock_processor::get_attrs() const { + return &engine_attrs; + } + + 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; } + } // namespace kbp +} // namespace km diff --git a/common/engine/keyboardprocessor/src/option.cpp b/common/engine/keyboardprocessor/src/option.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b4c12b5228e6546a1fa04b6125cf3fddab090b44 --- /dev/null +++ b/common/engine/keyboardprocessor/src/option.cpp @@ -0,0 +1,142 @@ +/* + Copyright: © 2018 SIL International. + Description: Internal option key value map class and adaptor class for + the API. + Create Date: 7 Oct 2018 + Authors: Tim Eves (TSE) + History: 7 Nov 2018 - TSE - Refactored into option.hpp & option.cpp. +*/ +#include +#include + +#include "keyboard.hpp" +#include "option.hpp" +#include "json.hpp" +#include "utfcodec.hpp" +#include "state.hpp" + +using namespace km::kbp; + +namespace +{ + constexpr char const * const scope_names_lut[] = { + u8"keyboard", + u8"environment" + }; +} + +// Forward declarations + + +option::option(km_kbp_option_scope s, std::u16string const & k, std::u16string const & v) +: km_kbp_option_item { new km_kbp_cp[k.size()+1], new km_kbp_cp[v.size()+1], + uint8_t(s) } +{ + std::copy_n(k.c_str(), k.size()+1, const_cast(key)); + std::copy_n(v.c_str(), v.size()+1, const_cast(value)); +} + + +char16_t const * options::lookup(km_kbp_option_scope scope, + std::u16string const & key) const noexcept +{ + // Search first in the updated values + for (auto & opt: _saved) + { + if (opt.key == key && opt.scope == scope) + return opt.value; + } + + // Then in the pristine copies. + km_kbp_option_item const * opt = _scopes[scope-1]; + while (opt->key && key != opt->key) ++opt; + return opt->key ? opt->value : nullptr; +} + + +km_kbp_option_item const * options::assign(km_kbp_state *state, km_kbp_option_scope scope, std::u16string const & key, + std::u16string const & value) +{ + km_kbp_option_item const * opt = _scopes[scope-1]; + while (opt->key && key != opt->key) ++opt; + if (!opt->key) return nullptr; + + for (auto & save: _saved) + { + if (save.key == key && save.scope == scope) + { + save = option(scope, key, value); + + //((km::kbp::state *)state)->keyboard(). + const_cast(static_cast(state)->keyboard().processor()).update_option(state, scope, key, value); + + return &save; + } + } + + _saved.emplace_back(scope, key, value); + + const_cast(static_cast(state)->keyboard().processor()).update_option(state, scope, key, value); + + return &_saved.back(); +} + +void options::reset(km_kbp_option_scope scope, std::u16string const & key) +{ + for (auto i = _saved.begin(); i == _saved.end(); ++i) + { + if (i->key == key && i->scope == scope) + { + _saved.erase(i); + break; + } + } +} + + + +json & operator << (json &j, char16_t const *u16str) { + char cps[5] = {0,}; + int8_t l; + std::string u8str; + + for (auto i = utf16::const_iterator(u16str); *i; ++i) + { + utf8::codec::put(reinterpret_cast(cps), *i, l); + u8str.append(cps, size_t(l)); + } + j << u8str; + return j; +} + + +json & km::kbp::operator << (json &j, options const &opts) +{ + j << json::object; + auto n = 0; + for (auto scope: opts._scopes) + { + j << scope_names_lut[n++] << json::object; + for (auto opt = scope; opt->key; ++opt) + { + j << opt->key << opt->value; + } + j << json::close; + } + + j << "saved" << json::object; + for (auto scope: {KM_KBP_OPT_KEYBOARD, KM_KBP_OPT_ENVIRONMENT}) + { + j << scope_names_lut[scope-1] << json::object; + for (auto & opt: opts._saved) + { + if (opt.scope != scope) continue; + j << opt.key << opt.value; + } + j << json::close; + } + j << json::close; + j << json::close; + + return j; +} diff --git a/common/engine/keyboardprocessor/src/option.hpp b/common/engine/keyboardprocessor/src/option.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6c9b6645d8d57aa9833f0f7c954513aa5afd5f95 --- /dev/null +++ b/common/engine/keyboardprocessor/src/option.hpp @@ -0,0 +1,107 @@ +/* + Copyright: © 2018 SIL International. + Description: Internal option key value map class and adaptor class for + the API. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 2 Oct 2018 - TSE - Refactored out of km_kbp_options_api.cpp. + 7 Nov 2018 - TSE - Refactored into option.hpp & option.cpp. +*/ + +#pragma once + +#include +#include + +#include + +// Forward declarations +class json; + +namespace km { +namespace kbp +{ + struct option : public km_kbp_option_item + { + option(): km_kbp_option_item KM_KBP_OPTIONS_END {} + option(option const &); + option(option &&); + option(km_kbp_option_scope, std::u16string const &, + std::u16string const &); + + ~option() noexcept; + + option & operator=(option && rhs); + }; + + inline + option::option(option const & rhs) + : option(km_kbp_option_scope(rhs.scope), rhs.key, rhs.value) + {} + + inline + option::option(option && rhs) + : km_kbp_option_item { rhs.key, rhs.value, rhs.scope } + { + rhs.key = nullptr; + rhs.value = nullptr; + } + + inline + option::~option() noexcept + { + delete [] key; + delete [] value; + } + + inline + option & option::operator=(option && rhs) + { + delete [] key; + delete [] value; + key = rhs.key; + value = rhs.value; rhs.key = nullptr; + scope = rhs.scope; rhs.value = nullptr; + return *this; + } + + + + class options + { + //km_kbp_keyboard_attrs const &_kb; + km_kbp_option_item const * _scopes[KM_KBP_OPT_MAX_SCOPES-1]; + std::vector