diff --git a/android/.gitignore b/android/.gitignore index 4bb6d1efb5..d0dd22b727 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -20,7 +20,7 @@ **/.DS_Store # This is the file output from KMEA; it's part of our build process. -**/libs/keyman-engine.aar +**/keyman-engine.aar # keymanweb dependencies KMEA/app/src/main/assets/ diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index b2301c4a71..e825694530 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -27,7 +27,11 @@ kmw['getOskHeight'] = getOskHeight; kmw['getOskWidth'] = getOskWidth; kmw['setActiveElement']('ta'); + ta.readOnly = false; + + kmw.addEventListener('keyboardloaded', setIsChiral); + kmw.addEventListener('keyboardchange', setIsChiral); } function setOskHeight(h) { @@ -56,6 +60,17 @@ return oskWidth; } + // Query KMW if a given keyboard uses chiral modifiers. + function setIsChiral(keyboardProperties) { + var name = typeof(keyboardProperties.internalName) == "undefined" ? keyboardProperties.keyboardName : keyboardProperties.internalName; + var isChiral = tavultesoft.keymanweb.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, version, font, oskFont) { //oskFont.files = ['NotoSansSyriacWestern-Regular.svg#NotoSansSyriacWesternRegular']; //window.console.log('oskFonts = '+JSON.stringify(oskFont)); @@ -172,11 +187,11 @@ kmw['executePopupKey'](keyID, keyText); } - function executeHardwareKeystroke(code, shift) { + function executeHardwareKeystroke(code, shift, lstates) { var kmw=window['tavultesoft']['keymanweb']; - window.console.log('executeHardwareKeystroke:('+code+', ' + shift + ');'); + window.console.log('executeHardwareKeystroke:('+code+', ' + shift + ', ' + lstates + ');'); try { - var r = kmw['executeHardwareKeystroke'](code, shift); + var r = kmw['executeHardwareKeystroke'](code, shift, lstates); window.console.log('executeHardwareKeystroke completed with '+r); } catch(e) { window.console.log('oops: '+e); 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 ea21b206cf..c0de2d7004 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 @@ -4,8 +4,6 @@ package com.tavultesoft.kmea; -import android.util.Log; - import android.content.Context; import android.view.KeyEvent; @@ -114,18 +112,46 @@ 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; } - int androidModifiers = event.getModifiers(), keymanModifiers = 0; - if ((androidModifiers & KeyEvent.META_SHIFT_ON) != 0) keymanModifiers |= 0x10; - if ((androidModifiers & KeyEvent.META_CTRL_ON) != 0) keymanModifiers |= 0x20; - if ((androidModifiers & KeyEvent.META_ALT_ON) != 0) keymanModifiers |= 0x40; + boolean isChiral = KMManager.getKMKeyboard(this.keyboardType).getChirality(); - if (keyCode == KeyEvent.KEYCODE_TAB && keymanModifiers == 0x20) { - // Trigger the Keyman language menu + // States of modifier keys + // KeyEvent.getModifiers() specifically masks out lock keys (KeyEvent.META_CAPS_LOCK_ON, + // KeyEvent.META_SCROLL_LOCK_ON, KeyEvent.META_NUM_LOCK_ON), so get their states separately + int androidModifiers = event.getModifiers(), keymanModifiers = 0; + boolean capsOn = event.isCapsLockOn(); + boolean numOn = event.isNumLockOn(); + boolean scrollOn = event.isScrollLockOn(); + + // By design, SHIFT is non-chiral + if ((androidModifiers & KeyEvent.META_SHIFT_ON) != 0) { + keymanModifiers |= KMModifierCodes.get("SHIFT"); + } + if ((androidModifiers & KeyEvent.META_CTRL_LEFT_ON) != 0) { + keymanModifiers |= isChiral ? KMModifierCodes.get("LCTRL") : KMModifierCodes.get("CTRL"); + } + if ((androidModifiers & KeyEvent.META_CTRL_RIGHT_ON) != 0) { + keymanModifiers |= isChiral ? KMModifierCodes.get("RCTRL") : KMModifierCodes.get("CTRL"); + } + if ((androidModifiers & KeyEvent.META_ALT_LEFT_ON) != 0) { + keymanModifiers |= isChiral ? KMModifierCodes.get("LALT") : KMModifierCodes.get("ALT"); + } + if ((androidModifiers & KeyEvent.META_ALT_RIGHT_ON) != 0) { + keymanModifiers |= isChiral ? KMModifierCodes.get("RALT") : KMModifierCodes.get("ALT"); + } + + int Lstates = 0; + Lstates |= capsOn ? KMModifierCodes.get("CAPS") : KMModifierCodes.get("NO_CAPS"); + Lstates |= numOn ? KMModifierCodes.get("NUM_LOCK") : KMModifierCodes.get("NO_NUM_LOCK"); + Lstates |= scrollOn ? KMModifierCodes.get("SCROLL_LOCK") : KMModifierCodes.get("NO_SCROLL_LOCK"); + + // CTRL-Tab triggers the Keyman language menu + if (keyCode == KeyEvent.KEYCODE_TAB && ((androidModifiers & KeyEvent.META_CTRL_ON) != 0)) { KMManager.showKeyboardPicker(context, keyboardType); return false; } @@ -136,7 +162,7 @@ public class KMHardwareKeyboardInterpreter implements KeyEvent.Callback { } // Send keystroke to KeymanWeb for processing: will return true to swallow the keystroke - return KMManager.executeHardwareKeystroke(keyCodeMap[keyCode], keymanModifiers, keyboardType); + return KMManager.executeHardwareKeystroke(keyCodeMap[keyCode], 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 eee996561a..74be9a794d 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 @@ -54,6 +54,7 @@ final class KMKeyboard extends WebView { private GestureDetector gestureDetector; private static ArrayList kbEventListeners = null; private boolean ShouldShowHelpBubble = false; + private boolean isChiral = false; protected boolean keyboardSet = false; protected boolean keyboardPickerEnabled = true; @@ -138,9 +139,9 @@ final class KMKeyboard extends WebView { setBackgroundColor(0); } - public void executeHardwareKeystroke(int code, int shift) { - String jsFormat = "javascript:executeHardwareKeystroke(%d,%d)"; - String jsString = String.format(jsFormat, code, shift); + public void executeHardwareKeystroke(int code, int shift, int lstates) { + String jsFormat = "javascript:executeHardwareKeystroke(%d,%d, %d)"; + String jsString = String.format(jsFormat, code, shift, lstates); loadUrl(jsString); } @@ -406,6 +407,16 @@ final class KMKeyboard extends WebView { return retVal; } + public void setChirality(boolean flag) { + this.isChiral = flag; + } + + public boolean getChirality() { + + return this.isChiral; + + } + private void saveCurrentKeyboardIndex() { SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); 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 5bfc4c8eca..653410c2d4 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 @@ -178,22 +178,23 @@ public final class KMManager { IMService = service; } - public static boolean executeHardwareKeystroke(int code, int shift) { + public static boolean executeHardwareKeystroke(int code, int shift, int lstates) { if (SystemKeyboard != null) { - return executeHardwareKeystroke(code, shift, KeyboardType.KEYBOARD_TYPE_SYSTEM); + return executeHardwareKeystroke(code, shift, KeyboardType.KEYBOARD_TYPE_SYSTEM, lstates); } else if (InAppKeyboard != null) { - return executeHardwareKeystroke(code, shift, KeyboardType.KEYBOARD_TYPE_INAPP); + return executeHardwareKeystroke(code, shift, KeyboardType.KEYBOARD_TYPE_INAPP, lstates); } return false; } - public static boolean executeHardwareKeystroke(int code, int shift, KeyboardType keyboard) { + public static boolean executeHardwareKeystroke( + int code, int shift, KeyboardType keyboard, int lstates) { if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) { - InAppKeyboard.executeHardwareKeystroke(code, shift); + InAppKeyboard.executeHardwareKeystroke(code, shift, lstates); return true; } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) { - SystemKeyboard.executeHardwareKeystroke(code, shift); + SystemKeyboard.executeHardwareKeystroke(code, shift, lstates); return true; } @@ -454,7 +455,8 @@ public final class KMManager { newKbInfo.put(KMManager.KMKey_LanguageID, KMManager.KMDefault_LanguageID); newKbInfo.put(KMManager.KMKey_KeyboardName, KMManager.KMDefault_KeyboardName); newKbInfo.put(KMManager.KMKey_LanguageName, KMManager.KMDefault_LanguageName); - newKbInfo.put(KMManager.KMKey_KeyboardVersion, getLatestKeyboardFileVersion(context, KMManager.KMDefault_KeyboardID)); + newKbInfo.put(KMManager.KMKey_KeyboardVersion, + getLatestKeyboardFileVersion(context, KMManager.KMDefault_KeyboardID)); newKbInfo.put(KMManager.KMKey_CustomKeyboard, "N"); newKbInfo.put(KMManager.KMKey_Font, KMManager.KMDefault_KeyboardFont); kbList.set(0, newKbInfo); @@ -1158,10 +1160,12 @@ public final class KMManager { } if (result > 0) { - if (KMManager.InAppKeyboard != null) - KMManager.InAppKeyboard.loadKeyboard(); - if (KMManager.SystemKeyboard != null) - KMManager.SystemKeyboard.loadKeyboard(); + if (KMManager.InAppKeyboard != null) { + InAppKeyboard.loadKeyboard(); + } + if ( KMManager.SystemKeyboard != null) { + SystemKeyboard.loadKeyboard(); + } } } }.execute(); @@ -1376,10 +1380,25 @@ public final class KMManager { String langName = kbInfo.get(KMManager.KMKey_LanguageName); String kFont = kbInfo.get(KMManager.KMKey_Font); String kOskFont = kbInfo.get(KMManager.KMKey_OskFont); - if (InAppKeyboard != null) + if (InAppKeyboard != null) { InAppKeyboard.setKeyboard(kbId, langId, kbName, langName, kFont, kOskFont); - if (SystemKeyboard != null) + } + if (SystemKeyboard != null) { SystemKeyboard.setKeyboard(kbId, langId, kbName, langName, kFont, kOskFont); + } + } + + // TODO: Refactor InAppKeyboard / SystemKeyboard logic + public static KMKeyboard getKMKeyboard(KeyboardType keyboard) { + if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) { + return InAppKeyboard; + } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + return SystemKeyboard; + } else { + // What should we do if KeyboardType.KEYBOARD_TYPE_UNDEFINED? + Log.w("KMManager", "Invalid keyboard"); + return null; + } } public static String getKeyboardTextFontFilename() { @@ -2031,7 +2050,6 @@ public final class KMManager { InAppKeyboard.subKeysList.add(hashMap); } } - return false; } } @@ -2256,6 +2274,15 @@ public final class KMManager { 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); + } + // This annotation is required in Jelly Bean and later: @JavascriptInterface public void insertText(final int dn, final String s) { @@ -2361,6 +2388,15 @@ public final class KMManager { 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); + } + // This annotation is required in Jelly Bean and later: @JavascriptInterface public void insertText(final int dn, final String s) { diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMModifierCodes.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMModifierCodes.java new file mode 100644 index 0000000000..872e37d569 --- /dev/null +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMModifierCodes.java @@ -0,0 +1,32 @@ +package com.tavultesoft.kmea; + +import java.util.HashMap; + +public final class KMModifierCodes { + + // Note: Keep this table in sync with web/source/kmwosk.js osk.modifierCodes + final static HashMap codes = new HashMap() {{ + put("LCTRL", 0x0001); + put("RCTRL", 0x0002); + put("LALT", 0x0004); + put("RALT", 0x0008); + put("SHIFT", 0x0010); + put("CTRL", 0x0020); + put("ALT", 0x0040); + put("CAPS", 0x0100); + put("NO_CAPS", 0x0200); + put("NUM_LOCK", 0x0400); + put("NO_NUM_LOCK", 0x0800); + put("SCROLL_LOCK", 0x1000); + put("NO_SCROLL_LOCK", 0x2000); + put("VIRTUAL_KEY", 0x4000); + }}; + + public static Integer get(String key) { + Integer bitflag = codes.get(key); + if (bitflag == null) { + bitflag = 0x0; + } + return bitflag; + }; +} diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh index dc530c5e84..63ad831821 100755 --- a/android/KMEA/build.sh +++ b/android/KMEA/build.sh @@ -113,9 +113,10 @@ if [ $? -ne 0 ]; then die "ERROR: Build of KMEA failed" fi -echo "Copying Keyman Engine for Android to KMAPro and Sample apps" +echo "Copying Keyman Engine for Android to KMAPro, Sample apps, and Tests" mv $KMA_ROOT/KMEA/app/build/outputs/aar/app-release.aar $KMA_ROOT/KMAPro/kMAPro/libs/keyman-engine.aar cp $KMA_ROOT/KMAPro/kMAPro/libs/keyman-engine.aar $KMA_ROOT/Samples/KMSample1/app/libs/keyman-engine.aar cp $KMA_ROOT/KMAPro/kMAPro/libs/keyman-engine.aar $KMA_ROOT/Samples/KMSample2/app/libs/keyman-engine.aar +cp $KMA_ROOT/KMAPro/kMAPro/libs/keyman-engine.aar $KMA_ROOT/Tests/keyman-engine.aar cd ..\ diff --git a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java index 3faa6b0c6c..bccf7d2366 100644 --- a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java +++ b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java @@ -1,9 +1,16 @@ package com.keyman.kmsample2; +import com.tavultesoft.kmea.KMManager; +import com.tavultesoft.kmea.KMManager.KeyboardType; +import com.tavultesoft.kmea.KMHardwareKeyboardInterpreter; +import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; + import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.inputmethodservice.InputMethodService; +import java.util.HashMap; +import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -12,15 +19,10 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import com.tavultesoft.kmea.KMManager; -import com.tavultesoft.kmea.KMManager.KeyboardType; -import com.tavultesoft.kmea.KeyboardEventHandler; - -import java.util.HashMap; - -public class SystemKeyboard extends InputMethodService implements KeyboardEventHandler.OnKeyboardEventListener { +public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { private static View inputView = null; + private KMHardwareKeyboardInterpreter interpreter = null; /** * Main initialization of the input method component. Be sure to call @@ -32,6 +34,7 @@ public class SystemKeyboard extends InputMethodService implements KeyboardEventH KMManager.setDebugMode(true); KMManager.addKeyboardEventListener(this); KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_SYSTEM); + interpreter = new KMHardwareKeyboardInterpreter(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_SYSTEM); // Add a custom keyboard HashMap kbInfo = new HashMap(); @@ -48,6 +51,7 @@ public class SystemKeyboard extends InputMethodService implements KeyboardEventH public void onDestroy() { inputView = null; KMManager.removeKeyboardEventListener(this); + interpreter = null; // Throw it away, since we're losing our application's context. KMManager.onDestroy(); super.onDestroy(); } @@ -103,7 +107,7 @@ public class SystemKeyboard extends InputMethodService implements KeyboardEventH attribute.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN; super.onStartInput(attribute, restarting); KMManager.onStartInput(attribute, restarting); - + KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); // User switched to a new input field so we should extract the text from input field // and pass it to Keyman Engine together with selection range InputConnection ic = getCurrentInputConnection(); @@ -175,4 +179,24 @@ public class SystemKeyboard extends InputMethodService implements KeyboardEventH public void onKeyboardDismissed() { // Handle Keyman keyboard dismissed event here if needed } -} + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return interpreter.onKeyDown(keyCode, event); // if false, will revert to default handling. + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return interpreter.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { + return interpreter.onKeyMultiple(keyCode, count, event); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return interpreter.onKeyLongPress(keyCode, event); + } +} \ No newline at end of file diff --git a/android/Tests/KeyboardHarness/app/.gitignore b/android/Tests/KeyboardHarness/app/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/android/Tests/KeyboardHarness/app/build.gradle b/android/Tests/KeyboardHarness/app/build.gradle new file mode 100644 index 0000000000..6ace5628d1 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.3" + + defaultConfig { + applicationId "com.keyman.android.tests.keyboardHarness" + minSdkVersion 15 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +repositories { + mavenCentral() + flatDir { + dirs 'libs' + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:25.1.0' + compile(name:'keyman-engine', ext:'aar') +} diff --git a/android/Tests/KeyboardHarness/app/libs/.gitkeep b/android/Tests/KeyboardHarness/app/libs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/android/Tests/KeyboardHarness/app/proguard-rules.pro b/android/Tests/KeyboardHarness/app/proguard-rules.pro new file mode 100644 index 0000000000..e0622e624f --- /dev/null +++ b/android/Tests/KeyboardHarness/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/serkankurt/Desktop/Android Development/adt-bundle-mac-x86_64-20140624/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# 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 *; +#} diff --git a/android/Tests/KeyboardHarness/app/src/androidTest/java/com/keyman/android/tests/keyboardHarness/ApplicationTest.java b/android/Tests/KeyboardHarness/app/src/androidTest/java/com/keyman/android/tests/keyboardHarness/ApplicationTest.java new file mode 100644 index 0000000000..25b8eea0cb --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/androidTest/java/com/keyman/android/tests/keyboardHarness/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.keyman.android.tests.keyboardHarness; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/android/Tests/KeyboardHarness/app/src/main/AndroidManifest.xml b/android/Tests/KeyboardHarness/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..8ca7121ff2 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/languages/chirality.js b/android/Tests/KeyboardHarness/app/src/main/assets/languages/chirality.js new file mode 100644 index 0000000000..ade1c50d02 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/assets/languages/chirality.js @@ -0,0 +1,79 @@ +KeymanWeb.KR(new Keyboard_chirality()); + +/** + * Please note that this is an experimental handwritten keyboard designed for initial testing of modifier chirality support for KeymanWeb. + * As it is handcoded, it uses multiple codesize optimization techniques that Keyman Developer simply does not and will not employ, and it + * will only work with KeymanWeb based implementations. (Not supported for Keyman Desktop or Keyman for Mac.) + * + * (This keyboard was written before Developer's implementation of chirality support and was used to bootstrap KeymanWeb chirality, serving + * as the initial wave of testing.) + */ + +function Keyboard_chirality() { + this.KI = "Keyboard_chirality"; + this.KN = "Development Chirality Test Keyboard"; + this.KV = { + KMBM: 0x001F, + F: ' 1em "Arial"', + K102: 0, + KLS: { 'default': new Array("`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "", "", "", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\", "", "", "", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "", "", "", "", "", "", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "", "", "", "", "", ""), + 'shift': new Array("~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "", "", "", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "|", "", "", "", "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "", "", "", "", "", "", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "", "", "", "", "", ""), + 'leftctrl': new Array("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ʍ", "ə", "ɹ", "ð", "ʏ", "ʉ", "ɨ", "ɵ", "ʘ", "", "", "", "", "", "", "ɑ", "ʃ", "", "ɸ", "ɣ", "ɥ", "ɟ", "", "ɬ", "ː", "", "", "", "", "", "", "", "ʒ", "χ", "ç", "ʋ", "β", "ɲ", "", "", "", "", "", "", "", "", "", ""), + 'leftctrl-shift': new Array("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ʡ", "", "ɘ", "ʀ", "θ", "", "ɯ", "ɪ", "", "", "", "", "", "", "", "", "ɒ", "ᶘ", "", "", "ɢ", "ʜ", "", "", "ʟ", "ː", "", "", "", "", "", "", "", "ᶚ", "", "", "", "ʙ", "ɴ", "", "", "", "ʔ", "", "", "", "", "", ""), + 'leftalt': new Array("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ɛ", "ɽ", "ʈ", "ɥ", "ʊ", "", "ɔ", "", "", "", "", "", "", "", "æ", "", "ɖ", "", "", "ɦ", "ʝ", "", "ɭ", "", "", "", "", "", "", "", "", "ʐ", "", "ɕ", "", "", "ɳ", "", "", "", "", "", "", "", "", "", ""), + 'rightalt': new Array("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ʠ", "ɰ", "ɜ", "ɾ", "ƭ", "", "ʌ", "", "ø", "ƥ", "", "", "", "", "", "", "ɐ", "σ", "ɗ", "", "ɠ", "ħ", "ʄ", "ƙ", "ɮ", "", "", "", "", "", "", "", "", "ʑ", "", "ƈ", "", "ɓ", "ŋ", "ɱ", "", "", "", "", "", "", "", "", ""), + 'leftalt-shift': new Array("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ʢ", "", "œ", "ɻ", "", "", "", "ᵻ", "ɞ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ʎ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""), + 'rightalt-shift': new Array("", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "ɶ", "ʁ", "", "", "ᵾ", "ᵼ", "ɤ", "", "", "", "", "", "", "", "ᴂ", "", "", "", "ʛ", "ɧ", "", "", "ɺ", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "") + } + }; + this.KH = ''; + this.KM = 0; + + this.dfltCodes = ["K_BKQUOTE","K_1","K_2","K_3","K_4","K_5","K_6","K_7","K_8","K_9","K_0", + "K_HYPHEN","K_EQUAL","K_*","K_*","K_*","K_Q","K_W","K_E","K_R","K_T", + "K_Y","K_U","K_I","K_O","K_P","K_LBRKT","K_RBRKT","K_BKSLASH","K_*", + "K_*","K_*","K_A","K_S","K_D","K_F","K_G","K_H","K_J","K_K","K_L", + "K_COLON","K_QUOTE","K_*","K_*","K_*","K_*","K_*","K_oE2", + "K_Z","K_X","K_C","K_V","K_B","K_N","K_M","K_COMMA","K_PERIOD", + "K_SLASH","K_*","K_*","K_*","K_*","K_*","K_SPACE"]; + + this.gs = function (t, e) { + return this.g0(t, e); + }; + this.g0 = function (t, e) { + var k = KeymanWeb, r = 0, m = 0; + + // Handwritten time! + var kls = this.KV.KLS; + + var layers = ['default', 'shift', 'leftctrl', 'leftctrl-shift', 'leftalt', 'rightalt', 'leftalt-shift', 'rightalt-shift']; + + // Maps keystrokes by base key-codes and array into the key symbols displayed in KLS. + for(var i = 0; i < layers.length; i++) { + // Obtain the modifier code to match for the selected layer. + // The following uses a non-public property potentially subject to change in the future. + var modCode = k.osk.modifierCodes['VIRTUAL_KEY'] | k.osk.getModifierState(layers[i]); + var layer = layers[i]; + + for(var key=0; key < kls[layer].length; key++) { + var keySymbol = this.dfltCodes[key]; + + if(keySymbol == "K_*") { + continue; + } else if(kls[layer][key] != '') { + if (k.KKM(e, modCode, k.osk.keyCodes[keySymbol])) { + r = m = 1; + if(k.KSM(e, k.osk.modifierCodes['CAPS'])) { + k.KO(0, t, kls[layer][key].toUpperCase()); + } else { + k.KO(0, t, kls[layer][key]); + } + return r; + } + } + } + } + + return r; + }; +} \ No newline at end of file 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 new file mode 100644 index 0000000000..fe6d5025b3 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java @@ -0,0 +1,117 @@ +package com.keyman.android.tests.keyboardHarness; + +import android.app.Activity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import com.tavultesoft.kmea.KMManager; +import com.tavultesoft.kmea.KMTextView; +import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; +import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener; +import com.tavultesoft.kmea.KMManager.KeyboardType; + +import java.util.HashMap; + +public class MainActivity extends Activity implements OnKeyboardEventListener, OnKeyboardDownloadEventListener { + + private KMTextView textView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + KMManager.setDebugMode(true); + KMManager.initialize(this, KeyboardType.KEYBOARD_TYPE_INAPP); + + setContentView(R.layout.activity_main); + textView = (KMTextView) findViewById(R.id.kmTextView); + + // Add a custom keyboard + HashMap kbInfo = new HashMap(); + final String Chirality_KeyboardFont = "{\"family\":\"LatinWeb\",\"source\":[\"DejaVuSans.ttf\"]}"; + + kbInfo.put(KMManager.KMKey_KeyboardID, "chirality"); + kbInfo.put(KMManager.KMKey_LanguageID, "eng"); + kbInfo.put(KMManager.KMKey_KeyboardName, "Chirality Keyboard"); + kbInfo.put(KMManager.KMKey_LanguageName, "English"); + kbInfo.put(KMManager.KMKey_KeyboardVersion, "1.0"); + kbInfo.put(KMManager.KMKey_Font, Chirality_KeyboardFont); + KMManager.addKeyboard(this, kbInfo); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + protected void onResume() { + super.onResume(); + KMManager.onResume(); + KMManager.addKeyboardEventListener(this); + KMManager.addKeyboardDownloadEventListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + KMManager.onPause(); + KMManager.removeKeyboardEventListener(this); + KMManager.removeKeyboardDownloadEventListener(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + @Override + public void onKeyboardLoaded(KeyboardType keyboardType) { + // Handle Keyman keyboard loaded event here if needed + } + + @Override + public void onKeyboardChanged(String newKeyboard) { + // Handle Keyman keyboard changed event here if needed + } + + @Override + public void onKeyboardShown() { + // Handle Keyman keyboard shown event here if needed + } + + @Override + public void onKeyboardDismissed() { + // Handle Keyman keyboard dismissed event here if needed + } + + @Override + public void onKeyboardDownloadStarted(HashMap keyboardInfo) { + // Handle Keyman keyboard download started event here if needed + } + + @Override + public void onKeyboardDownloadFinished(HashMap keyboardInfo, int result) { + // Handle Keyman keyboard download finished event here if needed + } +} diff --git a/android/Tests/KeyboardHarness/app/src/main/res/layout/activity_main.xml b/android/Tests/KeyboardHarness/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..317c86c320 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/android/Tests/KeyboardHarness/app/src/main/res/menu/menu_main.xml b/android/Tests/KeyboardHarness/app/src/main/res/menu/menu_main.xml new file mode 100644 index 0000000000..b1cb90811d --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,6 @@ + + + diff --git a/android/Tests/KeyboardHarness/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/Tests/KeyboardHarness/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..cde69bccce Binary files /dev/null and b/android/Tests/KeyboardHarness/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/Tests/KeyboardHarness/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/Tests/KeyboardHarness/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c133a0cbd3 Binary files /dev/null and b/android/Tests/KeyboardHarness/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/Tests/KeyboardHarness/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/Tests/KeyboardHarness/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..bfa42f0e7b Binary files /dev/null and b/android/Tests/KeyboardHarness/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/Tests/KeyboardHarness/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/Tests/KeyboardHarness/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..324e72cdd7 Binary files /dev/null and b/android/Tests/KeyboardHarness/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/Tests/KeyboardHarness/app/src/main/res/values-w820dp/dimens.xml b/android/Tests/KeyboardHarness/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000000..63fc816444 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/android/Tests/KeyboardHarness/app/src/main/res/values/dimens.xml b/android/Tests/KeyboardHarness/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..47c8224673 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/android/Tests/KeyboardHarness/app/src/main/res/values/strings.xml b/android/Tests/KeyboardHarness/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000..0222c4a67c --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + Keyboard Harness + + Hello world! + Settings + diff --git a/android/Tests/KeyboardHarness/app/src/main/res/values/styles.xml b/android/Tests/KeyboardHarness/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000..e84bc5ec14 --- /dev/null +++ b/android/Tests/KeyboardHarness/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/android/Tests/KeyboardHarness/build.gradle b/android/Tests/KeyboardHarness/build.gradle new file mode 100644 index 0000000000..596601301b --- /dev/null +++ b/android/Tests/KeyboardHarness/build.gradle @@ -0,0 +1,19 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/android/Tests/KeyboardHarness/build.sh b/android/Tests/KeyboardHarness/build.sh new file mode 100644 index 0000000000..2dbba42f4c --- /dev/null +++ b/android/Tests/KeyboardHarness/build.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Build KeyboardHarness test app + +echo Build KMEA +cd ../../KMEA +./build.sh +cd ../Tests/KeyboardHarness +cp ../keyman-engine.aar app/libs/ + +echo Build KeyboardHarness test app +./gradlew clean build diff --git a/android/Tests/KeyboardHarness/gradle.properties b/android/Tests/KeyboardHarness/gradle.properties new file mode 100644 index 0000000000..1d3591c8a4 --- /dev/null +++ b/android/Tests/KeyboardHarness/gradle.properties @@ -0,0 +1,18 @@ +# 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. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# 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 \ No newline at end of file diff --git a/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.jar b/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties b/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..eaba301170 --- /dev/null +++ b/android/Tests/KeyboardHarness/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/android/Tests/KeyboardHarness/gradlew b/android/Tests/KeyboardHarness/gradlew new file mode 100644 index 0000000000..91a7e269e1 --- /dev/null +++ b/android/Tests/KeyboardHarness/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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 +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# 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\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +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" ] ; 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"` + + # 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 + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/Tests/KeyboardHarness/gradlew.bat b/android/Tests/KeyboardHarness/gradlew.bat new file mode 100644 index 0000000000..8a0b282aa6 --- /dev/null +++ b/android/Tests/KeyboardHarness/gradlew.bat @@ -0,0 +1,90 @@ +@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 + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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 Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +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/KeyboardHarness/readme.md b/android/Tests/KeyboardHarness/readme.md new file mode 100644 index 0000000000..aa04c0edc2 --- /dev/null +++ b/android/Tests/KeyboardHarness/readme.md @@ -0,0 +1,9 @@ +# KeyboardHarness Test App # + +This app is is based on Samples/KMSample1 and targets keyboard functionality and reproducing bugs with keyboards rather than integration functionality. Use as system keyboard is currently not supported. + +## Version History ## + +## 2017-09-26 1.0 +* Initial creation + diff --git a/android/Tests/KeyboardHarness/settings.gradle b/android/Tests/KeyboardHarness/settings.gradle new file mode 100644 index 0000000000..e7b4def49c --- /dev/null +++ b/android/Tests/KeyboardHarness/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/android/history.md b/android/history.md index 1792b1f15a..17a4c4a9c4 100644 --- a/android/history.md +++ b/android/history.md @@ -1,6 +1,7 @@ # Keyman for Android ## 10.0 alpha +* Added support for L/R Alt and Ctrl and Caps Lock modifiers for keyboards if specified by a keyboard designer * Add feature to reset keyboard to default layer when new input field focused (keymanapp/keyman#288) * Removed "Share to Facebook" feature (keymanapp/keyman#156) * Fix dual keyboards that appear when closing Keyman Browser (keymanapp/keyman#220) diff --git a/web/source/keymanweb.js b/web/source/keymanweb.js index 6033930438..2825f33d46 100644 --- a/web/source/keymanweb.js +++ b/web/source/keymanweb.js @@ -3627,11 +3627,15 @@ if(!window['tavultesoft']['keymanweb']['initialized']) { /** * Function isChiral * Scope Public - * @param {Object=} k0 + * @param {string|Object=} k0 * @return {boolean} * Description Tests if the active keyboard (or optional argument) uses chiral modifiers. */ keymanweb.isChiral = keymanweb['isChiral'] = function(k0) { + if(typeof(k0) == "string") { + k0 = keymanweb._getKeyboardByID(k0); + } + return !!(keymanweb.getKeyboardModifierBitmask(k0) & keymanweb['osk'].modifierBitmasks.IS_CHIRAL); } diff --git a/web/source/kmwembedded.js b/web/source/kmwembedded.js index ee0a960950..45c86031a4 100644 --- a/web/source/kmwembedded.js +++ b/web/source/kmwembedded.js @@ -455,9 +455,11 @@ * API endpoint for hardware keystroke events from Android external keyboards * * @param {number} code key identifier - * @param {number} shift shift state (0x10=shift 0x20=ctrl 0x40=alt) + * @param {number} shift shift state (0x01=left ctrl 0x02=right ctrl 0x04=left alt 0x08=right alt + * 0x10=shift 0x20=ctrl 0x40=alt) + * @param {number} lstates lock state (0x0200=no caps 0x0400=num 0x0800=no num 0x1000=scroll 0x2000=no scroll locks) **/ - keymanweb['executeHardwareKeystroke'] = function(code, shift) { + keymanweb['executeHardwareKeystroke'] = function(code, shift, lstates = 0) { if(!keymanweb._ActiveKeyboard || code == 0) { return false; } @@ -466,7 +468,7 @@ device.touchable = false; device.formFactor = 'desktop'; try { - result = keymanweb.executeHardwareKeystrokeInternal(code, shift); + result = keymanweb.executeHardwareKeystrokeInternal(code, shift, lstates); } catch (err) { console.error(err.message, err); } @@ -479,9 +481,11 @@ * Process the hardware key to the keyboard mapping * * @param {number} code key identifier - * @param {number} shift shift state (0x10=shift 0x20=ctrl 0x40=alt) + * @param {number} shift shift state (0x01=left ctrl 0x02=right ctrl 0x04=left alt 0x08=right alt + * 0x10=shift 0x20=ctrl 0x40=alt) + * @param {number} lstates lock state (0x0200=no caps 0x0400=num 0x0800=no num 0x1000=scroll 0x2000=no scroll locks) **/ - keymanweb.executeHardwareKeystrokeInternal = function(code, shift) { + keymanweb.executeHardwareKeystrokeInternal = function(code, shift, lstates) { // Clear any pending (non-popup) key osk.keyPending = null; @@ -498,6 +502,7 @@ Lmodifiers: shift, vkCode: code, Lcode: code, + Lstates: lstates, LisVirtualKey: true, LisVirtualKeyCode: false };