diff options
| author | Po Lu | 2023-02-15 12:23:03 +0800 |
|---|---|---|
| committer | Po Lu | 2023-02-15 12:23:03 +0800 |
| commit | a158c1d5b964fda36f752998cef076d581dc4df4 (patch) | |
| tree | b9c7a22a7259dbd876be0e047304a338ef05e334 /java/org | |
| parent | 5a7855e84abd56e55b456aef4a43ae7623e76899 (diff) | |
| download | emacs-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 'java/org')
| -rw-r--r-- | java/org/gnu/emacs/EmacsContextMenu.java | 15 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsDialog.java | 15 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsEditable.java | 300 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsInputConnection.java | 187 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsNative.java | 46 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsService.java | 117 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsView.java | 55 |
7 files changed, 267 insertions, 468 deletions
diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 92429410d03..184c611bf9d 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java | |||
| @@ -279,20 +279,7 @@ public class EmacsContextMenu | |||
| 279 | } | 279 | } |
| 280 | }; | 280 | }; |
| 281 | 281 | ||
| 282 | synchronized (runnable) | 282 | EmacsService.syncRunnable (runnable); |
| 283 | { | ||
| 284 | EmacsService.SERVICE.runOnUiThread (runnable); | ||
| 285 | |||
| 286 | try | ||
| 287 | { | ||
| 288 | runnable.wait (); | ||
| 289 | } | ||
| 290 | catch (InterruptedException e) | ||
| 291 | { | ||
| 292 | EmacsNative.emacsAbort (); | ||
| 293 | } | ||
| 294 | } | ||
| 295 | |||
| 296 | return rc.thing; | 283 | return rc.thing; |
| 297 | } | 284 | } |
| 298 | 285 | ||
diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index bd5e9ba8ee7..bc0333e99b9 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java | |||
| @@ -317,20 +317,7 @@ public class EmacsDialog implements DialogInterface.OnDismissListener | |||
| 317 | } | 317 | } |
| 318 | }; | 318 | }; |
| 319 | 319 | ||
| 320 | synchronized (runnable) | 320 | EmacsService.syncRunnable (runnable); |
| 321 | { | ||
| 322 | EmacsService.SERVICE.runOnUiThread (runnable); | ||
| 323 | |||
| 324 | try | ||
| 325 | { | ||
| 326 | runnable.wait (); | ||
| 327 | } | ||
| 328 | catch (InterruptedException e) | ||
| 329 | { | ||
| 330 | EmacsNative.emacsAbort (); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | return rc.thing; | 321 | return rc.thing; |
| 335 | } | 322 | } |
| 336 | 323 | ||
diff --git a/java/org/gnu/emacs/EmacsEditable.java b/java/org/gnu/emacs/EmacsEditable.java deleted file mode 100644 index 79af65a6ccd..00000000000 --- a/java/org/gnu/emacs/EmacsEditable.java +++ /dev/null | |||
| @@ -1,300 +0,0 @@ | |||
| 1 | /* Communication module for Android terminals. -*- c-file-style: "GNU" -*- | ||
| 2 | |||
| 3 | Copyright (C) 2023 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | This file is part of GNU Emacs. | ||
| 6 | |||
| 7 | GNU Emacs is free software: you can redistribute it and/or modify | ||
| 8 | it under the terms of the GNU General Public License as published by | ||
| 9 | the Free Software Foundation, either version 3 of the License, or (at | ||
| 10 | your option) any later version. | ||
| 11 | |||
| 12 | GNU Emacs is distributed in the hope that it will be useful, | ||
| 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | GNU General Public License for more details. | ||
| 16 | |||
| 17 | You should have received a copy of the GNU General Public License | ||
| 18 | along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | ||
| 19 | |||
| 20 | package org.gnu.emacs; | ||
| 21 | |||
| 22 | import android.text.InputFilter; | ||
| 23 | import android.text.SpannableStringBuilder; | ||
| 24 | import android.text.Spanned; | ||
| 25 | import android.text.SpanWatcher; | ||
| 26 | import android.text.Selection; | ||
| 27 | |||
| 28 | import android.content.Context; | ||
| 29 | |||
| 30 | import android.view.inputmethod.InputMethodManager; | ||
| 31 | import android.view.inputmethod.ExtractedText; | ||
| 32 | import android.view.inputmethod.ExtractedTextRequest; | ||
| 33 | |||
| 34 | import android.text.Spannable; | ||
| 35 | |||
| 36 | import android.util.Log; | ||
| 37 | |||
| 38 | import android.os.Build; | ||
| 39 | |||
| 40 | /* Android input methods insist on having access to buffer contents. | ||
| 41 | Since Emacs is not designed like ``any other Android text editor'', | ||
| 42 | that is not possible. | ||
| 43 | |||
| 44 | This file provides a fake editing buffer that is designed to weasel | ||
| 45 | as much information as possible out of an input method, without | ||
| 46 | actually providing buffer contents to Emacs. | ||
| 47 | |||
| 48 | The basic idea is to have the fake editing buffer be initially | ||
| 49 | empty. | ||
| 50 | |||
| 51 | When the input method inserts composed text, it sets a flag. | ||
| 52 | Updates to the buffer while the flag is set are sent to Emacs to be | ||
| 53 | displayed as ``preedit text''. | ||
| 54 | |||
| 55 | Once some heuristics decide that composition has been completed, | ||
| 56 | the composed text is sent to Emacs, and the text that was inserted | ||
| 57 | in this editing buffer is erased. */ | ||
| 58 | |||
| 59 | public class EmacsEditable extends SpannableStringBuilder | ||
| 60 | implements SpanWatcher | ||
| 61 | { | ||
| 62 | private static final String TAG = "EmacsEditable"; | ||
| 63 | |||
| 64 | /* Whether or not composition is currently in progress. */ | ||
| 65 | private boolean isComposing; | ||
| 66 | |||
| 67 | /* The associated input connection. */ | ||
| 68 | private EmacsInputConnection connection; | ||
| 69 | |||
| 70 | /* The associated IM manager. */ | ||
| 71 | private InputMethodManager imManager; | ||
| 72 | |||
| 73 | /* Any extracted text an input method may be monitoring. */ | ||
| 74 | private ExtractedText extractedText; | ||
| 75 | |||
| 76 | /* The corresponding text request. */ | ||
| 77 | private ExtractedTextRequest extractRequest; | ||
| 78 | |||
| 79 | /* The number of nested batch edits. */ | ||
| 80 | private int batchEditCount; | ||
| 81 | |||
| 82 | /* Whether or not invalidateInput should be called upon batch edits | ||
| 83 | ending. */ | ||
| 84 | private boolean pendingInvalidate; | ||
| 85 | |||
| 86 | /* The ``composing span'' indicating the bounds of an ongoing | ||
| 87 | character composition. */ | ||
| 88 | private Object composingSpan; | ||
| 89 | |||
| 90 | public | ||
| 91 | EmacsEditable (EmacsInputConnection connection) | ||
| 92 | { | ||
| 93 | /* Initialize the editable with one initial space, so backspace | ||
| 94 | always works. */ | ||
| 95 | super (); | ||
| 96 | |||
| 97 | Object tem; | ||
| 98 | Context context; | ||
| 99 | |||
| 100 | this.connection = connection; | ||
| 101 | |||
| 102 | context = connection.view.getContext (); | ||
| 103 | tem = context.getSystemService (Context.INPUT_METHOD_SERVICE); | ||
| 104 | imManager = (InputMethodManager) tem; | ||
| 105 | |||
| 106 | /* To watch for changes to text properties on Android, you | ||
| 107 | add... a text property. */ | ||
| 108 | setSpan (this, 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE); | ||
| 109 | } | ||
| 110 | |||
| 111 | public void | ||
| 112 | endBatchEdit () | ||
| 113 | { | ||
| 114 | if (batchEditCount < 1) | ||
| 115 | return; | ||
| 116 | |||
| 117 | if (--batchEditCount == 0 && pendingInvalidate) | ||
| 118 | invalidateInput (); | ||
| 119 | } | ||
| 120 | |||
| 121 | public void | ||
| 122 | beginBatchEdit () | ||
| 123 | { | ||
| 124 | ++batchEditCount; | ||
| 125 | } | ||
| 126 | |||
| 127 | public void | ||
| 128 | setExtractedTextAndRequest (ExtractedText text, | ||
| 129 | ExtractedTextRequest request, | ||
| 130 | boolean monitor) | ||
| 131 | { | ||
| 132 | /* Extract the text. If monitor is set, also record it as the | ||
| 133 | text that is currently being extracted. */ | ||
| 134 | |||
| 135 | text.startOffset = 0; | ||
| 136 | text.selectionStart = Selection.getSelectionStart (this); | ||
| 137 | text.selectionEnd = Selection.getSelectionStart (this); | ||
| 138 | text.text = this; | ||
| 139 | |||
| 140 | if (monitor) | ||
| 141 | { | ||
| 142 | extractedText = text; | ||
| 143 | extractRequest = request; | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | public void | ||
| 148 | compositionStart () | ||
| 149 | { | ||
| 150 | isComposing = true; | ||
| 151 | } | ||
| 152 | |||
| 153 | public void | ||
| 154 | compositionEnd () | ||
| 155 | { | ||
| 156 | isComposing = false; | ||
| 157 | sendComposingText (null); | ||
| 158 | } | ||
| 159 | |||
| 160 | private void | ||
| 161 | sendComposingText (String string) | ||
| 162 | { | ||
| 163 | EmacsWindow window; | ||
| 164 | long time, serial; | ||
| 165 | |||
| 166 | window = connection.view.window; | ||
| 167 | |||
| 168 | if (window.isDestroyed ()) | ||
| 169 | return; | ||
| 170 | |||
| 171 | time = System.currentTimeMillis (); | ||
| 172 | |||
| 173 | /* A composition event is simply a special key event with a | ||
| 174 | keycode of -1. */ | ||
| 175 | |||
| 176 | synchronized (window.eventStrings) | ||
| 177 | { | ||
| 178 | serial | ||
| 179 | = EmacsNative.sendKeyPress (window.handle, time, 0, -1, -1); | ||
| 180 | |||
| 181 | /* Save the string so that android_lookup_string can find | ||
| 182 | it. */ | ||
| 183 | if (string != null) | ||
| 184 | window.saveUnicodeString ((int) serial, string); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | private void | ||
| 189 | invalidateInput () | ||
| 190 | { | ||
| 191 | int start, end, composingSpanStart, composingSpanEnd; | ||
| 192 | |||
| 193 | if (batchEditCount > 0) | ||
| 194 | { | ||
| 195 | Log.d (TAG, "invalidateInput: deferring for batch edit"); | ||
| 196 | pendingInvalidate = true; | ||
| 197 | return; | ||
| 198 | } | ||
| 199 | |||
| 200 | pendingInvalidate = false; | ||
| 201 | |||
| 202 | start = Selection.getSelectionStart (this); | ||
| 203 | end = Selection.getSelectionEnd (this); | ||
| 204 | |||
| 205 | if (composingSpan != null) | ||
| 206 | { | ||
| 207 | composingSpanStart = getSpanStart (composingSpan); | ||
| 208 | composingSpanEnd = getSpanEnd (composingSpan); | ||
| 209 | } | ||
| 210 | else | ||
| 211 | { | ||
| 212 | composingSpanStart = -1; | ||
| 213 | composingSpanEnd = -1; | ||
| 214 | } | ||
| 215 | |||
| 216 | Log.d (TAG, "invalidateInput: now " + start + ", " + end); | ||
| 217 | |||
| 218 | /* Tell the input method that the cursor changed. */ | ||
| 219 | imManager.updateSelection (connection.view, start, end, | ||
| 220 | composingSpanStart, | ||
| 221 | composingSpanEnd); | ||
| 222 | |||
| 223 | /* If there is any extracted text, tell the IME that it has | ||
| 224 | changed. */ | ||
| 225 | if (extractedText != null) | ||
| 226 | imManager.updateExtractedText (connection.view, | ||
| 227 | extractRequest.token, | ||
| 228 | extractedText); | ||
| 229 | } | ||
| 230 | |||
| 231 | public SpannableStringBuilder | ||
| 232 | replace (int start, int end, CharSequence tb, int tbstart, | ||
| 233 | int tbend) | ||
| 234 | { | ||
| 235 | super.replace (start, end, tb, tbstart, tbend); | ||
| 236 | |||
| 237 | /* If a change happens during composition, perform the change and | ||
| 238 | then send the text being composed. */ | ||
| 239 | |||
| 240 | if (isComposing) | ||
| 241 | sendComposingText (toString ()); | ||
| 242 | |||
| 243 | return this; | ||
| 244 | } | ||
| 245 | |||
| 246 | private boolean | ||
| 247 | isSelectionSpan (Object span) | ||
| 248 | { | ||
| 249 | return ((Selection.SELECTION_START == span | ||
| 250 | || Selection.SELECTION_END == span) | ||
| 251 | && (getSpanFlags (span) | ||
| 252 | & Spanned.SPAN_INTERMEDIATE) == 0); | ||
| 253 | } | ||
| 254 | |||
| 255 | @Override | ||
| 256 | public void | ||
| 257 | onSpanAdded (Spannable text, Object what, int start, int end) | ||
| 258 | { | ||
| 259 | Log.d (TAG, "onSpanAdded: " + text + " " + what + " " | ||
| 260 | + start + " " + end); | ||
| 261 | |||
| 262 | /* Try to find the composing span. This isn't a public API. */ | ||
| 263 | |||
| 264 | if (what.getClass ().getName ().contains ("ComposingText")) | ||
| 265 | composingSpan = what; | ||
| 266 | |||
| 267 | if (isSelectionSpan (what)) | ||
| 268 | invalidateInput (); | ||
| 269 | } | ||
| 270 | |||
| 271 | @Override | ||
| 272 | public void | ||
| 273 | onSpanChanged (Spannable text, Object what, int ostart, | ||
| 274 | int oend, int nstart, int nend) | ||
| 275 | { | ||
| 276 | Log.d (TAG, "onSpanChanged: " + text + " " + what + " " | ||
| 277 | + nstart + " " + nend); | ||
| 278 | |||
| 279 | if (isSelectionSpan (what)) | ||
| 280 | invalidateInput (); | ||
| 281 | } | ||
| 282 | |||
| 283 | @Override | ||
| 284 | public void | ||
| 285 | onSpanRemoved (Spannable text, Object what, | ||
| 286 | int start, int end) | ||
| 287 | { | ||
| 288 | Log.d (TAG, "onSpanRemoved: " + text + " " + what + " " | ||
| 289 | + start + " " + end); | ||
| 290 | |||
| 291 | if (isSelectionSpan (what)) | ||
| 292 | invalidateInput (); | ||
| 293 | } | ||
| 294 | |||
| 295 | public boolean | ||
| 296 | isInBatchEdit () | ||
| 297 | { | ||
| 298 | return batchEditCount > 0; | ||
| 299 | } | ||
| 300 | } | ||
diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 897a393b984..3cf4419838b 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java | |||
| @@ -25,6 +25,7 @@ import android.view.inputmethod.ExtractedText; | |||
| 25 | import android.view.inputmethod.ExtractedTextRequest; | 25 | import android.view.inputmethod.ExtractedTextRequest; |
| 26 | import android.view.inputmethod.InputMethodManager; | 26 | import android.view.inputmethod.InputMethodManager; |
| 27 | import android.view.inputmethod.SurroundingText; | 27 | import android.view.inputmethod.SurroundingText; |
| 28 | import android.view.inputmethod.TextSnapshot; | ||
| 28 | import android.view.KeyEvent; | 29 | import android.view.KeyEvent; |
| 29 | 30 | ||
| 30 | import android.text.Editable; | 31 | import android.text.Editable; |
| @@ -38,138 +39,156 @@ import android.util.Log; | |||
| 38 | public class EmacsInputConnection extends BaseInputConnection | 39 | public class EmacsInputConnection extends BaseInputConnection |
| 39 | { | 40 | { |
| 40 | private static final String TAG = "EmacsInputConnection"; | 41 | private static final String TAG = "EmacsInputConnection"; |
| 41 | public EmacsView view; | 42 | private EmacsView view; |
| 42 | private EmacsEditable editable; | 43 | private short windowHandle; |
| 43 | |||
| 44 | /* The length of the last string to be committed. */ | ||
| 45 | private int lastCommitLength; | ||
| 46 | |||
| 47 | int currentLargeOffset; | ||
| 48 | 44 | ||
| 49 | public | 45 | public |
| 50 | EmacsInputConnection (EmacsView view) | 46 | EmacsInputConnection (EmacsView view) |
| 51 | { | 47 | { |
| 52 | super (view, false); | 48 | super (view, true); |
| 49 | |||
| 53 | this.view = view; | 50 | this.view = view; |
| 54 | this.editable = new EmacsEditable (this); | 51 | this.windowHandle = view.window.handle; |
| 55 | } | 52 | } |
| 56 | 53 | ||
| 57 | @Override | 54 | @Override |
| 58 | public Editable | 55 | public boolean |
| 59 | getEditable () | 56 | beginBatchEdit () |
| 60 | { | 57 | { |
| 61 | return editable; | 58 | Log.d (TAG, "beginBatchEdit"); |
| 59 | EmacsNative.beginBatchEdit (windowHandle); | ||
| 60 | return true; | ||
| 62 | } | 61 | } |
| 63 | 62 | ||
| 64 | @Override | 63 | @Override |
| 65 | public boolean | 64 | public boolean |
| 66 | setComposingText (CharSequence text, int newCursorPosition) | 65 | endBatchEdit () |
| 67 | { | 66 | { |
| 68 | editable.compositionStart (); | 67 | Log.d (TAG, "endBatchEdit"); |
| 69 | super.setComposingText (text, newCursorPosition); | 68 | EmacsNative.endBatchEdit (windowHandle); |
| 70 | return true; | 69 | return true; |
| 71 | } | 70 | } |
| 72 | 71 | ||
| 73 | @Override | 72 | @Override |
| 74 | public boolean | 73 | public boolean |
| 75 | setComposingRegion (int start, int end) | 74 | commitCompletion (CompletionInfo info) |
| 76 | { | 75 | { |
| 77 | int i; | 76 | Log.d (TAG, "commitCompletion: " + info); |
| 78 | 77 | EmacsNative.commitCompletion (windowHandle, | |
| 79 | if (lastCommitLength != 0) | 78 | info.getText ().toString (), |
| 80 | { | 79 | info.getPosition ()); |
| 81 | Log.d (TAG, "Restarting composition for: " + lastCommitLength); | 80 | return true; |
| 82 | 81 | } | |
| 83 | for (i = 0; i < lastCommitLength; ++i) | ||
| 84 | sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN, | ||
| 85 | KeyEvent.KEYCODE_DEL)); | ||
| 86 | 82 | ||
| 87 | lastCommitLength = 0; | 83 | @Override |
| 88 | } | 84 | public boolean |
| 85 | commitText (CharSequence text, int newCursorPosition) | ||
| 86 | { | ||
| 87 | Log.d (TAG, "commitText: " + text + " " + newCursorPosition); | ||
| 88 | EmacsNative.commitText (windowHandle, text.toString (), | ||
| 89 | newCursorPosition); | ||
| 90 | return true; | ||
| 91 | } | ||
| 89 | 92 | ||
| 90 | editable.compositionStart (); | 93 | @Override |
| 91 | super.setComposingRegion (start, end); | 94 | public boolean |
| 95 | deleteSurroundingText (int leftLength, int rightLength) | ||
| 96 | { | ||
| 97 | Log.d (TAG, ("deleteSurroundingText: " | ||
| 98 | + leftLength + " " + rightLength)); | ||
| 99 | EmacsNative.deleteSurroundingText (windowHandle, leftLength, | ||
| 100 | rightLength); | ||
| 92 | return true; | 101 | return true; |
| 93 | } | 102 | } |
| 94 | 103 | ||
| 95 | @Override | 104 | @Override |
| 96 | public boolean | 105 | public boolean |
| 97 | finishComposingText () | 106 | finishComposingText () |
| 98 | { | 107 | { |
| 99 | editable.compositionEnd (); | 108 | Log.d (TAG, "finishComposingText"); |
| 100 | return super.finishComposingText (); | 109 | |
| 110 | EmacsNative.finishComposingText (windowHandle); | ||
| 111 | return true; | ||
| 112 | } | ||
| 113 | |||
| 114 | @Override | ||
| 115 | public String | ||
| 116 | getSelectedText (int flags) | ||
| 117 | { | ||
| 118 | Log.d (TAG, "getSelectedText: " + flags); | ||
| 119 | |||
| 120 | return EmacsNative.getSelectedText (windowHandle, flags); | ||
| 121 | } | ||
| 122 | |||
| 123 | @Override | ||
| 124 | public String | ||
| 125 | getTextAfterCursor (int length, int flags) | ||
| 126 | { | ||
| 127 | Log.d (TAG, "getTextAfterCursor: " + length + " " + flags); | ||
| 128 | |||
| 129 | return EmacsNative.getTextAfterCursor (windowHandle, length, | ||
| 130 | flags); | ||
| 131 | } | ||
| 132 | |||
| 133 | @Override | ||
| 134 | public String | ||
| 135 | getTextBeforeCursor (int length, int flags) | ||
| 136 | { | ||
| 137 | Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags); | ||
| 138 | |||
| 139 | return EmacsNative.getTextBeforeCursor (windowHandle, length, | ||
| 140 | flags); | ||
| 101 | } | 141 | } |
| 102 | 142 | ||
| 103 | @Override | 143 | @Override |
| 104 | public boolean | 144 | public boolean |
| 105 | beginBatchEdit () | 145 | setComposingText (CharSequence text, int newCursorPosition) |
| 106 | { | 146 | { |
| 107 | editable.beginBatchEdit (); | 147 | Log.d (TAG, "setComposingText: " + newCursorPosition); |
| 108 | return super.beginBatchEdit (); | 148 | |
| 149 | EmacsNative.setComposingText (windowHandle, text.toString (), | ||
| 150 | newCursorPosition); | ||
| 151 | return true; | ||
| 109 | } | 152 | } |
| 110 | 153 | ||
| 111 | @Override | 154 | @Override |
| 112 | public boolean | 155 | public boolean |
| 113 | endBatchEdit () | 156 | setComposingRegion (int start, int end) |
| 114 | { | 157 | { |
| 115 | editable.endBatchEdit (); | 158 | Log.d (TAG, "setComposingRegion: " + start + " " + end); |
| 116 | return super.endBatchEdit (); | 159 | |
| 160 | EmacsNative.setComposingRegion (windowHandle, start, end); | ||
| 161 | return true; | ||
| 117 | } | 162 | } |
| 118 | 163 | ||
| 119 | @Override | 164 | @Override |
| 120 | public boolean | 165 | public boolean |
| 121 | commitText (CharSequence text, int newCursorPosition) | 166 | performEditorAction (int editorAction) |
| 122 | { | 167 | { |
| 123 | editable.compositionEnd (); | 168 | Log.d (TAG, "performEditorAction: " + editorAction); |
| 124 | super.commitText (text, newCursorPosition); | ||
| 125 | |||
| 126 | /* An observation is that input methods rarely recompose trailing | ||
| 127 | spaces. Avoid re-setting the commit length in that case. */ | ||
| 128 | |||
| 129 | if (text.toString ().equals (" ")) | ||
| 130 | lastCommitLength += 1; | ||
| 131 | else | ||
| 132 | /* At this point, the editable is now empty. | ||
| 133 | |||
| 134 | The input method may try to cancel the edit upon a subsequent | ||
| 135 | backspace by calling setComposingRegion with a region that is | ||
| 136 | the length of TEXT. | ||
| 137 | |||
| 138 | Record this length in order to be able to send backspace | ||
| 139 | events to ``delete'' the text in that case. */ | ||
| 140 | lastCommitLength = text.length (); | ||
| 141 | |||
| 142 | Log.d (TAG, "commitText: \"" + text + "\""); | ||
| 143 | 169 | ||
| 170 | EmacsNative.performEditorAction (windowHandle, editorAction); | ||
| 144 | return true; | 171 | return true; |
| 145 | } | 172 | } |
| 146 | 173 | ||
| 147 | /* Return a large offset, cycling through 100000, 30000, 0. | 174 | @Override |
| 148 | The offset is typically used to force the input method to update | 175 | public ExtractedText |
| 149 | its notion of ``surrounding text'', bypassing any caching that | 176 | getExtractedText (ExtractedTextRequest request, int flags) |
| 150 | it might have in progress. | 177 | { |
| 178 | Log.d (TAG, "getExtractedText: " + request + " " + flags); | ||
| 179 | |||
| 180 | return EmacsNative.getExtractedText (windowHandle, request, | ||
| 181 | flags); | ||
| 182 | } | ||
| 151 | 183 | ||
| 152 | There must be another way to do this, but I can't find it. */ | 184 | |
| 185 | /* Override functions which are not implemented. */ | ||
| 153 | 186 | ||
| 154 | public int | 187 | @Override |
| 155 | largeSelectionOffset () | 188 | public TextSnapshot |
| 189 | takeSnapshot () | ||
| 156 | { | 190 | { |
| 157 | switch (currentLargeOffset) | 191 | Log.d (TAG, "takeSnapshot"); |
| 158 | { | 192 | return null; |
| 159 | case 0: | ||
| 160 | currentLargeOffset = 100000; | ||
| 161 | return 100000; | ||
| 162 | |||
| 163 | case 100000: | ||
| 164 | currentLargeOffset = 30000; | ||
| 165 | return 30000; | ||
| 166 | |||
| 167 | case 30000: | ||
| 168 | currentLargeOffset = 0; | ||
| 169 | return 0; | ||
| 170 | } | ||
| 171 | |||
| 172 | currentLargeOffset = 0; | ||
| 173 | return -1; | ||
| 174 | } | 193 | } |
| 175 | } | 194 | } |
diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index f0219843d35..aaac9446510 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java | |||
| @@ -22,6 +22,8 @@ package org.gnu.emacs; | |||
| 22 | import java.lang.System; | 22 | import java.lang.System; |
| 23 | 23 | ||
| 24 | import android.content.res.AssetManager; | 24 | import android.content.res.AssetManager; |
| 25 | import android.view.inputmethod.ExtractedText; | ||
| 26 | import android.view.inputmethod.ExtractedTextRequest; | ||
| 25 | 27 | ||
| 26 | public class EmacsNative | 28 | public class EmacsNative |
| 27 | { | 29 | { |
| @@ -161,6 +163,50 @@ public class EmacsNative | |||
| 161 | descriptor, or NULL if there is none. */ | 163 | descriptor, or NULL if there is none. */ |
| 162 | public static native byte[] getProcName (int fd); | 164 | public static native byte[] getProcName (int fd); |
| 163 | 165 | ||
| 166 | /* Notice that the Emacs thread will now start waiting for the main | ||
| 167 | thread's looper to respond. */ | ||
| 168 | public static native void beginSynchronous (); | ||
| 169 | |||
| 170 | /* Notice that the Emacs thread will has finished waiting for the | ||
| 171 | main thread's looper to respond. */ | ||
| 172 | public static native void endSynchronous (); | ||
| 173 | |||
| 174 | |||
| 175 | |||
| 176 | /* Input connection functions. These mostly correspond to their | ||
| 177 | counterparts in Android's InputConnection. */ | ||
| 178 | |||
| 179 | public static native void beginBatchEdit (short window); | ||
| 180 | public static native void endBatchEdit (short window); | ||
| 181 | public static native void commitCompletion (short window, String text, | ||
| 182 | int position); | ||
| 183 | public static native void commitText (short window, String text, | ||
| 184 | int position); | ||
| 185 | public static native void deleteSurroundingText (short window, | ||
| 186 | int leftLength, | ||
| 187 | int rightLength); | ||
| 188 | public static native void finishComposingText (short window); | ||
| 189 | public static native String getSelectedText (short window, int flags); | ||
| 190 | public static native String getTextAfterCursor (short window, int length, | ||
| 191 | int flags); | ||
| 192 | public static native String getTextBeforeCursor (short window, int length, | ||
| 193 | int flags); | ||
| 194 | public static native void setComposingText (short window, String text, | ||
| 195 | int newCursorPosition); | ||
| 196 | public static native void setComposingRegion (short window, int start, | ||
| 197 | int end); | ||
| 198 | public static native void setSelection (short window, int start, int end); | ||
| 199 | public static native void performEditorAction (short window, | ||
| 200 | int editorAction); | ||
| 201 | public static native ExtractedText getExtractedText (short window, | ||
| 202 | ExtractedTextRequest req, | ||
| 203 | int flags); | ||
| 204 | |||
| 205 | |||
| 206 | /* Return the current value of the selection, or -1 upon | ||
| 207 | failure. */ | ||
| 208 | public static native int getSelection (short window); | ||
| 209 | |||
| 164 | static | 210 | static |
| 165 | { | 211 | { |
| 166 | /* Older versions of Android cannot link correctly with shared | 212 | /* Older versions of Android cannot link correctly with shared |
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 2ec2ddf9bda..855a738a30f 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java | |||
| @@ -80,6 +80,11 @@ public class EmacsService extends Service | |||
| 80 | private EmacsThread thread; | 80 | private EmacsThread thread; |
| 81 | private Handler handler; | 81 | private Handler handler; |
| 82 | 82 | ||
| 83 | /* Keep this in synch with androidgui.h. */ | ||
| 84 | public static final int IC_MODE_NULL = 0; | ||
| 85 | public static final int IC_MODE_ACTION = 1; | ||
| 86 | public static final int IC_MODE_TEXT = 2; | ||
| 87 | |||
| 83 | /* Display metrics used by font backends. */ | 88 | /* Display metrics used by font backends. */ |
| 84 | public DisplayMetrics metrics; | 89 | public DisplayMetrics metrics; |
| 85 | 90 | ||
| @@ -258,20 +263,7 @@ public class EmacsService extends Service | |||
| 258 | } | 263 | } |
| 259 | }; | 264 | }; |
| 260 | 265 | ||
| 261 | synchronized (runnable) | 266 | syncRunnable (runnable); |
| 262 | { | ||
| 263 | runOnUiThread (runnable); | ||
| 264 | |||
| 265 | try | ||
| 266 | { | ||
| 267 | runnable.wait (); | ||
| 268 | } | ||
| 269 | catch (InterruptedException e) | ||
| 270 | { | ||
| 271 | EmacsNative.emacsAbort (); | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | return view.thing; | 267 | return view.thing; |
| 276 | } | 268 | } |
| 277 | 269 | ||
| @@ -292,19 +284,7 @@ public class EmacsService extends Service | |||
| 292 | } | 284 | } |
| 293 | }; | 285 | }; |
| 294 | 286 | ||
| 295 | synchronized (runnable) | 287 | syncRunnable (runnable); |
| 296 | { | ||
| 297 | runOnUiThread (runnable); | ||
| 298 | |||
| 299 | try | ||
| 300 | { | ||
| 301 | runnable.wait (); | ||
| 302 | } | ||
| 303 | catch (InterruptedException e) | ||
| 304 | { | ||
| 305 | EmacsNative.emacsAbort (); | ||
| 306 | } | ||
| 307 | } | ||
| 308 | } | 288 | } |
| 309 | 289 | ||
| 310 | public void | 290 | public void |
| @@ -502,19 +482,7 @@ public class EmacsService extends Service | |||
| 502 | } | 482 | } |
| 503 | }; | 483 | }; |
| 504 | 484 | ||
| 505 | synchronized (runnable) | 485 | syncRunnable (runnable); |
| 506 | { | ||
| 507 | runOnUiThread (runnable); | ||
| 508 | |||
| 509 | try | ||
| 510 | { | ||
| 511 | runnable.wait (); | ||
| 512 | } | ||
| 513 | catch (InterruptedException e) | ||
| 514 | { | ||
| 515 | EmacsNative.emacsAbort (); | ||
| 516 | } | ||
| 517 | } | ||
| 518 | } | 486 | } |
| 519 | 487 | ||
| 520 | 488 | ||
| @@ -594,20 +562,7 @@ public class EmacsService extends Service | |||
| 594 | } | 562 | } |
| 595 | }; | 563 | }; |
| 596 | 564 | ||
| 597 | synchronized (runnable) | 565 | syncRunnable (runnable); |
| 598 | { | ||
| 599 | runOnUiThread (runnable); | ||
| 600 | |||
| 601 | try | ||
| 602 | { | ||
| 603 | runnable.wait (); | ||
| 604 | } | ||
| 605 | catch (InterruptedException e) | ||
| 606 | { | ||
| 607 | EmacsNative.emacsAbort (); | ||
| 608 | } | ||
| 609 | } | ||
| 610 | |||
| 611 | return manager.thing; | 566 | return manager.thing; |
| 612 | } | 567 | } |
| 613 | 568 | ||
| @@ -622,4 +577,58 @@ public class EmacsService extends Service | |||
| 622 | startActivity (intent); | 577 | startActivity (intent); |
| 623 | System.exit (0); | 578 | System.exit (0); |
| 624 | } | 579 | } |
| 580 | |||
| 581 | /* Wait synchronously for the specified RUNNABLE to complete in the | ||
| 582 | UI thread. Must be called from the Emacs thread. */ | ||
| 583 | |||
| 584 | public static void | ||
| 585 | syncRunnable (Runnable runnable) | ||
| 586 | { | ||
| 587 | EmacsNative.beginSynchronous (); | ||
| 588 | |||
| 589 | synchronized (runnable) | ||
| 590 | { | ||
| 591 | SERVICE.runOnUiThread (runnable); | ||
| 592 | |||
| 593 | while (true) | ||
| 594 | { | ||
| 595 | try | ||
| 596 | { | ||
| 597 | runnable.wait (); | ||
| 598 | break; | ||
| 599 | } | ||
| 600 | catch (InterruptedException e) | ||
| 601 | { | ||
| 602 | continue; | ||
| 603 | } | ||
| 604 | } | ||
| 605 | } | ||
| 606 | |||
| 607 | EmacsNative.endSynchronous (); | ||
| 608 | } | ||
| 609 | |||
| 610 | public void | ||
| 611 | updateIC (EmacsWindow window, int newSelectionStart, | ||
| 612 | int newSelectionEnd, int composingRegionStart, | ||
| 613 | int composingRegionEnd) | ||
| 614 | { | ||
| 615 | Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart | ||
| 616 | + " " + newSelectionEnd + " " | ||
| 617 | + composingRegionStart + " " | ||
| 618 | + composingRegionEnd)); | ||
| 619 | window.view.imManager.updateSelection (window.view, | ||
| 620 | newSelectionStart, | ||
| 621 | newSelectionEnd, | ||
| 622 | composingRegionStart, | ||
| 623 | composingRegionEnd); | ||
| 624 | } | ||
| 625 | |||
| 626 | public void | ||
| 627 | resetIC (EmacsWindow window, int icMode) | ||
| 628 | { | ||
| 629 | Log.d (TAG, "resetIC: " + window); | ||
| 630 | |||
| 631 | window.view.setICMode (icMode); | ||
| 632 | window.view.imManager.restartInput (window.view); | ||
| 633 | } | ||
| 625 | }; | 634 | }; |
diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index bc3716f6da8..52da6d41f7d 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java | |||
| @@ -103,6 +103,13 @@ public class EmacsView extends ViewGroup | |||
| 103 | displayed whenever possible. */ | 103 | displayed whenever possible. */ |
| 104 | public boolean isCurrentlyTextEditor; | 104 | public boolean isCurrentlyTextEditor; |
| 105 | 105 | ||
| 106 | /* The associated input connection. */ | ||
| 107 | private EmacsInputConnection inputConnection; | ||
| 108 | |||
| 109 | /* The current IC mode. See `android_reset_ic' for more | ||
| 110 | details. */ | ||
| 111 | private int icMode; | ||
| 112 | |||
| 106 | public | 113 | public |
| 107 | EmacsView (EmacsWindow window) | 114 | EmacsView (EmacsWindow window) |
| 108 | { | 115 | { |
| @@ -554,14 +561,46 @@ public class EmacsView extends ViewGroup | |||
| 554 | public InputConnection | 561 | public InputConnection |
| 555 | onCreateInputConnection (EditorInfo info) | 562 | onCreateInputConnection (EditorInfo info) |
| 556 | { | 563 | { |
| 564 | int selection, mode; | ||
| 565 | |||
| 566 | /* Figure out what kind of IME behavior Emacs wants. */ | ||
| 567 | mode = getICMode (); | ||
| 568 | |||
| 557 | /* Make sure the input method never displays a full screen input | 569 | /* Make sure the input method never displays a full screen input |
| 558 | box that obscures Emacs. */ | 570 | box that obscures Emacs. */ |
| 559 | info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; | 571 | info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; |
| 560 | 572 | ||
| 561 | /* Set a reasonable inputType. */ | 573 | /* Set a reasonable inputType. */ |
| 562 | info.inputType = InputType.TYPE_NULL; | 574 | info.inputType = InputType.TYPE_CLASS_TEXT; |
| 575 | |||
| 576 | /* Obtain the current position of point and set it as the | ||
| 577 | selection. */ | ||
| 578 | selection = EmacsNative.getSelection (window.handle); | ||
| 579 | |||
| 580 | Log.d (TAG, "onCreateInputConnection: current selection is: " + selection); | ||
| 581 | |||
| 582 | /* If this fails or ANDROID_IC_MODE_NULL was requested, then don't | ||
| 583 | initialize the input connection. */ | ||
| 584 | if (selection == -1 || mode == EmacsService.IC_MODE_NULL) | ||
| 585 | { | ||
| 586 | info.inputType = InputType.TYPE_NULL; | ||
| 587 | return null; | ||
| 588 | } | ||
| 589 | |||
| 590 | if (mode == EmacsService.IC_MODE_ACTION) | ||
| 591 | info.imeOptions |= EditorInfo.IME_ACTION_DONE; | ||
| 563 | 592 | ||
| 564 | return null; | 593 | /* Set the initial selection fields. */ |
| 594 | info.initialSelStart = selection; | ||
| 595 | info.initialSelEnd = selection; | ||
| 596 | |||
| 597 | /* Create the input connection if necessary. */ | ||
| 598 | |||
| 599 | if (inputConnection == null) | ||
| 600 | inputConnection = new EmacsInputConnection (this); | ||
| 601 | |||
| 602 | /* Return the input connection. */ | ||
| 603 | return inputConnection; | ||
| 565 | } | 604 | } |
| 566 | 605 | ||
| 567 | @Override | 606 | @Override |
| @@ -572,4 +611,16 @@ public class EmacsView extends ViewGroup | |||
| 572 | keyboard. */ | 611 | keyboard. */ |
| 573 | return isCurrentlyTextEditor; | 612 | return isCurrentlyTextEditor; |
| 574 | } | 613 | } |
| 614 | |||
| 615 | public synchronized void | ||
| 616 | setICMode (int icMode) | ||
| 617 | { | ||
| 618 | this.icMode = icMode; | ||
| 619 | } | ||
| 620 | |||
| 621 | public synchronized int | ||
| 622 | getICMode () | ||
| 623 | { | ||
| 624 | return icMode; | ||
| 625 | } | ||
| 575 | }; | 626 | }; |