diff --git a/android/android_ldc_armv7a.mk b/android/android_ldc_armv7a.mk index 467b010c..26d9b7ca 100644 --- a/android/android_ldc_armv7a.mk +++ b/android/android_ldc_armv7a.mk @@ -2,9 +2,16 @@ PLATFORM_DIR=armeabi-v7a echo "\nLOCAL_MODULE: $LOCAL_MODULE" + +OS_TYPE="unknown" +case "$OSTYPE" in + msys*|cygwin*) OS_TYPE="windows" ;; + linux*) OS_TYPE="linux" ;; +esac + LDC_PARAMS="-mtriple=armv7-none-linux-androideabi -relocation-model=pic " -export LD=$NDK/toolchains/llvm/prebuilt/linux-$NDK_ARCH/bin/llvm-link -export CC=$NDK/toolchains/llvm/prebuilt/linux-$NDK_ARCH/bin/clang +export LD=$NDK/toolchains/llvm/prebuilt/$OS_TYPE-$NDK_ARCH/bin/llvm-link +export CC=$NDK/toolchains/llvm/prebuilt/$OS_TYPE-$NDK_ARCH/bin/clang SOURCES="$LOCAL_SRC_FILES $DLANGUI_SOURCES" SOURCE_PATHS="-I./jni $DLANGUI_SOURCE_PATHS $DLANGUI_IMPORT_PATHS" @@ -24,7 +31,7 @@ LINK_OPTIONS="\ -Wl,-soname,libnative-activity.so \ -shared \ --sysroot=$NDK/platforms/$ANDROID_TARGET/arch-arm \ --gcc-toolchain $NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-$NDK_ARCH \ +-gcc-toolchain $NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/$OS_TYPE-$NDK_ARCH \ -no-canonical-prefixes \ -target armv7-none-linux-androideabi \ -Wl,--fix-cortex-a8 \ diff --git a/android/dlangui_source_files.mk b/android/dlangui_source_files.mk index 2e1b574f..5e772f33 100644 --- a/android/dlangui_source_files.mk +++ b/android/dlangui_source_files.mk @@ -4,6 +4,7 @@ DLANGUI_SOURCES="\ $DLANGUI_DIR/src/dlangui/platforms/android/androidapp.d \ +$DLANGUI_DIR/src/dlangui/platforms/android/imm.d \ $DLANGUI_DIR/src/dlangui/platforms/common/startup.d \ $DLANGUI_DIR/src/dlangui/platforms/common/platform.d \ $DLANGUI_DIR/src/dlangui/dialogs/filedlg.d \ diff --git a/src/dlangui/platforms/android/androidapp.d b/src/dlangui/platforms/android/androidapp.d index f76ef462..13e3fd9e 100644 --- a/src/dlangui/platforms/android/androidapp.d +++ b/src/dlangui/platforms/android/androidapp.d @@ -16,6 +16,7 @@ import dlangui.platforms.common.platform; import android.input, android.looper : ALooper_pollAll; import android.native_window : ANativeWindow_setBuffersGeometry; import android.configuration; +import android.keycodes; import android.log, android.android_native_app_glue; /** @@ -139,6 +140,8 @@ class AndroidWindow : Window { * Process the next input event. */ int handle_input(AInputEvent* event) { + import imm = dlangui.platforms.android.imm; + import std.conv : to; Log.i("handle input, event=", AInputEvent_getType(event)); auto et = AInputEvent_getType(event); if (et == AINPUT_EVENT_TYPE_MOTION) { @@ -173,7 +176,37 @@ class AndroidWindow : Window { return 1; } else if (et == AINPUT_EVENT_TYPE_KEY) { Log.d("AINPUT_EVENT_TYPE_KEY"); - return 0; + KeyEvent evt; + auto app = (cast(AndroidPlatform)platform)._appstate; + int _keyFlags = AKeyEvent_getMetaState(event).toKeyFlag(); + int sysKeyCode = AKeyEvent_getKeyCode(event); + int sysMeta = AKeyEvent_getMetaState(event); + int keyCode = androidKeyMap.get(sysKeyCode, KeyCode.init); + auto action = toKeyAction(AKeyEvent_getAction(event)); + int char_ = imm.GetUnicodeChar(app, action, sysKeyCode, sysMeta); + dchar[] text; + if (!isTextEditControl(sysKeyCode)) { + if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE) { + // it's a string from IME + if (sysKeyCode == AKEYCODE_UNKNOWN) { + text = cast(dchar[]) to!dstring(imm.GetUnicodeString(app, event)); + action = KeyAction.Text; + } + // else repeat character AKeyEvent_getRepeatCount() times + } + else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN && (char_ || isASCIIChar(sysKeyCode))) { + text ~= cast(dchar)(char_ == 0 ? sysKeyCode : char_); + action = KeyAction.Text; + } + } + Log.d("ACTION: ", action, " syskeyCode: ", sysKeyCode, " sysMeta: ", sysMeta, "meta: ", _keyFlags, " char '", cast(dchar)char_, "' str:", cast(dstring)text); + if (action == KeyAction.Text) + evt = new KeyEvent(KeyAction.Text, 0, 0, cast(dstring)text); + else + evt = new KeyEvent(action, keyCode, _keyFlags); + if (evt && dispatchKeyEvent(evt)) + update(); + return 1; } return 0; } @@ -225,6 +258,12 @@ class AndroidPlatform : Platform { } + void showSoftKeyboard(bool shouldShow) { + import imm = dlangui.platforms.android.imm; + imm.showSoftKeyboard(_appstate, shouldShow); + } + + /** * Initialize an EGL context for the current display. */ @@ -706,3 +745,82 @@ extern (C) void android_main(android_app* state) { } + +private KeyAction toKeyAction(int androidKeyAction) { + switch(androidKeyAction) { + case AKEY_EVENT_ACTION_DOWN: return KeyAction.KeyDown; + case AKEY_EVENT_ACTION_UP: return KeyAction.KeyUp; + case AKEY_EVENT_ACTION_MULTIPLE: return KeyAction.Repeat; // can also be text + default: + assert(0, "should never reach this"); + } +} + + +private bool isASCIIChar(int ch) { + return 31 < ch && ch < 127; +} + +// Text editor controls such as move caret or (de)select +private bool isTextEditControl(int keyCode) { + switch (keyCode){ + case AKEYCODE_DEL: // backspace ("DEL" or "", "(II)V"); + jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, eventType, keyCode); + + unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char); + } + else + { + jmethodID method_get_unicode_char = (*env).GetMethodID(env, class_key_event, "getUnicodeChar", "(I)I"); + jmethodID eventConstructor = (*env).GetMethodID(env, class_key_event, "", "(II)V"); + jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, eventType, keyCode); + + unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char, metaState); + } + + (*javaVM).DetachCurrentThread(javaVM); + + return unicodeKey; +} + + +// Issue: native app glue seems to mess up the input. +// It is clearly seen in debugger that initally key event do have real input, +// but second time it is called it is all messed up +string GetUnicodeString(android_app* app, AInputEvent* event) +{ + string str; + auto javaVM = app.activity.vm; + auto env = app.activity.env; + + JavaVMAttachArgs attachArgs; + attachArgs.version_ = JNI_VERSION_1_6; + attachArgs.name = "NativeThread"; + attachArgs.group = null; + + if ((*javaVM).AttachCurrentThread(javaVM, &env, &attachArgs) == JNI_ERR) + { + Log.e("showSoftKeyboard Unable to attach to JVM"); + return null; + } + + + jclass class_key_event = (*env).FindClass(env, "android/view/KeyEvent"); + + jmethodID eventConstructor = (*env).GetMethodID(env, class_key_event, "", "(JJIIIIIIII)V"); + jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, + AKeyEvent_getDownTime(event), + AKeyEvent_getEventTime(event), + AKeyEvent_getAction(event), + AKeyEvent_getKeyCode(event), + AKeyEvent_getRepeatCount(event), + AKeyEvent_getMetaState(event), + AInputEvent_getDeviceId(event), + AKeyEvent_getScanCode(event), + AKeyEvent_getFlags(event), + AInputEvent_getSource(event) + ); + + // this won't work because characters is a member passed on construction and getCharacter() is just a getter + jmethodID method_get_characters = (*env).GetMethodID(env, class_key_event, "getCharacters", "()Ljava/lang/String;"); + if (auto jstr = (*env).CallObjectMethod(env, eventObj, method_get_characters)) { + str.length = (*env).GetStringUTFLength(env, jstr); + (*env).GetStringUTFRegion(env, jstr, 0, str.length, cast(char*)str.ptr); + } + + { + jmethodID method_get_unicode_char = (*env).GetMethodID(env, class_key_event, "getUnicodeChar", "()I"); + int unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char); + if (str.length == 0) { + import std.conv : to; + dchar[] tmp; + tmp ~= unicodeKey; + str = to!string(tmp); + } + } + + (*javaVM).DetachCurrentThread(javaVM); + + return str; +} \ No newline at end of file diff --git a/src/dlangui/widgets/editors.d b/src/dlangui/widgets/editors.d index 88857657..534b003c 100644 --- a/src/dlangui/widgets/editors.d +++ b/src/dlangui/widgets/editors.d @@ -345,8 +345,10 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction /// sets focus to this widget or suitable focusable child, returns previously focused widget override Widget setFocus(FocusReason reason = FocusReason.Unspecified) { Widget res = super.setFocus(reason); - if (focused) + if (focused) { + showSoftKeyboard(); handleEditorStateChange(); + } return res; } diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 655d6762..294a5343 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -1099,6 +1099,7 @@ public: // try to find focusable child return window.focusedWidget; } + hideSoftKeyboard(); return window.setFocus(this, reason); } /// searches children for first focusable item, returns null if not found @@ -1116,6 +1117,24 @@ public: return null; } + /// + final void hideSoftKeyboard() { + version(Android) { + import dlangui.platforms.android.androidapp; + if (auto androidPlatform = cast(AndroidPlatform)platform) + androidPlatform.showSoftKeyboard(false); + } + } + + /// Shows system virtual keyabord where applicable + final void showSoftKeyboard() { + version(Android) { + import dlangui.platforms.android.androidapp; + if (auto androidPlatform = cast(AndroidPlatform)platform) + androidPlatform.showSoftKeyboard(true); + } + } + // ======================================================= // Events