aboutsummaryrefslogtreecommitdiffstats
path: root/src/androidterm.c
diff options
context:
space:
mode:
authorPo Lu2023-02-15 12:23:03 +0800
committerPo Lu2023-02-15 12:23:03 +0800
commita158c1d5b964fda36f752998cef076d581dc4df4 (patch)
treeb9c7a22a7259dbd876be0e047304a338ef05e334 /src/androidterm.c
parent5a7855e84abd56e55b456aef4a43ae7623e76899 (diff)
downloademacs-a158c1d5b964fda36f752998cef076d581dc4df4.tar.gz
emacs-a158c1d5b964fda36f752998cef076d581dc4df4.zip
Update Android port
* configure.ac (HAVE_TEXT_CONVERSION): Define on Android. * doc/emacs/input.texi (On-Screen Keyboards): Document ``text conversion'' slightly. * doc/lispref/commands.texi (Misc Events): Document new `text-conversion' event. * java/org/gnu/emacs/EmacsContextMenu.java (display): Use `syncRunnable'. * java/org/gnu/emacs/EmacsDialog.java (display): Likewise. * java/org/gnu/emacs/EmacsEditable.java: Delete file. * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): Reimplement from scratch. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Add new functions. * java/org/gnu/emacs/EmacsService.java (EmacsService, getEmacsView) (getLocationOnScreen, sync, getClipboardManager, restartEmacs): Use syncRunnable. (syncRunnable): New function. (updateIC, resetIC): New functions. * java/org/gnu/emacs/EmacsView.java (EmacsView): New field `inputConnection' and `icMode'. (onCreateInputConnection): Update accordingly. (setICMode, getICMode): New functions. * lisp/bindings.el (global-map): Ignore text conversion events. * src/alloc.c (mark_frame): Mark text conversion data. * src/android.c (struct android_emacs_service): New fields `update_ic' and `reset_ic'. (event_serial): Export. (android_query_sem): New function. (android_init_events): Initialize new semaphore. (android_write_event): Export. (android_select): Check for UI thread code. (setEmacsParams, android_init_emacs_service): Initialize new methods. (android_check_query, android_begin_query, android_end_query) (android_run_in_emacs_thread): (android_update_ic, android_reset_ic): New functions for managing synchronous queries from one thread to another. * src/android.h: Export new functions. * src/androidgui.h (enum android_event_type): Add input method events. (enum android_ime_operation, struct android_ime_event) (union android_event, enum android_ic_mode): New structs and enums. * src/androidterm.c (android_window_to_frame): Allow DPYINFO to be NULL. (android_decode_utf16, android_handle_ime_event) (handle_one_android_event, android_sync_edit) (android_copy_java_string, beginBatchEdit, endBatchEdit) (commitCompletion, deleteSurroundingText, finishComposingText) (getSelectedtext, getTextAfterCursor, getTextBeforeCursor) (setComposingText, setComposingRegion, setSelection, getSelection) (performEditorAction, getExtractedText): New functions. (struct android_conversion_query_context): (android_perform_conversion_query): (android_text_to_string): (struct android_get_selection_context): (android_get_selection): (struct android_get_extracted_text_context): (android_get_extracted_text): (struct android_extracted_text_request_class): (struct android_extracted_text_class): (android_update_selection): (android_reset_conversion): (android_set_point): (android_compose_region_changed): (android_notify_conversion): (text_conversion_interface): New functions and structures. (android_term_init): Initialize text conversion. * src/coding.c (syms_of_coding): Define Qutf_16le on Android. * src/frame.c (make_frame): Clear conversion data. (delete_frame): Reset conversion state. * src/frame.h (enum text_conversion_operation) (struct text_conversion_action, struct text_conversion_state) (GCALIGNED_STRUCT): Update structures. * src/keyboard.c (read_char, readable_events, kbd_buffer_get_event) (syms_of_keyboard): Handle text conversion events. * src/lisp.h: * src/process.c: Fix includes. * src/textconv.c (enum textconv_batch_edit_flags, textconv_query) (reset_frame_state, detect_conversion_events) (restore_selected_window, really_commit_text) (really_finish_composing_text, really_set_composing_text) (really_set_composing_region, really_delete_surrounding_text) (really_set_point, complete_edit) (handle_pending_conversion_events_1) (handle_pending_conversion_events, start_batch_edit) (end_batch_edit, commit_text, finish_composing_text) (set_composing_text, set_composing_region, textconv_set_point) (delete_surrounding_text, get_extracted_text) (report_selected_window_change, report_point_change) (register_texconv_interface): New functions. * src/textconv.h (struct textconv_interface) (TEXTCONV_SKIP_CONVERSION_REGION): Update prototype. * src/xdisp.c (mark_window_display_accurate_1): * src/xfns.c (xic_string_conversion_callback): * src/xterm.c (init_xterm): Adjust accordingly.
Diffstat (limited to 'src/androidterm.c')
-rw-r--r--src/androidterm.c1037
1 files changed, 1036 insertions, 1 deletions
diff --git a/src/androidterm.c b/src/androidterm.c
index a57d5623c66..a44bae954da 100644
--- a/src/androidterm.c
+++ b/src/androidterm.c
@@ -20,6 +20,9 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
20#include <config.h> 20#include <config.h>
21#include <stdio.h> 21#include <stdio.h>
22#include <math.h> 22#include <math.h>
23#include <stdlib.h>
24#include <assert.h>
25#include <semaphore.h>
23 26
24#include "lisp.h" 27#include "lisp.h"
25#include "androidterm.h" 28#include "androidterm.h"
@@ -28,6 +31,8 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
28#include "android.h" 31#include "android.h"
29#include "buffer.h" 32#include "buffer.h"
30#include "window.h" 33#include "window.h"
34#include "textconv.h"
35#include "coding.h"
31 36
32/* This is a chain of structures for all the X displays currently in 37/* This is a chain of structures for all the X displays currently in
33 use. */ 38 use. */
@@ -59,6 +64,12 @@ enum
59 ANDROID_EVENT_DROP, 64 ANDROID_EVENT_DROP,
60 }; 65 };
61 66
67/* Find the frame whose window has the identifier WDESC.
68
69 This is like x_window_to_frame in xterm.c, except that DPYINFO may
70 be NULL, as there is only at most one Android display, and is only
71 specified in order to stay consistent with X. */
72
62static struct frame * 73static struct frame *
63android_window_to_frame (struct android_display_info *dpyinfo, 74android_window_to_frame (struct android_display_info *dpyinfo,
64 android_window wdesc) 75 android_window wdesc)
@@ -73,7 +84,7 @@ android_window_to_frame (struct android_display_info *dpyinfo,
73 { 84 {
74 f = XFRAME (frame); 85 f = XFRAME (frame);
75 86
76 if (!FRAME_ANDROID_P (f) || FRAME_DISPLAY_INFO (f) != dpyinfo) 87 if (!FRAME_ANDROID_P (f))
77 continue; 88 continue;
78 89
79 if (FRAME_ANDROID_WINDOW (f) == wdesc) 90 if (FRAME_ANDROID_WINDOW (f) == wdesc)
@@ -527,6 +538,103 @@ android_find_tool (struct frame *f, int pointer_id)
527 return NULL; 538 return NULL;
528} 539}
529 540
541/* Decode STRING, an array of N little endian UTF-16 characters, into
542 a Lisp string. Return Qnil if the string is too large, and the
543 encoded string otherwise. */
544
545static Lisp_Object
546android_decode_utf16 (unsigned short *utf16, size_t n)
547{
548 struct coding_system coding;
549 ptrdiff_t size;
550
551 if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
552 return Qnil;
553
554 /* Set up the coding system. Decoding a UTF-16 string (with no BOM)
555 should not signal. */
556
557 memset (&coding, 0, sizeof coding);
558
559 setup_coding_system (Qutf_16le, &coding);
560 coding.source = (const unsigned char *) utf16;
561 decode_coding_object (&coding, Qnil, 0, 0, size,
562 size, Qt);
563
564 return coding.dst_object;
565}
566
567/* Handle a single input method event EVENT, delivered to the frame
568 F.
569
570 Perform the text conversion action specified inside. */
571
572static void
573android_handle_ime_event (union android_event *event, struct frame *f)
574{
575 Lisp_Object text;
576
577 /* First, decode the text if necessary. */
578
579 switch (event->ime.operation)
580 {
581 case ANDROID_IME_COMMIT_TEXT:
582 case ANDROID_IME_FINISH_COMPOSING_TEXT:
583 case ANDROID_IME_SET_COMPOSING_TEXT:
584 text = android_decode_utf16 (event->ime.text,
585 event->ime.length);
586 xfree (event->ime.text);
587 break;
588
589 default:
590 break;
591 }
592
593 /* Finally, perform the appropriate conversion action. */
594
595 switch (event->ime.operation)
596 {
597 case ANDROID_IME_COMMIT_TEXT:
598 commit_text (f, text, event->ime.position,
599 event->ime.counter);
600 break;
601
602 case ANDROID_IME_DELETE_SURROUNDING_TEXT:
603 delete_surrounding_text (f, event->ime.start,
604 event->ime.end,
605 event->ime.counter);
606 break;
607
608 case ANDROID_IME_FINISH_COMPOSING_TEXT:
609 finish_composing_text (f, event->ime.counter);
610 break;
611
612 case ANDROID_IME_SET_COMPOSING_TEXT:
613 set_composing_text (f, text, event->ime.position,
614 event->ime.counter);
615 break;
616
617 case ANDROID_IME_SET_COMPOSING_REGION:
618 set_composing_region (f, event->ime.start,
619 event->ime.end,
620 event->ime.counter);
621 break;
622
623 case ANDROID_IME_SET_POINT:
624 textconv_set_point (f, event->ime.position,
625 event->ime.counter);
626 break;
627
628 case ANDROID_IME_START_BATCH_EDIT:
629 start_batch_edit (f, event->ime.counter);
630 break;
631
632 case ANDROID_IME_END_BATCH_EDIT:
633 end_batch_edit (f, event->ime.counter);
634 break;
635 }
636}
637
530static int 638static int
531handle_one_android_event (struct android_display_info *dpyinfo, 639handle_one_android_event (struct android_display_info *dpyinfo,
532 union android_event *event, int *finish, 640 union android_event *event, int *finish,
@@ -745,6 +853,17 @@ handle_one_android_event (struct android_display_info *dpyinfo,
745 853
746 case ANDROID_WINDOW_ACTION: 854 case ANDROID_WINDOW_ACTION:
747 855
856 /* This is a special event sent by android_run_in_emacs_thread
857 used to make Android run stuff. */
858
859 if (!event->xaction.window && !event->xaction.action)
860 {
861 /* Check for and run anything the UI thread wants to run on the main
862 thread. */
863 android_check_query ();
864 goto OTHER;
865 }
866
748 f = any; 867 f = any;
749 868
750 if (event->xaction.action == 0) 869 if (event->xaction.action == 0)
@@ -1334,6 +1453,19 @@ handle_one_android_event (struct android_display_info *dpyinfo,
1334 1453
1335 goto OTHER; 1454 goto OTHER;
1336 1455
1456 /* Input method events. textconv.c functions are called here to
1457 queue events, which are then executed in a safe context
1458 inside keyboard.c. */
1459 case ANDROID_INPUT_METHOD:
1460
1461 if (!any)
1462 /* Free any text allocated for this event. */
1463 xfree (event->ime.text);
1464 else
1465 android_handle_ime_event (event, any);
1466
1467 goto OTHER;
1468
1337 default: 1469 default:
1338 goto OTHER; 1470 goto OTHER;
1339 } 1471 }
@@ -4148,6 +4280,904 @@ android_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1)
4148 4280
4149 4281
4150 4282
4283#ifdef __clang__
4284#pragma clang diagnostic push
4285#pragma clang diagnostic ignored "-Wmissing-prototypes"
4286#else
4287#pragma GCC diagnostic push
4288#pragma GCC diagnostic ignored "-Wmissing-prototypes"
4289#endif
4290
4291/* Input method related functions. Some of these are called from Java
4292 within the UI thread. */
4293
4294/* A counter used to decide when an editing request completes. */
4295static unsigned long edit_counter;
4296
4297/* The last counter known to have completed. */
4298static unsigned long last_edit_counter;
4299
4300/* Semaphore posted every time the counter increases. */
4301static sem_t edit_sem;
4302
4303/* Try to synchronize with the UI thread, waiting a certain amount of
4304 time for outstanding editing requests to complete.
4305
4306 Every time one of the text retrieval functions is called and an
4307 editing request is made, Emacs gives the main thread approximately
4308 50 ms to process it, in order to mostly keep the input method in
4309 sync with the buffer contents. */
4310
4311static void
4312android_sync_edit (void)
4313{
4314 struct timespec start, end, rem;
4315
4316 if (__atomic_load_n (&last_edit_counter,
4317 __ATOMIC_SEQ_CST)
4318 == edit_counter)
4319 return;
4320
4321 start = current_timespec ();
4322 end = timespec_add (start, make_timespec (0, 50000000));
4323
4324 while (true)
4325 {
4326 rem = timespec_sub (end, current_timespec ());
4327
4328 /* Timeout. */
4329 if (timespec_sign (rem) < 0)
4330 break;
4331
4332 if (__atomic_load_n (&last_edit_counter,
4333 __ATOMIC_SEQ_CST)
4334 == edit_counter)
4335 break;
4336
4337 sem_timedwait (&edit_sem, &end);
4338 }
4339}
4340
4341/* Return a copy of the specified Java string and its length in
4342 *LENGTH. Use the JNI environment ENV. Value is NULL if copying
4343 *the string fails. */
4344
4345static unsigned short *
4346android_copy_java_string (JNIEnv *env, jstring string, size_t *length)
4347{
4348 jsize size, i;
4349 const jchar *java;
4350 unsigned short *buffer;
4351
4352 size = (*env)->GetStringLength (env, string);
4353 buffer = malloc (size * sizeof *buffer);
4354
4355 if (!buffer)
4356 return NULL;
4357
4358 java = (*env)->GetStringChars (env, string, NULL);
4359
4360 if (!java)
4361 {
4362 free (buffer);
4363 return NULL;
4364 }
4365
4366 for (i = 0; i < size; ++i)
4367 buffer[i] = java[i];
4368
4369 *length = size;
4370 (*env)->ReleaseStringChars (env, string, java);
4371 return buffer;
4372}
4373
4374JNIEXPORT void JNICALL
4375NATIVE_NAME (beginBatchEdit) (JNIEnv *env, jobject object, jshort window)
4376{
4377 union android_event event;
4378
4379 event.ime.type = ANDROID_INPUT_METHOD;
4380 event.ime.serial = ++event_serial;
4381 event.ime.window = window;
4382 event.ime.operation = ANDROID_IME_START_BATCH_EDIT;
4383 event.ime.start = 0;
4384 event.ime.end = 0;
4385 event.ime.length = 0;
4386 event.ime.position = 0;
4387 event.ime.text = NULL;
4388 event.ime.counter = ++edit_counter;
4389
4390 android_write_event (&event);
4391}
4392
4393JNIEXPORT void JNICALL
4394NATIVE_NAME (endBatchEdit) (JNIEnv *env, jobject object, jshort window)
4395{
4396 union android_event event;
4397
4398 event.ime.type = ANDROID_INPUT_METHOD;
4399 event.ime.serial = ++event_serial;
4400 event.ime.window = window;
4401 event.ime.operation = ANDROID_IME_END_BATCH_EDIT;
4402 event.ime.start = 0;
4403 event.ime.end = 0;
4404 event.ime.length = 0;
4405 event.ime.position = 0;
4406 event.ime.text = NULL;
4407 event.ime.counter = ++edit_counter;
4408
4409 android_write_event (&event);
4410}
4411
4412JNIEXPORT void JNICALL
4413NATIVE_NAME (commitCompletion) (JNIEnv *env, jobject object, jshort window,
4414 jstring completion_text, jint position)
4415{
4416 union android_event event;
4417 unsigned short *text;
4418 size_t length;
4419
4420 /* First, obtain a copy of the Java string. */
4421 text = android_copy_java_string (env, completion_text, &length);
4422
4423 if (!text)
4424 return;
4425
4426 /* Next, populate the event. Events will always eventually be
4427 delivered on Android, so handle_one_android_event can be relied
4428 on to free text. */
4429
4430 event.ime.type = ANDROID_INPUT_METHOD;
4431 event.ime.serial = ++event_serial;
4432 event.ime.window = window;
4433 event.ime.operation = ANDROID_IME_COMMIT_TEXT;
4434 event.ime.start = 0;
4435 event.ime.end = 0;
4436 event.ime.length = min (length, PTRDIFF_MAX);
4437 event.ime.position = position;
4438 event.ime.text = text;
4439 event.ime.counter = ++edit_counter;
4440
4441 android_write_event (&event);
4442}
4443
4444JNIEXPORT void JNICALL
4445NATIVE_NAME (commitText) (JNIEnv *env, jobject object, jshort window,
4446 jstring commit_text, jint position)
4447{
4448 union android_event event;
4449 unsigned short *text;
4450 size_t length;
4451
4452 /* First, obtain a copy of the Java string. */
4453 text = android_copy_java_string (env, commit_text, &length);
4454
4455 if (!text)
4456 return;
4457
4458 /* Next, populate the event. Events will always eventually be
4459 delivered on Android, so handle_one_android_event can be relied
4460 on to free text. */
4461
4462 event.ime.type = ANDROID_INPUT_METHOD;
4463 event.ime.serial = ++event_serial;
4464 event.ime.window = window;
4465 event.ime.operation = ANDROID_IME_COMMIT_TEXT;
4466 event.ime.start = 0;
4467 event.ime.end = 0;
4468 event.ime.length = min (length, PTRDIFF_MAX);
4469 event.ime.position = position;
4470 event.ime.text = text;
4471 event.ime.counter = ++edit_counter;
4472
4473 android_write_event (&event);
4474}
4475
4476JNIEXPORT void JNICALL
4477NATIVE_NAME (deleteSurroundingText) (JNIEnv *env, jobject object,
4478 jshort window, jint left_length,
4479 jint right_length)
4480{
4481 union android_event event;
4482
4483 event.ime.type = ANDROID_INPUT_METHOD;
4484 event.ime.serial = ++event_serial;
4485 event.ime.window = window;
4486 event.ime.operation = ANDROID_IME_DELETE_SURROUNDING_TEXT;
4487 event.ime.start = left_length;
4488 event.ime.end = right_length;
4489 event.ime.length = 0;
4490 event.ime.position = 0;
4491 event.ime.text = NULL;
4492 event.ime.counter = ++edit_counter;
4493
4494 android_write_event (&event);
4495}
4496
4497JNIEXPORT void JNICALL
4498NATIVE_NAME (finishComposingText) (JNIEnv *env, jobject object,
4499 jshort window)
4500{
4501 union android_event event;
4502
4503 event.ime.type = ANDROID_INPUT_METHOD;
4504 event.ime.serial = ++event_serial;
4505 event.ime.window = window;
4506 event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT;
4507 event.ime.start = 0;
4508 event.ime.end = 0;
4509 event.ime.length = 0;
4510 event.ime.position = 0;
4511 event.ime.text = NULL;
4512 event.ime.counter = ++edit_counter;
4513
4514 android_write_event (&event);
4515}
4516
4517/* Structure describing the context used for a text query. */
4518
4519struct android_conversion_query_context
4520{
4521 /* The conversion request. */
4522 struct textconv_callback_struct query;
4523
4524 /* The window the request is being made on. */
4525 android_window window;
4526
4527 /* Whether or not the request was successful. */
4528 bool success;
4529};
4530
4531/* Obtain the text from the frame whose window is that specified in
4532 DATA using the text conversion query specified there.
4533
4534 Adjust the query position to skip over any active composing region.
4535
4536 Set ((struct android_conversion_query_context *) DATA)->success on
4537 success. */
4538
4539static void
4540android_perform_conversion_query (void *data)
4541{
4542 struct android_conversion_query_context *context;
4543 struct frame *f;
4544
4545 context = data;
4546
4547 /* Find the frame associated with the window. */
4548 f = android_window_to_frame (NULL, context->window);
4549
4550 if (!f)
4551 return;
4552
4553 textconv_query (f, &context->query,
4554 TEXTCONV_SKIP_CONVERSION_REGION);
4555
4556 /* context->query.text will have been set even if textconv_query
4557 returns 1. */
4558
4559 context->success = true;
4560}
4561
4562/* Convert a string BUFFERS containing N characters in Emacs's
4563 internal multibyte encoding to a Java string utilizing the
4564 specified JNI environment.
4565
4566 If N is equal to BYTES, then BUFFER is a single byte buffer.
4567 Otherwise, BUFFER is a multibyte buffer.
4568
4569 Make sure N and BYTES are absolutely correct, or you are asking for
4570 trouble.
4571
4572 Value is the string upon success, NULL otherwise. Any exceptions
4573 generated are not cleared. */
4574
4575static jstring
4576android_text_to_string (JNIEnv *env, char *buffer, ptrdiff_t n,
4577 ptrdiff_t bytes)
4578{
4579 jchar *utf16;
4580 size_t size, index;
4581 jstring string;
4582 int encoded;
4583
4584 if (n == bytes)
4585 {
4586 /* This buffer holds no multibyte characters. */
4587
4588 if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
4589 return NULL;
4590
4591 utf16 = malloc (size);
4592 index = 0;
4593
4594 if (!utf16)
4595 return NULL;
4596
4597 while (n--)
4598 {
4599 utf16[index] = buffer[index];
4600 index++;
4601 }
4602
4603 string = (*env)->NewString (env, utf16, bytes);
4604 free (utf16);
4605
4606 return string;
4607 }
4608
4609 /* Allocate enough to hold N characters. */
4610
4611 if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size))
4612 return NULL;
4613
4614 utf16 = malloc (size);
4615 index = 0;
4616
4617 if (!utf16)
4618 return NULL;
4619
4620 while (n--)
4621 {
4622 eassert (CHAR_HEAD_P (*buffer));
4623 encoded = STRING_CHAR ((unsigned char *) buffer);
4624
4625 /* Now figure out how to save ENCODED into the string.
4626 Emacs operates on multibyte characters, not UTF-16
4627 characters with surrogate pairs as Android does.
4628
4629 However, character positions in Java are represented in 2
4630 byte units, meaning that the text position reported to
4631 Android can become out of sync if characters are found in a
4632 buffer that require surrogate pairs.
4633
4634 The hack used by Emacs is to simply replace each multibyte
4635 character that doesn't fit in a jchar with the NULL
4636 character. */
4637
4638 if (encoded >= 65536)
4639 encoded = 0;
4640
4641 utf16[index++] = encoded;
4642 buffer += BYTES_BY_CHAR_HEAD (*buffer);
4643 }
4644
4645 /* Create the string. */
4646 string = (*env)->NewString (env, utf16, index);
4647 free (utf16);
4648 return string;
4649}
4650
4651JNIEXPORT jstring JNICALL
4652NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object,
4653 jshort window)
4654{
4655 return NULL;
4656}
4657
4658JNIEXPORT jstring JNICALL
4659NATIVE_NAME (getTextAfterCursor) (JNIEnv *env, jobject object, jshort window,
4660 jint length, jint flags)
4661{
4662 struct android_conversion_query_context context;
4663 jstring string;
4664
4665 /* First, set up the conversion query. */
4666 context.query.position = 0;
4667 context.query.direction = TEXTCONV_FORWARD_CHAR;
4668 context.query.factor = min (length, 65535);
4669 context.query.operation = TEXTCONV_RETRIEVAL;
4670
4671 /* Next, set the rest of the context. */
4672 context.window = window;
4673 context.success = false;
4674
4675 /* Now try to perform the query. */
4676 android_sync_edit ();
4677 if (android_run_in_emacs_thread (android_perform_conversion_query,
4678 &context))
4679 return NULL;
4680
4681 if (!context.success)
4682 return NULL;
4683
4684 /* context->query.text now contains the text in Emacs's internal
4685 UTF-8 based encoding.
4686
4687 Convert it to Java's UTF-16 encoding, which is the same as
4688 UTF-16, except that NULL bytes are encoded as surrogate pairs.
4689
4690 This assumes that `free' can free data allocated with xmalloc. */
4691
4692 string = android_text_to_string (env, context.query.text.text,
4693 context.query.text.length,
4694 context.query.text.bytes);
4695 free (context.query.text.text);
4696
4697 return string;
4698}
4699
4700JNIEXPORT jstring JNICALL
4701NATIVE_NAME (getTextBeforeCursor) (JNIEnv *env, jobject object, jshort window,
4702 jint length, jint flags)
4703{
4704 struct android_conversion_query_context context;
4705 jstring string;
4706
4707 /* First, set up the conversion query. */
4708 context.query.position = 0;
4709 context.query.direction = TEXTCONV_BACKWARD_CHAR;
4710 context.query.factor = min (length, 65535);
4711 context.query.operation = TEXTCONV_RETRIEVAL;
4712
4713 /* Next, set the rest of the context. */
4714 context.window = window;
4715 context.success = false;
4716
4717 /* Now try to perform the query. */
4718 android_sync_edit ();
4719 if (android_run_in_emacs_thread (android_perform_conversion_query,
4720 &context))
4721 return NULL;
4722
4723 if (!context.success)
4724 return NULL;
4725
4726 /* context->query.text now contains the text in Emacs's internal
4727 UTF-8 based encoding.
4728
4729 Convert it to Java's UTF-16 encoding, which is the same as
4730 UTF-16, except that NULL bytes are encoded as surrogate pairs.
4731
4732 This assumes that `free' can free data allocated with xmalloc. */
4733
4734 string = android_text_to_string (env, context.query.text.text,
4735 context.query.text.length,
4736 context.query.text.bytes);
4737 free (context.query.text.text);
4738
4739 return string;
4740}
4741
4742JNIEXPORT void JNICALL
4743NATIVE_NAME (setComposingText) (JNIEnv *env, jobject object, jshort window,
4744 jstring composing_text,
4745 jint new_cursor_position)
4746{
4747 union android_event event;
4748 unsigned short *text;
4749 size_t length;
4750
4751 /* First, obtain a copy of the Java string. */
4752 text = android_copy_java_string (env, composing_text, &length);
4753
4754 if (!text)
4755 return;
4756
4757 /* Next, populate the event. Events will always eventually be
4758 delivered on Android, so handle_one_android_event can be relied
4759 on to free text. */
4760
4761 event.ime.type = ANDROID_INPUT_METHOD;
4762 event.ime.serial = ++event_serial;
4763 event.ime.window = window;
4764 event.ime.operation = ANDROID_IME_SET_COMPOSING_TEXT;
4765 event.ime.start = 0;
4766 event.ime.end = 0;
4767 event.ime.length = min (length, PTRDIFF_MAX);
4768 event.ime.position = new_cursor_position;
4769 event.ime.text = text;
4770 event.ime.counter = ++edit_counter;
4771
4772 android_write_event (&event);
4773}
4774
4775JNIEXPORT void JNICALL
4776NATIVE_NAME (setComposingRegion) (JNIEnv *env, jobject object, jshort window,
4777 jint start, jint end)
4778{
4779 union android_event event;
4780
4781 event.ime.type = ANDROID_INPUT_METHOD;
4782 event.ime.serial = ++event_serial;
4783 event.ime.window = window;
4784 event.ime.operation = ANDROID_IME_SET_COMPOSING_REGION;
4785 event.ime.start = start;
4786 event.ime.end = end;
4787 event.ime.length = 0;
4788 event.ime.position = 0;
4789 event.ime.text = NULL;
4790 event.ime.counter = ++edit_counter;
4791
4792 android_write_event (&event);
4793}
4794
4795JNIEXPORT void JNICALL
4796NATIVE_NAME (setSelection) (JNIEnv *env, jobject object, jshort window,
4797 jint start, jint end)
4798{
4799 union android_event event;
4800
4801 /* While IMEs want access to the entire selection, Emacs only
4802 supports setting the point. */
4803
4804 event.ime.type = ANDROID_INPUT_METHOD;
4805 event.ime.serial = ++event_serial;
4806 event.ime.window = window;
4807 event.ime.operation = ANDROID_IME_SET_POINT;
4808 event.ime.start = 0;
4809 event.ime.end = 0;
4810 event.ime.length = 0;
4811 event.ime.position = start;
4812 event.ime.text = NULL;
4813 event.ime.counter = ++edit_counter;
4814}
4815
4816/* Structure describing the context for `getSelection'. */
4817
4818struct android_get_selection_context
4819{
4820 /* The window in question. */
4821 android_window window;
4822
4823 /* The position of the window's point when it was last
4824 redisplayed. */
4825 ptrdiff_t point;
4826};
4827
4828/* Function run on the main thread by `getSelection'.
4829 Place the character position of point in PT. */
4830
4831static void
4832android_get_selection (void *data)
4833{
4834 struct android_get_selection_context *context;
4835 struct frame *f;
4836 struct window *w;
4837
4838 context = data;
4839
4840 /* Look up the associated frame and its selected window. */
4841 f = android_window_to_frame (NULL, context->window);
4842
4843 if (!f)
4844 context->point = -1;
4845 else
4846 {
4847 w = XWINDOW (f->selected_window);
4848
4849 /* Return W's point at the time of the last redisplay. This is
4850 rather important to keep the input method consistent with the
4851 contents of the display. */
4852 context->point = w->last_point;
4853 }
4854}
4855
4856JNIEXPORT jint JNICALL
4857NATIVE_NAME (getSelection) (JNIEnv *env, jobject object, jshort window)
4858{
4859 struct android_get_selection_context context;
4860
4861 context.window = window;
4862
4863 android_sync_edit ();
4864 if (android_run_in_emacs_thread (android_get_selection,
4865 &context))
4866 return -1;
4867
4868 if (context.point == -1)
4869 return -1;
4870
4871 return min (context.point, TYPE_MAXIMUM (jint));
4872}
4873
4874JNIEXPORT void JNICALL
4875NATIVE_NAME (performEditorAction) (JNIEnv *env, jobject object,
4876 jshort window, int action)
4877{
4878 union android_event event;
4879
4880 /* Undocumented behavior: performEditorAction is apparently expected
4881 to finish composing any text. */
4882
4883 NATIVE_NAME (finishComposingText) (env, object, window);
4884
4885 event.xkey.type = ANDROID_KEY_PRESS;
4886 event.xkey.serial = ++event_serial;
4887 event.xkey.window = window;
4888 event.xkey.time = 0;
4889 event.xkey.state = 0;
4890 event.xkey.keycode = 66;
4891 event.xkey.unicode_char = 0;
4892
4893 android_write_event (&event);
4894}
4895
4896struct android_get_extracted_text_context
4897{
4898 /* The parameters of the request. */
4899 int hint_max_chars;
4900
4901 /* Token for the request. */
4902 int token;
4903
4904 /* The returned text, or NULL. */
4905 char *text;
4906
4907 /* The size of that text in characters and bytes. */
4908 ptrdiff_t length, bytes;
4909
4910 /* Offsets into that text. */
4911 ptrdiff_t start, offset;
4912
4913 /* The window. */
4914 android_window window;
4915};
4916
4917/* Return the extracted text in the extracted text context specified
4918 by DATA. */
4919
4920static void
4921android_get_extracted_text (void *data)
4922{
4923 struct android_get_extracted_text_context *request;
4924 struct frame *f;
4925
4926 request = data;
4927
4928 /* Find the frame associated with the window. */
4929 f = android_window_to_frame (NULL, request->window);
4930
4931 if (!f)
4932 return;
4933
4934 /* Now get the extracted text. */
4935 request->text
4936 = get_extracted_text (f, min (request->hint_max_chars, 600),
4937 &request->start, &request->offset,
4938 &request->length, &request->bytes);
4939}
4940
4941/* Structure describing the `ExtractedTextRequest' class.
4942 Valid only on the UI thread. */
4943
4944struct android_extracted_text_request_class
4945{
4946 bool initialized;
4947 jfieldID hint_max_chars;
4948 jfieldID token;
4949};
4950
4951/* Structure describing the `ExtractedText' class.
4952 Valid only on the UI thread. */
4953
4954struct android_extracted_text_class
4955{
4956 jclass class;
4957 jmethodID constructor;
4958 jfieldID partial_start_offset;
4959 jfieldID partial_end_offset;
4960 jfieldID selection_start;
4961 jfieldID selection_end;
4962 jfieldID start_offset;
4963 jfieldID text;
4964};
4965
4966JNIEXPORT jobject JNICALL
4967NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object,
4968 jshort window, jobject request,
4969 jint flags)
4970{
4971 struct android_get_extracted_text_context context;
4972 static struct android_extracted_text_request_class request_class;
4973 static struct android_extracted_text_class text_class;
4974 jstring string;
4975 jclass class;
4976 jobject object;
4977
4978 /* TODO: report changes to extracted text. */
4979
4980 /* Initialize both classes if necessary. */
4981
4982 if (!request_class.initialized)
4983 {
4984 class
4985 = (*env)->FindClass (env, ("android/view/inputmethod"
4986 "/ExtractedTextRequest"));
4987 assert (class);
4988
4989 request_class.hint_max_chars
4990 = (*env)->GetFieldID (env, class, "hintMaxChars", "I");
4991 assert (request_class.hint_max_chars);
4992
4993 request_class.token
4994 = (*env)->GetFieldID (env, class, "token", "I");
4995 assert (request_class.token);
4996
4997 request_class.initialized = true;
4998 }
4999
5000 if (!text_class.class)
5001 {
5002 text_class.class
5003 = (*env)->FindClass (env, ("android/view/inputmethod"
5004 "/ExtractedText"));
5005 assert (text_class.class);
5006
5007 class
5008 = text_class.class
5009 = (*env)->NewGlobalRef (env, text_class.class);
5010 assert (text_class.class);
5011
5012 text_class.partial_start_offset
5013 = (*env)->GetFieldID (env, class, "partialStartOffset", "I");
5014 text_class.partial_end_offset
5015 = (*env)->GetFieldID (env, class, "partialEndOffset", "I");
5016 text_class.selection_start
5017 = (*env)->GetFieldID (env, class, "selectionStart", "I");
5018 text_class.selection_end
5019 = (*env)->GetFieldID (env, class, "selectionEnd", "I");
5020 text_class.start_offset
5021 = (*env)->GetFieldID (env, class, "startOffset", "I");
5022 text_class.text
5023 = (*env)->GetFieldID (env, class, "text", "Ljava/lang/CharSequence;");
5024 text_class.constructor
5025 = (*env)->GetMethodID (env, class, "<init>", "()V");
5026 }
5027
5028 context.hint_max_chars
5029 = (*env)->GetIntField (env, request, request_class.hint_max_chars);
5030 context.token
5031 = (*env)->GetIntField (env, request, request_class.token);
5032 context.text = NULL;
5033 context.window = window;
5034
5035 android_sync_edit ();
5036 if (android_run_in_emacs_thread (android_get_extracted_text,
5037 &context))
5038 return NULL;
5039
5040 if (!context.text)
5041 return NULL;
5042
5043 /* Encode the returned text. */
5044 string = android_text_to_string (env, context.text, context.length,
5045 context.bytes);
5046 free (context.text);
5047
5048 if (!string)
5049 return NULL;
5050
5051 /* Create an ExtractedText object containing this information. */
5052 object = (*android_java_env)->NewObject (env, text_class.class,
5053 text_class.constructor);
5054 if (!object)
5055 return NULL;
5056
5057 (*env)->SetIntField (env, object, text_class.partial_start_offset, -1);
5058 (*env)->SetIntField (env, object, text_class.partial_end_offset, -1);
5059 (*env)->SetIntField (env, object, text_class.selection_start,
5060 min (context.offset, TYPE_MAXIMUM (jint)));
5061 (*env)->SetIntField (env, object, text_class.selection_end,
5062 min (context.offset, TYPE_MAXIMUM (jint)));
5063 (*env)->SetIntField (env, object, text_class.start_offset,
5064 min (context.start, TYPE_MAXIMUM (jint)));
5065 (*env)->SetObjectField (env, object, text_class.text, string);
5066 return object;
5067}
5068
5069#ifdef __clang__
5070#pragma clang diagnostic pop
5071#else
5072#pragma GCC diagnostic pop
5073#endif
5074
5075
5076
5077/* Tell the input method where the composing region and selection of
5078 F's selected window is located. W should be F's selected window;
5079 if it is NULL, then F->selected_window is used in its place. */
5080
5081static void
5082android_update_selection (struct frame *f, struct window *w)
5083{
5084 ptrdiff_t start, end, point;
5085
5086 if (MARKERP (f->conversion.compose_region_start))
5087 {
5088 eassert (MARKERP (f->conversion.compose_region_end));
5089
5090 start = marker_position (f->conversion.compose_region_start);
5091 end = marker_position (f->conversion.compose_region_end);
5092 }
5093 else
5094 start = -1, end = -1;
5095
5096 /* Now constrain START and END to the maximium size of a Java
5097 integer. */
5098 start = min (start, TYPE_MAXIMUM (jint));
5099 end = min (end, TYPE_MAXIMUM (jint));
5100
5101 if (!w)
5102 w = XWINDOW (f->selected_window);
5103
5104 /* Figure out where the point is. */
5105 point = min (w->last_point, TYPE_MAXIMUM (jint));
5106
5107 /* Send the update. */
5108 android_update_ic (FRAME_ANDROID_WINDOW (f), point, point,
5109 start, end);
5110}
5111
5112/* Notice that the input method connection to F should be reset as a
5113 result of a change to its contents. */
5114
5115static void
5116android_reset_conversion (struct frame *f)
5117{
5118 /* Reset the input method.
5119
5120 Pick an appropriate ``input mode'' based on whether or not the
5121 minibuffer window is selected; this controls whether or not
5122 ``RET'' inserts a newline or sends an actual key event. */
5123 android_reset_ic (FRAME_ANDROID_WINDOW (f),
5124 (EQ (f->selected_window,
5125 f->minibuffer_window)
5126 ? ANDROID_IC_MODE_ACTION
5127 : ANDROID_IC_MODE_TEXT));
5128
5129 /* Move its selection to the specified position. */
5130 android_update_selection (f, NULL);
5131}
5132
5133/* Notice that point has moved in the F's selected window's selected
5134 buffer. W is the window, and BUFFER is that buffer. */
5135
5136static void
5137android_set_point (struct frame *f, struct window *w,
5138 struct buffer *buffer)
5139{
5140 android_update_selection (f, w);
5141}
5142
5143/* Notice that the composition region on F's old selected window has
5144 changed. */
5145
5146static void
5147android_compose_region_changed (struct frame *f)
5148{
5149 android_update_selection (f, XWINDOW (f->old_selected_window));
5150}
5151
5152/* Notice that the text conversion has completed. */
5153
5154static void
5155android_notify_conversion (unsigned long counter)
5156{
5157 int sval;
5158
5159 if (last_edit_counter < counter)
5160 __atomic_store_n (&last_edit_counter, counter,
5161 __ATOMIC_SEQ_CST);
5162
5163 sem_getvalue (&edit_sem, &sval);
5164
5165 if (sval <= 0)
5166 sem_post (&edit_sem);
5167}
5168
5169/* Android text conversion interface. */
5170
5171static struct textconv_interface text_conversion_interface =
5172 {
5173 android_reset_conversion,
5174 android_set_point,
5175 android_compose_region_changed,
5176 android_notify_conversion,
5177 };
5178
5179
5180
4151extern frame_parm_handler android_frame_parm_handlers[]; 5181extern frame_parm_handler android_frame_parm_handlers[];
4152 5182
4153#endif /* !ANDROID_STUBIFY */ 5183#endif /* !ANDROID_STUBIFY */
@@ -4327,6 +5357,11 @@ android_term_init (void)
4327 5357
4328 /* Set the baud rate to the same value it gets set to on X. */ 5358 /* Set the baud rate to the same value it gets set to on X. */
4329 baud_rate = 19200; 5359 baud_rate = 19200;
5360
5361#ifndef ANDROID_STUBIFY
5362 sem_init (&edit_sem, false, 0);
5363 register_textconv_interface (&text_conversion_interface);
5364#endif
4330} 5365}
4331 5366
4332 5367