Merge pull request #341 from keymanapp/android-modifier-chirality

Android modifier chirality
This commit is contained in:
Darcy Wong 2017-10-04 11:04:54 +07:00 committed by GitHub
commit 52b94d6d77
38 changed files with 851 additions and 46 deletions

2
android/.gitignore vendored
View file

@ -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/

View file

@ -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);

View file

@ -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

View file

@ -54,6 +54,7 @@ final class KMKeyboard extends WebView {
private GestureDetector gestureDetector;
private static ArrayList<OnKeyboardEventListener> 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();

View file

@ -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) {

View file

@ -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<String, Integer> codes = new HashMap<String, Integer>() {{
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;
};
}

View file

@ -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 ..\

View file

@ -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<String, String> kbInfo = new HashMap<String, String>();
@ -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);
}
}

View file

@ -0,0 +1 @@
/build

View file

@ -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')
}

View file

@ -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 *;
#}

View file

@ -0,0 +1,13 @@
package com.keyman.android.tests.keyboardHarness;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keyman.android.tests.keyboardHarness" >
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -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;
};
}

View file

@ -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<String, String> kbInfo = new HashMap<String, String>();
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<String, String> keyboardInfo) {
// Handle Keyman keyboard download started event here if needed
}
@Override
public void onKeyboardDownloadFinished(HashMap<String, String> keyboardInfo, int result) {
// Handle Keyman keyboard download finished event here if needed
}
}

View file

@ -0,0 +1,20 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.tavultesoft.kmea.KMTextView
android:id="@+id/kmTextView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentTop="true"
android:ems="10"
android:inputType="textMultiLine"
android:gravity="top"
android:scrollbars="vertical" >
<requestFocus />
</com.tavultesoft.kmea.KMTextView>
</RelativeLayout>

View file

@ -0,0 +1,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
<item android:id="@+id/action_settings" android:title="@string/action_settings"
android:orderInCategory="100" app:showAsAction="never" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View file

@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View file

@ -0,0 +1,6 @@
<resources>
<string name="app_name">Keyboard Harness</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
</resources>

View file

@ -0,0 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Light">
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -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()
}
}

View file

@ -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

View file

@ -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

Binary file not shown.

View file

@ -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

164
android/Tests/KeyboardHarness/gradlew vendored Normal file
View file

@ -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 "$@"

View file

@ -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

View file

@ -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

View file

@ -0,0 +1 @@
include ':app'

View file

@ -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)

View file

@ -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);
}

View file

@ -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
};