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)
This commit is contained in:
Po Lu 2024-03-02 14:04:56 +08:00
parent 8b96503b6e
commit 5e20b114ef
3 changed files with 148 additions and 5 deletions

View file

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

View file

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

View file

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