1 module dlangui.platforms.android.imm; 2 3 version(Android): 4 5 import jni; 6 import android.android_native_app_glue; 7 import android.input; 8 9 import dlangui.core.logger; 10 11 12 alias IMMResult = int; 13 // values from InputMethodManager.java 14 private enum : IMMResult 15 { 16 RESULT_UNCHANGED_SHOWN = 0, 17 RESULT_UNCHANGED_HIDDEN = 1, 18 RESULT_SHOWN = 2, 19 RESULT_HIDDEN = 3, 20 } 21 22 23 alias IMMFlags = int; 24 // values from InputMethodManager.java 25 private enum : IMMFlags 26 { 27 SHOW_IMPLICIT = 0x0001, 28 SHOW_FORCED = 0x0002, 29 30 HIDE_IMPLICIT_ONLY = 0x0001, 31 HIDE_NOT_ALWAYS = 0x0002, 32 } 33 34 35 36 /** 37 * JNI wrapper used with native actitiy to show/hide software keyboard 38 * It relies on java reflection and it might be slow. 39 */ 40 void showSoftKeyboard(android_app* app, bool shouldShow) 41 { 42 // The code is based on https://stackoverflow.com/questions/5864790/how-to-show-the-soft-keyboard-on-native-activity 43 // Attaches the current thread to the JVM. 44 jint result; 45 IMMFlags flags; 46 47 auto javaVM = app.activity.vm; 48 auto env = app.activity.env; 49 50 JavaVMAttachArgs attachArgs; 51 attachArgs.version_ = JNI_VERSION_1_6; 52 attachArgs.name = "NativeThread"; 53 attachArgs.group = null; 54 55 if ((*javaVM).AttachCurrentThread(javaVM, &env, &attachArgs) == JNI_ERR) 56 { 57 Log.e("showSoftKeyboard Unable to attach to JVM"); 58 return; 59 } 60 61 // Retrieves NativeActivity. 62 jobject nativeActivity = app.activity.clazz; 63 jclass nativeActivityClass = (*env).GetObjectClass(env, nativeActivity); 64 65 // Retrieves Context.INPUT_METHOD_SERVICE. 66 jclass contextClass = (*env).FindClass(env, "android/content/Context"); 67 jfieldID FieldINPUT_METHOD_SERVICE = 68 (*env).GetStaticFieldID(env, contextClass, 69 "INPUT_METHOD_SERVICE", "Ljava/lang/String;"); 70 jobject INPUT_METHOD_SERVICE = 71 (*env).GetStaticObjectField(env, contextClass, FieldINPUT_METHOD_SERVICE); 72 //jniCheck(INPUT_METHOD_SERVICE); 73 74 // Runs getSystemService(Context.INPUT_METHOD_SERVICE). 75 jclass immClass = (*env).FindClass( 76 env, "android/view/inputmethod/InputMethodManager"); 77 jmethodID MethodGetSystemService = (*env).GetMethodID( 78 env, nativeActivityClass, "getSystemService", 79 "(Ljava/lang/String;)Ljava/lang/Object;"); 80 jobject imm = (*env).CallObjectMethod( 81 env, nativeActivity, MethodGetSystemService, 82 INPUT_METHOD_SERVICE); 83 84 // Runs getWindow().getDecorView(). 85 jmethodID MethodGetWindow = (*env).GetMethodID( 86 env, nativeActivityClass, "getWindow", "()Landroid/view/Window;"); 87 jobject window = (*env).CallObjectMethod( 88 env, nativeActivity, MethodGetWindow); 89 jclass windowClass = (*env).FindClass( 90 env, "android/view/Window"); 91 jmethodID MethodGetDecorView = (*env).GetMethodID( 92 env, windowClass, "getDecorView", "()Landroid/view/View;"); 93 jobject decorView = (*env).CallObjectMethod( 94 env, window, MethodGetDecorView); 95 96 if (shouldShow) { 97 // Runs imm.showSoftInput(...). 98 jmethodID MethodShowSoftInput = (*env).GetMethodID( 99 env, immClass, "showSoftInput", "(Landroid/view/View;I)Z"); 100 jboolean res = (*env).CallBooleanMethod( 101 env, imm, MethodShowSoftInput, decorView, flags); 102 } else { 103 // Runs lWindow.getViewToken() 104 jclass viewClass = (*env).FindClass( 105 env, "android/view/View"); 106 jmethodID MethodGetWindowToken = (*env).GetMethodID( 107 env, viewClass, "getWindowToken", "()Landroid/os/IBinder;"); 108 jobject binder = (*env).CallObjectMethod( 109 env, decorView, MethodGetWindowToken); 110 111 // lInputMethodManager.hideSoftInput(...). 112 jmethodID MethodHideSoftInput = (*env).GetMethodID( 113 env, immClass, "hideSoftInputFromWindow", 114 "(Landroid/os/IBinder;I)Z"); 115 jboolean res = (*env).CallBooleanMethod( 116 env, imm, MethodHideSoftInput, binder, flags); 117 } 118 119 // Finished with the JVM. 120 (*javaVM).DetachCurrentThread(javaVM); 121 } 122 123 124 int GetUnicodeChar(android_app* app, int eventType, int keyCode, int metaState) 125 { 126 auto javaVM = app.activity.vm; 127 auto env = app.activity.env; 128 129 JavaVMAttachArgs attachArgs; 130 attachArgs.version_ = JNI_VERSION_1_6; 131 attachArgs.name = "NativeThread"; 132 attachArgs.group = null; 133 134 if ((*javaVM).AttachCurrentThread(javaVM, &env, &attachArgs) == JNI_ERR) 135 return 0; 136 137 jclass class_key_event = (*env).FindClass(env, "android/view/KeyEvent"); 138 int unicodeKey; 139 140 if(metaState == 0) 141 { 142 jmethodID method_get_unicode_char = (*env).GetMethodID(env, class_key_event, "getUnicodeChar", "()I"); 143 jmethodID eventConstructor = (*env).GetMethodID(env, class_key_event, "<init>", "(II)V"); 144 jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, eventType, keyCode); 145 146 unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char); 147 } 148 else 149 { 150 jmethodID method_get_unicode_char = (*env).GetMethodID(env, class_key_event, "getUnicodeChar", "(I)I"); 151 jmethodID eventConstructor = (*env).GetMethodID(env, class_key_event, "<init>", "(II)V"); 152 jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, eventType, keyCode); 153 154 unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char, metaState); 155 } 156 157 (*javaVM).DetachCurrentThread(javaVM); 158 159 return unicodeKey; 160 } 161 162 163 // Issue: native app glue seems to mess up the input. 164 // It is clearly seen in debugger that initally key event do have real input, 165 // but second time it is called it is all messed up 166 string GetUnicodeString(android_app* app, AInputEvent* event) 167 { 168 string str; 169 auto javaVM = app.activity.vm; 170 auto env = app.activity.env; 171 172 JavaVMAttachArgs attachArgs; 173 attachArgs.version_ = JNI_VERSION_1_6; 174 attachArgs.name = "NativeThread"; 175 attachArgs.group = null; 176 177 if ((*javaVM).AttachCurrentThread(javaVM, &env, &attachArgs) == JNI_ERR) 178 { 179 Log.e("showSoftKeyboard Unable to attach to JVM"); 180 return null; 181 } 182 183 184 jclass class_key_event = (*env).FindClass(env, "android/view/KeyEvent"); 185 186 jmethodID eventConstructor = (*env).GetMethodID(env, class_key_event, "<init>", "(JJIIIIIIII)V"); 187 jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, 188 AKeyEvent_getDownTime(event), 189 AKeyEvent_getEventTime(event), 190 AKeyEvent_getAction(event), 191 AKeyEvent_getKeyCode(event), 192 AKeyEvent_getRepeatCount(event), 193 AKeyEvent_getMetaState(event), 194 AInputEvent_getDeviceId(event), 195 AKeyEvent_getScanCode(event), 196 AKeyEvent_getFlags(event), 197 AInputEvent_getSource(event) 198 ); 199 200 // this won't work because characters is a member passed on construction and getCharacter() is just a getter 201 jmethodID method_get_characters = (*env).GetMethodID(env, class_key_event, "getCharacters", "()Ljava/lang/String;"); 202 if (auto jstr = (*env).CallObjectMethod(env, eventObj, method_get_characters)) { 203 str.length = (*env).GetStringUTFLength(env, jstr); 204 (*env).GetStringUTFRegion(env, jstr, 0, str.length, cast(char*)str.ptr); 205 } 206 207 { 208 jmethodID method_get_unicode_char = (*env).GetMethodID(env, class_key_event, "getUnicodeChar", "()I"); 209 int unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char); 210 if (str.length == 0) { 211 import std.conv : to; 212 dchar[] tmp; 213 tmp ~= unicodeKey; 214 str = to!string(tmp); 215 } 216 } 217 218 (*javaVM).DetachCurrentThread(javaVM); 219 220 return str; 221 }