From: Po Lu Date: Sat, 2 Mar 2024 06:04:56 +0000 (+0800) Subject: Implement dead key combination on Android X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=868fa041bef04c90cdf017689b9aa5f610998590;p=emacs.git Implement dead key combination on Android * src/android.c (android_init_key_character_map) (android_get_dead_char): New functions. (android_wc_lookup_string): New argument COMPOSE_STATE. Ignore key events with the COMBINING_ACCENT flag set while recording their character values there, and combine such characters with the key event when processing a subsequent key event. * src/androidgui.h (struct android_compose_status): New structure. * src/androidterm.c (handle_one_android_event): Port dead key combination code from X. (bug#69321) (cherry picked from commit 5e20b114ef32d504f4429fd35ecd0d5dcf3bd8db) --- diff --git a/src/android.c b/src/android.c index 41481afa475..eb6981093be 100644 --- a/src/android.c +++ b/src/android.c @@ -123,6 +123,12 @@ struct android_emacs_cursor jmethodID constructor; }; +struct android_key_character_map +{ + jclass class; + jmethodID get_dead_char; +}; + /* The API level of the current device. */ static int android_api_level; @@ -203,6 +209,9 @@ static struct android_emacs_window window_class; /* Various methods associated with the EmacsCursor class. */ static struct android_emacs_cursor cursor_class; +/* Various methods associated with the KeyCharacterMap class. */ +static struct android_key_character_map key_character_map_class; + /* The time at which Emacs was installed, which also supplies the mtime of asset files. */ struct timespec emacs_installation_time; @@ -1865,6 +1874,32 @@ android_init_emacs_cursor (void) #undef FIND_METHOD } +static void +android_init_key_character_map (void) +{ + jclass old; + + key_character_map_class.class + = (*android_java_env)->FindClass (android_java_env, + "android/view/KeyCharacterMap"); + eassert (key_character_map_class.class); + + old = key_character_map_class.class; + key_character_map_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!key_character_map_class.class) + emacs_abort (); + + key_character_map_class.get_dead_char + = (*android_java_env)->GetStaticMethodID (android_java_env, + key_character_map_class.class, + "getDeadChar", "(II)I"); + eassert (key_character_map_class.get_dead_char); +} + JNIEXPORT void JNICALL NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, jobject dump_file_object) @@ -1913,6 +1948,7 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, android_init_emacs_drawable (); android_init_emacs_window (); android_init_emacs_cursor (); + android_init_key_character_map (); /* Set HOME to the app data directory. */ setenv ("HOME", android_files_dir, 1); @@ -5376,11 +5412,51 @@ android_translate_coordinates (android_window src, int x, ANDROID_DELETE_LOCAL_REF (coordinates); } +/* Return the character produced by combining the diacritic character + DCHAR with the key-producing character C in *VALUE. Value is 1 if + there is no character for this combination, 0 otherwise. */ + +static int +android_get_dead_char (unsigned int dchar, unsigned int c, + unsigned int *value) +{ + jmethodID method; + jclass class; + jint result; + + /* Call getDeadChar. */ + class = key_character_map_class.class; + method = key_character_map_class.get_dead_char; + result = (*android_java_env)->CallStaticIntMethod (android_java_env, + class, method, + (jint) dchar, + (jint) c); + + if (result) + { + *value = result; + return 0; + } + + return 1; +} + +/* Return a Unicode string in BUFFER_RETURN, a buffer of size + WCHARS_BUFFER, from the key press event EVENT, much like + XmbLookupString. If EVENT represents a key press without a + corresponding Unicode character, return its keysym in *KEYSYM_RETURN. + Return the action taken in *STATUS_RETURN. + + COMPOSE_STATUS, if non-NULL, should point to a structure for + temporary information to be stored in during dead key + composition. */ + int android_wc_lookup_string (android_key_pressed_event *event, wchar_t *buffer_return, int wchars_buffer, int *keysym_return, - enum android_lookup_status *status_return) + enum android_lookup_status *status_return, + struct android_compose_status *compose_status) { enum android_lookup_status status; int rc; @@ -5389,6 +5465,7 @@ android_wc_lookup_string (android_key_pressed_event *event, jsize size; size_t i; JNIEnv *env; + unsigned int unicode_char; env = android_java_env; status = ANDROID_LOOKUP_NONE; @@ -5402,6 +5479,13 @@ android_wc_lookup_string (android_key_pressed_event *event, { if (event->unicode_char) { + /* KeyCharacterMap.COMBINING_ACCENT. */ + if ((event->unicode_char & 0x80000000) && compose_status) + goto dead_key; + + /* Remove combining accent bits. */ + unicode_char = event->unicode_char & ~0x80000000; + if (wchars_buffer < 1) { *status_return = ANDROID_BUFFER_OVERFLOW; @@ -5409,7 +5493,31 @@ android_wc_lookup_string (android_key_pressed_event *event, } else { - buffer_return[0] = event->unicode_char; + /* If COMPOSE_STATUS holds a diacritic mark unicode_char + ought to be combined with, and this combination is + valid, return the result alone with no keysym. */ + + if (compose_status + && compose_status->chars_matched + && !android_get_dead_char (compose_status->accent, + unicode_char, + &unicode_char)) + { + buffer_return[0] = unicode_char; + *status_return = ANDROID_LOOKUP_CHARS; + compose_status->chars_matched = 0; + return 1; + } + else if (compose_status && compose_status->chars_matched) + { + /* If the combination is valid the compose status must + be reset and no character returned. */ + compose_status->chars_matched = 0; + status = ANDROID_LOOKUP_NONE; + return 0; + } + + buffer_return[0] = unicode_char; status = ANDROID_LOOKUP_CHARS; rc = 1; } @@ -5426,7 +5534,6 @@ android_wc_lookup_string (android_key_pressed_event *event, } *status_return = status; - return rc; } @@ -5482,6 +5589,15 @@ android_wc_lookup_string (android_key_pressed_event *event, *status_return = status; return rc; + + dead_key: + /* event->unicode_char is a dead key, which are diacritic marks that + should not be directly inserted but instead be combined with a + subsequent character before insertion. */ + *status_return = ANDROID_LOOKUP_NONE; + compose_status->chars_matched = 1; + compose_status->accent = event->unicode_char & ~0x80000000; + return 0; } diff --git a/src/androidgui.h b/src/androidgui.h index 89317581191..73b60c483d3 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -612,6 +612,15 @@ struct android_window_changes enum android_stack_mode stack_mode; }; +struct android_compose_status +{ + /* Accent character to be combined with another. */ + unsigned int accent; + + /* Number of characters matched. */ + int chars_matched; +}; + extern int android_pending (void); extern void android_next_event (union android_event *); extern bool android_check_if_event (union android_event *, @@ -707,7 +716,8 @@ extern void android_translate_coordinates (android_window, int, int, int *, int *); extern int android_wc_lookup_string (android_key_pressed_event *, wchar_t *, int, int *, - enum android_lookup_status *); + enum android_lookup_status *, + struct android_compose_status *); extern void android_recreate_activity (android_window); extern void android_update_ic (android_window, ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t); diff --git a/src/androidterm.c b/src/androidterm.c index 2bd2b45743d..baf26abe322 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -811,6 +811,7 @@ handle_one_android_event (struct android_display_info *dpyinfo, int keysym; ptrdiff_t nchars, i; struct window *w; + static struct android_compose_status compose_status; /* It is okay for this to not resemble handle_one_xevent so much. Differences in event handling code are much less nasty than @@ -947,6 +948,14 @@ handle_one_android_event (struct android_display_info *dpyinfo, extra_keyboard_modifiers); modifiers = event->xkey.state; + /* In case Meta is ComposeCharacter, clear its status. According + to Markus Ehrnsperger + Markus.Ehrnsperger@lehrstuhl-bross.physik.uni-muenchen.de this + enables ComposeCharacter to work whether or not it is combined + with Meta. */ + if (modifiers & ANDROID_ALT_MASK) + memset (&compose_status, 0, sizeof (compose_status)); + /* Common for all keysym input events. */ XSETFRAME (inev.ie.frame_or_window, any); inev.ie.modifiers @@ -960,7 +969,8 @@ handle_one_android_event (struct android_display_info *dpyinfo, nchars = android_wc_lookup_string (&event->xkey, copy_bufptr, copy_bufsiz, &keysym, - &status_return); + &status_return, + &compose_status); /* android_lookup_string can't be called twice, so there's no way to recover from buffer overflow. */ @@ -1000,6 +1010,13 @@ handle_one_android_event (struct android_display_info *dpyinfo, } } + /* If a compose sequence is in progress, we break here. + Otherwise, chars_matched is always 0. */ + if (compose_status.chars_matched > 0 && nchars == 0) + break; + + memset (&compose_status, 0, sizeof (compose_status)); + if (nchars == 1 && copy_bufptr[0] >= 32) { /* Deal with characters. */