mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-02-16 09:14:18 +00:00
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.
This commit is contained in:
parent
5a7855e84a
commit
a158c1d5b9
27 changed files with 2806 additions and 525 deletions
|
|
@ -7217,7 +7217,7 @@ if test "$window_system" != "none"; then
|
|||
[Define if you poll periodically to detect C-g.])
|
||||
WINDOW_SYSTEM_OBJ="fontset.o fringe.o image.o"
|
||||
|
||||
if test "$window_system" = "x11"; then
|
||||
if test "$window_system" = "x11" || test "$REALLY_ANDROID" = "yes"; then
|
||||
AC_DEFINE([HAVE_TEXT_CONVERSION], [1],
|
||||
[Define if the window system has text conversion support.])
|
||||
WINDOW_SYSTEM_OBJ="$WINDOW_SYSTEM_OBJ textconv.o"
|
||||
|
|
|
|||
|
|
@ -109,3 +109,20 @@ Emacs quitting. @xref{Quitting}.
|
|||
The exact button is used to do this varies by system: on X, it is
|
||||
defined in the variable @code{x-quit-keysym}, and on Android, it is
|
||||
always the volume down button.
|
||||
|
||||
@cindex text conversion, keyboards
|
||||
Most input methods designed to work with on-screen keyboards perform
|
||||
buffer edits differently from desktop input methods.
|
||||
|
||||
On a conventional desktop windowing system, an input method will
|
||||
simply display the contents of any on going character compositions on
|
||||
screen, and send the appropriate key events to Emacs after completion.
|
||||
|
||||
However, on screen keyboard input methods directly perform edits to
|
||||
the selected window of each frame; this is known as ``text
|
||||
conversion'', or ``string conversion'' under the X Window System.
|
||||
|
||||
Text conversion is performed asynchronously whenever Emacs receives
|
||||
a request to perform the conversion from the input method. After the
|
||||
conversion completes, a @code{text-conversion} event is sent.
|
||||
@xref{Misc Events,,, elisp, the Emacs Reference Manual}.
|
||||
|
|
|
|||
|
|
@ -2200,6 +2200,15 @@ the buffer in which the xwidget will be displayed, using
|
|||
A few other event types represent occurrences within the system.
|
||||
|
||||
@table @code
|
||||
@cindex @code{text-conversion} event
|
||||
@item text-conversion
|
||||
This kind of event is sent @strong{after} a system-wide input method
|
||||
performs an edit to one or more buffers.
|
||||
|
||||
Once the event is sent, the input method may already have made changes
|
||||
to multiple frames. @c TODO: allow querying which frames to which
|
||||
@c changes have been made.
|
||||
|
||||
@cindex @code{delete-frame} event
|
||||
@item (delete-frame (@var{frame}))
|
||||
This kind of event indicates that the user gave the window manager
|
||||
|
|
|
|||
|
|
@ -279,20 +279,7 @@ private class Item implements MenuItem.OnMenuItemClickListener
|
|||
}
|
||||
};
|
||||
|
||||
synchronized (runnable)
|
||||
{
|
||||
EmacsService.SERVICE.runOnUiThread (runnable);
|
||||
|
||||
try
|
||||
{
|
||||
runnable.wait ();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
EmacsNative.emacsAbort ();
|
||||
}
|
||||
}
|
||||
|
||||
EmacsService.syncRunnable (runnable);
|
||||
return rc.thing;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -317,20 +317,7 @@ private class EmacsButton implements View.OnClickListener,
|
|||
}
|
||||
};
|
||||
|
||||
synchronized (runnable)
|
||||
{
|
||||
EmacsService.SERVICE.runOnUiThread (runnable);
|
||||
|
||||
try
|
||||
{
|
||||
runnable.wait ();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
EmacsNative.emacsAbort ();
|
||||
}
|
||||
}
|
||||
|
||||
EmacsService.syncRunnable (runnable);
|
||||
return rc.thing;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,300 +0,0 @@
|
|||
/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
|
||||
|
||||
Copyright (C) 2023 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Emacs.
|
||||
|
||||
GNU Emacs is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or (at
|
||||
your option) any later version.
|
||||
|
||||
GNU Emacs is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
package org.gnu.emacs;
|
||||
|
||||
import android.text.InputFilter;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.SpanWatcher;
|
||||
import android.text.Selection;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
|
||||
import android.text.Spannable;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
/* Android input methods insist on having access to buffer contents.
|
||||
Since Emacs is not designed like ``any other Android text editor'',
|
||||
that is not possible.
|
||||
|
||||
This file provides a fake editing buffer that is designed to weasel
|
||||
as much information as possible out of an input method, without
|
||||
actually providing buffer contents to Emacs.
|
||||
|
||||
The basic idea is to have the fake editing buffer be initially
|
||||
empty.
|
||||
|
||||
When the input method inserts composed text, it sets a flag.
|
||||
Updates to the buffer while the flag is set are sent to Emacs to be
|
||||
displayed as ``preedit text''.
|
||||
|
||||
Once some heuristics decide that composition has been completed,
|
||||
the composed text is sent to Emacs, and the text that was inserted
|
||||
in this editing buffer is erased. */
|
||||
|
||||
public class EmacsEditable extends SpannableStringBuilder
|
||||
implements SpanWatcher
|
||||
{
|
||||
private static final String TAG = "EmacsEditable";
|
||||
|
||||
/* Whether or not composition is currently in progress. */
|
||||
private boolean isComposing;
|
||||
|
||||
/* The associated input connection. */
|
||||
private EmacsInputConnection connection;
|
||||
|
||||
/* The associated IM manager. */
|
||||
private InputMethodManager imManager;
|
||||
|
||||
/* Any extracted text an input method may be monitoring. */
|
||||
private ExtractedText extractedText;
|
||||
|
||||
/* The corresponding text request. */
|
||||
private ExtractedTextRequest extractRequest;
|
||||
|
||||
/* The number of nested batch edits. */
|
||||
private int batchEditCount;
|
||||
|
||||
/* Whether or not invalidateInput should be called upon batch edits
|
||||
ending. */
|
||||
private boolean pendingInvalidate;
|
||||
|
||||
/* The ``composing span'' indicating the bounds of an ongoing
|
||||
character composition. */
|
||||
private Object composingSpan;
|
||||
|
||||
public
|
||||
EmacsEditable (EmacsInputConnection connection)
|
||||
{
|
||||
/* Initialize the editable with one initial space, so backspace
|
||||
always works. */
|
||||
super ();
|
||||
|
||||
Object tem;
|
||||
Context context;
|
||||
|
||||
this.connection = connection;
|
||||
|
||||
context = connection.view.getContext ();
|
||||
tem = context.getSystemService (Context.INPUT_METHOD_SERVICE);
|
||||
imManager = (InputMethodManager) tem;
|
||||
|
||||
/* To watch for changes to text properties on Android, you
|
||||
add... a text property. */
|
||||
setSpan (this, 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
|
||||
public void
|
||||
endBatchEdit ()
|
||||
{
|
||||
if (batchEditCount < 1)
|
||||
return;
|
||||
|
||||
if (--batchEditCount == 0 && pendingInvalidate)
|
||||
invalidateInput ();
|
||||
}
|
||||
|
||||
public void
|
||||
beginBatchEdit ()
|
||||
{
|
||||
++batchEditCount;
|
||||
}
|
||||
|
||||
public void
|
||||
setExtractedTextAndRequest (ExtractedText text,
|
||||
ExtractedTextRequest request,
|
||||
boolean monitor)
|
||||
{
|
||||
/* Extract the text. If monitor is set, also record it as the
|
||||
text that is currently being extracted. */
|
||||
|
||||
text.startOffset = 0;
|
||||
text.selectionStart = Selection.getSelectionStart (this);
|
||||
text.selectionEnd = Selection.getSelectionStart (this);
|
||||
text.text = this;
|
||||
|
||||
if (monitor)
|
||||
{
|
||||
extractedText = text;
|
||||
extractRequest = request;
|
||||
}
|
||||
}
|
||||
|
||||
public void
|
||||
compositionStart ()
|
||||
{
|
||||
isComposing = true;
|
||||
}
|
||||
|
||||
public void
|
||||
compositionEnd ()
|
||||
{
|
||||
isComposing = false;
|
||||
sendComposingText (null);
|
||||
}
|
||||
|
||||
private void
|
||||
sendComposingText (String string)
|
||||
{
|
||||
EmacsWindow window;
|
||||
long time, serial;
|
||||
|
||||
window = connection.view.window;
|
||||
|
||||
if (window.isDestroyed ())
|
||||
return;
|
||||
|
||||
time = System.currentTimeMillis ();
|
||||
|
||||
/* A composition event is simply a special key event with a
|
||||
keycode of -1. */
|
||||
|
||||
synchronized (window.eventStrings)
|
||||
{
|
||||
serial
|
||||
= EmacsNative.sendKeyPress (window.handle, time, 0, -1, -1);
|
||||
|
||||
/* Save the string so that android_lookup_string can find
|
||||
it. */
|
||||
if (string != null)
|
||||
window.saveUnicodeString ((int) serial, string);
|
||||
}
|
||||
}
|
||||
|
||||
private void
|
||||
invalidateInput ()
|
||||
{
|
||||
int start, end, composingSpanStart, composingSpanEnd;
|
||||
|
||||
if (batchEditCount > 0)
|
||||
{
|
||||
Log.d (TAG, "invalidateInput: deferring for batch edit");
|
||||
pendingInvalidate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
pendingInvalidate = false;
|
||||
|
||||
start = Selection.getSelectionStart (this);
|
||||
end = Selection.getSelectionEnd (this);
|
||||
|
||||
if (composingSpan != null)
|
||||
{
|
||||
composingSpanStart = getSpanStart (composingSpan);
|
||||
composingSpanEnd = getSpanEnd (composingSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
composingSpanStart = -1;
|
||||
composingSpanEnd = -1;
|
||||
}
|
||||
|
||||
Log.d (TAG, "invalidateInput: now " + start + ", " + end);
|
||||
|
||||
/* Tell the input method that the cursor changed. */
|
||||
imManager.updateSelection (connection.view, start, end,
|
||||
composingSpanStart,
|
||||
composingSpanEnd);
|
||||
|
||||
/* If there is any extracted text, tell the IME that it has
|
||||
changed. */
|
||||
if (extractedText != null)
|
||||
imManager.updateExtractedText (connection.view,
|
||||
extractRequest.token,
|
||||
extractedText);
|
||||
}
|
||||
|
||||
public SpannableStringBuilder
|
||||
replace (int start, int end, CharSequence tb, int tbstart,
|
||||
int tbend)
|
||||
{
|
||||
super.replace (start, end, tb, tbstart, tbend);
|
||||
|
||||
/* If a change happens during composition, perform the change and
|
||||
then send the text being composed. */
|
||||
|
||||
if (isComposing)
|
||||
sendComposingText (toString ());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private boolean
|
||||
isSelectionSpan (Object span)
|
||||
{
|
||||
return ((Selection.SELECTION_START == span
|
||||
|| Selection.SELECTION_END == span)
|
||||
&& (getSpanFlags (span)
|
||||
& Spanned.SPAN_INTERMEDIATE) == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void
|
||||
onSpanAdded (Spannable text, Object what, int start, int end)
|
||||
{
|
||||
Log.d (TAG, "onSpanAdded: " + text + " " + what + " "
|
||||
+ start + " " + end);
|
||||
|
||||
/* Try to find the composing span. This isn't a public API. */
|
||||
|
||||
if (what.getClass ().getName ().contains ("ComposingText"))
|
||||
composingSpan = what;
|
||||
|
||||
if (isSelectionSpan (what))
|
||||
invalidateInput ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void
|
||||
onSpanChanged (Spannable text, Object what, int ostart,
|
||||
int oend, int nstart, int nend)
|
||||
{
|
||||
Log.d (TAG, "onSpanChanged: " + text + " " + what + " "
|
||||
+ nstart + " " + nend);
|
||||
|
||||
if (isSelectionSpan (what))
|
||||
invalidateInput ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void
|
||||
onSpanRemoved (Spannable text, Object what,
|
||||
int start, int end)
|
||||
{
|
||||
Log.d (TAG, "onSpanRemoved: " + text + " " + what + " "
|
||||
+ start + " " + end);
|
||||
|
||||
if (isSelectionSpan (what))
|
||||
invalidateInput ();
|
||||
}
|
||||
|
||||
public boolean
|
||||
isInBatchEdit ()
|
||||
{
|
||||
return batchEditCount > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.SurroundingText;
|
||||
import android.view.inputmethod.TextSnapshot;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import android.text.Editable;
|
||||
|
|
@ -38,35 +39,115 @@
|
|||
public class EmacsInputConnection extends BaseInputConnection
|
||||
{
|
||||
private static final String TAG = "EmacsInputConnection";
|
||||
public EmacsView view;
|
||||
private EmacsEditable editable;
|
||||
|
||||
/* The length of the last string to be committed. */
|
||||
private int lastCommitLength;
|
||||
|
||||
int currentLargeOffset;
|
||||
private EmacsView view;
|
||||
private short windowHandle;
|
||||
|
||||
public
|
||||
EmacsInputConnection (EmacsView view)
|
||||
{
|
||||
super (view, false);
|
||||
super (view, true);
|
||||
|
||||
this.view = view;
|
||||
this.editable = new EmacsEditable (this);
|
||||
this.windowHandle = view.window.handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editable
|
||||
getEditable ()
|
||||
public boolean
|
||||
beginBatchEdit ()
|
||||
{
|
||||
return editable;
|
||||
Log.d (TAG, "beginBatchEdit");
|
||||
EmacsNative.beginBatchEdit (windowHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
endBatchEdit ()
|
||||
{
|
||||
Log.d (TAG, "endBatchEdit");
|
||||
EmacsNative.endBatchEdit (windowHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
commitCompletion (CompletionInfo info)
|
||||
{
|
||||
Log.d (TAG, "commitCompletion: " + info);
|
||||
EmacsNative.commitCompletion (windowHandle,
|
||||
info.getText ().toString (),
|
||||
info.getPosition ());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
commitText (CharSequence text, int newCursorPosition)
|
||||
{
|
||||
Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
|
||||
EmacsNative.commitText (windowHandle, text.toString (),
|
||||
newCursorPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
deleteSurroundingText (int leftLength, int rightLength)
|
||||
{
|
||||
Log.d (TAG, ("deleteSurroundingText: "
|
||||
+ leftLength + " " + rightLength));
|
||||
EmacsNative.deleteSurroundingText (windowHandle, leftLength,
|
||||
rightLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
finishComposingText ()
|
||||
{
|
||||
Log.d (TAG, "finishComposingText");
|
||||
|
||||
EmacsNative.finishComposingText (windowHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String
|
||||
getSelectedText (int flags)
|
||||
{
|
||||
Log.d (TAG, "getSelectedText: " + flags);
|
||||
|
||||
return EmacsNative.getSelectedText (windowHandle, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String
|
||||
getTextAfterCursor (int length, int flags)
|
||||
{
|
||||
Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
|
||||
|
||||
return EmacsNative.getTextAfterCursor (windowHandle, length,
|
||||
flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String
|
||||
getTextBeforeCursor (int length, int flags)
|
||||
{
|
||||
Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
|
||||
|
||||
return EmacsNative.getTextBeforeCursor (windowHandle, length,
|
||||
flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
setComposingText (CharSequence text, int newCursorPosition)
|
||||
{
|
||||
editable.compositionStart ();
|
||||
super.setComposingText (text, newCursorPosition);
|
||||
Log.d (TAG, "setComposingText: " + newCursorPosition);
|
||||
|
||||
EmacsNative.setComposingText (windowHandle, text.toString (),
|
||||
newCursorPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -74,102 +155,40 @@ public class EmacsInputConnection extends BaseInputConnection
|
|||
public boolean
|
||||
setComposingRegion (int start, int end)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (lastCommitLength != 0)
|
||||
{
|
||||
Log.d (TAG, "Restarting composition for: " + lastCommitLength);
|
||||
|
||||
for (i = 0; i < lastCommitLength; ++i)
|
||||
sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN,
|
||||
KeyEvent.KEYCODE_DEL));
|
||||
|
||||
lastCommitLength = 0;
|
||||
}
|
||||
|
||||
editable.compositionStart ();
|
||||
super.setComposingRegion (start, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
finishComposingText ()
|
||||
{
|
||||
editable.compositionEnd ();
|
||||
return super.finishComposingText ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
beginBatchEdit ()
|
||||
{
|
||||
editable.beginBatchEdit ();
|
||||
return super.beginBatchEdit ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
endBatchEdit ()
|
||||
{
|
||||
editable.endBatchEdit ();
|
||||
return super.endBatchEdit ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
commitText (CharSequence text, int newCursorPosition)
|
||||
{
|
||||
editable.compositionEnd ();
|
||||
super.commitText (text, newCursorPosition);
|
||||
|
||||
/* An observation is that input methods rarely recompose trailing
|
||||
spaces. Avoid re-setting the commit length in that case. */
|
||||
|
||||
if (text.toString ().equals (" "))
|
||||
lastCommitLength += 1;
|
||||
else
|
||||
/* At this point, the editable is now empty.
|
||||
|
||||
The input method may try to cancel the edit upon a subsequent
|
||||
backspace by calling setComposingRegion with a region that is
|
||||
the length of TEXT.
|
||||
|
||||
Record this length in order to be able to send backspace
|
||||
events to ``delete'' the text in that case. */
|
||||
lastCommitLength = text.length ();
|
||||
|
||||
Log.d (TAG, "commitText: \"" + text + "\"");
|
||||
Log.d (TAG, "setComposingRegion: " + start + " " + end);
|
||||
|
||||
EmacsNative.setComposingRegion (windowHandle, start, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return a large offset, cycling through 100000, 30000, 0.
|
||||
The offset is typically used to force the input method to update
|
||||
its notion of ``surrounding text'', bypassing any caching that
|
||||
it might have in progress.
|
||||
|
||||
There must be another way to do this, but I can't find it. */
|
||||
|
||||
public int
|
||||
largeSelectionOffset ()
|
||||
@Override
|
||||
public boolean
|
||||
performEditorAction (int editorAction)
|
||||
{
|
||||
switch (currentLargeOffset)
|
||||
{
|
||||
case 0:
|
||||
currentLargeOffset = 100000;
|
||||
return 100000;
|
||||
Log.d (TAG, "performEditorAction: " + editorAction);
|
||||
|
||||
case 100000:
|
||||
currentLargeOffset = 30000;
|
||||
return 30000;
|
||||
EmacsNative.performEditorAction (windowHandle, editorAction);
|
||||
return true;
|
||||
}
|
||||
|
||||
case 30000:
|
||||
currentLargeOffset = 0;
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public ExtractedText
|
||||
getExtractedText (ExtractedTextRequest request, int flags)
|
||||
{
|
||||
Log.d (TAG, "getExtractedText: " + request + " " + flags);
|
||||
|
||||
currentLargeOffset = 0;
|
||||
return -1;
|
||||
return EmacsNative.getExtractedText (windowHandle, request,
|
||||
flags);
|
||||
}
|
||||
|
||||
|
||||
/* Override functions which are not implemented. */
|
||||
|
||||
@Override
|
||||
public TextSnapshot
|
||||
takeSnapshot ()
|
||||
{
|
||||
Log.d (TAG, "takeSnapshot");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
import java.lang.System;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
|
||||
public class EmacsNative
|
||||
{
|
||||
|
|
@ -161,6 +163,50 @@ public static native long sendExpose (short window, int x, int y,
|
|||
descriptor, or NULL if there is none. */
|
||||
public static native byte[] getProcName (int fd);
|
||||
|
||||
/* Notice that the Emacs thread will now start waiting for the main
|
||||
thread's looper to respond. */
|
||||
public static native void beginSynchronous ();
|
||||
|
||||
/* Notice that the Emacs thread will has finished waiting for the
|
||||
main thread's looper to respond. */
|
||||
public static native void endSynchronous ();
|
||||
|
||||
|
||||
|
||||
/* Input connection functions. These mostly correspond to their
|
||||
counterparts in Android's InputConnection. */
|
||||
|
||||
public static native void beginBatchEdit (short window);
|
||||
public static native void endBatchEdit (short window);
|
||||
public static native void commitCompletion (short window, String text,
|
||||
int position);
|
||||
public static native void commitText (short window, String text,
|
||||
int position);
|
||||
public static native void deleteSurroundingText (short window,
|
||||
int leftLength,
|
||||
int rightLength);
|
||||
public static native void finishComposingText (short window);
|
||||
public static native String getSelectedText (short window, int flags);
|
||||
public static native String getTextAfterCursor (short window, int length,
|
||||
int flags);
|
||||
public static native String getTextBeforeCursor (short window, int length,
|
||||
int flags);
|
||||
public static native void setComposingText (short window, String text,
|
||||
int newCursorPosition);
|
||||
public static native void setComposingRegion (short window, int start,
|
||||
int end);
|
||||
public static native void setSelection (short window, int start, int end);
|
||||
public static native void performEditorAction (short window,
|
||||
int editorAction);
|
||||
public static native ExtractedText getExtractedText (short window,
|
||||
ExtractedTextRequest req,
|
||||
int flags);
|
||||
|
||||
|
||||
/* Return the current value of the selection, or -1 upon
|
||||
failure. */
|
||||
public static native int getSelection (short window);
|
||||
|
||||
static
|
||||
{
|
||||
/* Older versions of Android cannot link correctly with shared
|
||||
|
|
|
|||
|
|
@ -80,6 +80,11 @@ public class EmacsService extends Service
|
|||
private EmacsThread thread;
|
||||
private Handler handler;
|
||||
|
||||
/* Keep this in synch with androidgui.h. */
|
||||
public static final int IC_MODE_NULL = 0;
|
||||
public static final int IC_MODE_ACTION = 1;
|
||||
public static final int IC_MODE_TEXT = 2;
|
||||
|
||||
/* Display metrics used by font backends. */
|
||||
public DisplayMetrics metrics;
|
||||
|
||||
|
|
@ -258,20 +263,7 @@ invocation of app_process (through android-emacs) can
|
|||
}
|
||||
};
|
||||
|
||||
synchronized (runnable)
|
||||
{
|
||||
runOnUiThread (runnable);
|
||||
|
||||
try
|
||||
{
|
||||
runnable.wait ();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
EmacsNative.emacsAbort ();
|
||||
}
|
||||
}
|
||||
|
||||
syncRunnable (runnable);
|
||||
return view.thing;
|
||||
}
|
||||
|
||||
|
|
@ -292,19 +284,7 @@ invocation of app_process (through android-emacs) can
|
|||
}
|
||||
};
|
||||
|
||||
synchronized (runnable)
|
||||
{
|
||||
runOnUiThread (runnable);
|
||||
|
||||
try
|
||||
{
|
||||
runnable.wait ();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
EmacsNative.emacsAbort ();
|
||||
}
|
||||
}
|
||||
syncRunnable (runnable);
|
||||
}
|
||||
|
||||
public void
|
||||
|
|
@ -502,19 +482,7 @@ invocation of app_process (through android-emacs) can
|
|||
}
|
||||
};
|
||||
|
||||
synchronized (runnable)
|
||||
{
|
||||
runOnUiThread (runnable);
|
||||
|
||||
try
|
||||
{
|
||||
runnable.wait ();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
EmacsNative.emacsAbort ();
|
||||
}
|
||||
}
|
||||
syncRunnable (runnable);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -594,20 +562,7 @@ invocation of app_process (through android-emacs) can
|
|||
}
|
||||
};
|
||||
|
||||
synchronized (runnable)
|
||||
{
|
||||
runOnUiThread (runnable);
|
||||
|
||||
try
|
||||
{
|
||||
runnable.wait ();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
EmacsNative.emacsAbort ();
|
||||
}
|
||||
}
|
||||
|
||||
syncRunnable (runnable);
|
||||
return manager.thing;
|
||||
}
|
||||
|
||||
|
|
@ -622,4 +577,58 @@ invocation of app_process (through android-emacs) can
|
|||
startActivity (intent);
|
||||
System.exit (0);
|
||||
}
|
||||
|
||||
/* Wait synchronously for the specified RUNNABLE to complete in the
|
||||
UI thread. Must be called from the Emacs thread. */
|
||||
|
||||
public static void
|
||||
syncRunnable (Runnable runnable)
|
||||
{
|
||||
EmacsNative.beginSynchronous ();
|
||||
|
||||
synchronized (runnable)
|
||||
{
|
||||
SERVICE.runOnUiThread (runnable);
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
runnable.wait ();
|
||||
break;
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EmacsNative.endSynchronous ();
|
||||
}
|
||||
|
||||
public void
|
||||
updateIC (EmacsWindow window, int newSelectionStart,
|
||||
int newSelectionEnd, int composingRegionStart,
|
||||
int composingRegionEnd)
|
||||
{
|
||||
Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart
|
||||
+ " " + newSelectionEnd + " "
|
||||
+ composingRegionStart + " "
|
||||
+ composingRegionEnd));
|
||||
window.view.imManager.updateSelection (window.view,
|
||||
newSelectionStart,
|
||||
newSelectionEnd,
|
||||
composingRegionStart,
|
||||
composingRegionEnd);
|
||||
}
|
||||
|
||||
public void
|
||||
resetIC (EmacsWindow window, int icMode)
|
||||
{
|
||||
Log.d (TAG, "resetIC: " + window);
|
||||
|
||||
window.view.setICMode (icMode);
|
||||
window.view.imManager.restartInput (window.view);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -103,6 +103,13 @@ public class EmacsView extends ViewGroup
|
|||
displayed whenever possible. */
|
||||
public boolean isCurrentlyTextEditor;
|
||||
|
||||
/* The associated input connection. */
|
||||
private EmacsInputConnection inputConnection;
|
||||
|
||||
/* The current IC mode. See `android_reset_ic' for more
|
||||
details. */
|
||||
private int icMode;
|
||||
|
||||
public
|
||||
EmacsView (EmacsWindow window)
|
||||
{
|
||||
|
|
@ -554,14 +561,46 @@ else if (child.getVisibility () != GONE)
|
|||
public InputConnection
|
||||
onCreateInputConnection (EditorInfo info)
|
||||
{
|
||||
int selection, mode;
|
||||
|
||||
/* Figure out what kind of IME behavior Emacs wants. */
|
||||
mode = getICMode ();
|
||||
|
||||
/* Make sure the input method never displays a full screen input
|
||||
box that obscures Emacs. */
|
||||
info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
|
||||
|
||||
/* Set a reasonable inputType. */
|
||||
info.inputType = InputType.TYPE_NULL;
|
||||
info.inputType = InputType.TYPE_CLASS_TEXT;
|
||||
|
||||
return null;
|
||||
/* Obtain the current position of point and set it as the
|
||||
selection. */
|
||||
selection = EmacsNative.getSelection (window.handle);
|
||||
|
||||
Log.d (TAG, "onCreateInputConnection: current selection is: " + selection);
|
||||
|
||||
/* If this fails or ANDROID_IC_MODE_NULL was requested, then don't
|
||||
initialize the input connection. */
|
||||
if (selection == -1 || mode == EmacsService.IC_MODE_NULL)
|
||||
{
|
||||
info.inputType = InputType.TYPE_NULL;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mode == EmacsService.IC_MODE_ACTION)
|
||||
info.imeOptions |= EditorInfo.IME_ACTION_DONE;
|
||||
|
||||
/* Set the initial selection fields. */
|
||||
info.initialSelStart = selection;
|
||||
info.initialSelEnd = selection;
|
||||
|
||||
/* Create the input connection if necessary. */
|
||||
|
||||
if (inputConnection == null)
|
||||
inputConnection = new EmacsInputConnection (this);
|
||||
|
||||
/* Return the input connection. */
|
||||
return inputConnection;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -572,4 +611,16 @@ else if (child.getVisibility () != GONE)
|
|||
keyboard. */
|
||||
return isCurrentlyTextEditor;
|
||||
}
|
||||
|
||||
public synchronized void
|
||||
setICMode (int icMode)
|
||||
{
|
||||
this.icMode = icMode;
|
||||
}
|
||||
|
||||
public synchronized int
|
||||
getICMode ()
|
||||
{
|
||||
return icMode;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1521,6 +1521,9 @@ if `inhibit-field-text-motion' is non-nil."
|
|||
(define-key special-event-map [sigusr1] 'ignore)
|
||||
(define-key special-event-map [sigusr2] 'ignore)
|
||||
|
||||
;; Text conversion
|
||||
(define-key global-map [text-conversion] 'ignore)
|
||||
|
||||
;; Don't look for autoload cookies in this file.
|
||||
;; Local Variables:
|
||||
;; no-update-autoloads: t
|
||||
|
|
|
|||
14
src/alloc.c
14
src/alloc.c
|
|
@ -6939,6 +6939,11 @@ static void
|
|||
mark_frame (struct Lisp_Vector *ptr)
|
||||
{
|
||||
struct frame *f = (struct frame *) ptr;
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
struct text_conversion_action *tem;
|
||||
#endif
|
||||
|
||||
|
||||
mark_vectorlike (&ptr->header);
|
||||
mark_face_cache (f->face_cache);
|
||||
#ifdef HAVE_WINDOW_SYSTEM
|
||||
|
|
@ -6950,6 +6955,15 @@ mark_frame (struct Lisp_Vector *ptr)
|
|||
mark_vectorlike (&font->header);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
mark_object (f->conversion.compose_region_start);
|
||||
mark_object (f->conversion.compose_region_end);
|
||||
mark_object (f->conversion.compose_region_overlay);
|
||||
|
||||
for (tem = f->conversion.actions; tem; tem = tem->next)
|
||||
mark_object (tem->data);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
295
src/android.c
295
src/android.c
|
|
@ -98,6 +98,8 @@ struct android_emacs_service
|
|||
jmethodID sync;
|
||||
jmethodID browse_url;
|
||||
jmethodID restart_emacs;
|
||||
jmethodID update_ic;
|
||||
jmethodID reset_ic;
|
||||
};
|
||||
|
||||
struct android_emacs_pixmap
|
||||
|
|
@ -207,7 +209,7 @@ static struct android_emacs_window window_class;
|
|||
|
||||
/* The last event serial used. This is a 32 bit value, but it is
|
||||
stored in unsigned long to be consistent with X. */
|
||||
static unsigned int event_serial;
|
||||
unsigned int event_serial;
|
||||
|
||||
/* Unused pointer used to control compiler optimizations. */
|
||||
void *unused_pointer;
|
||||
|
|
@ -408,6 +410,10 @@ android_handle_sigusr1 (int sig, siginfo_t *siginfo, void *arg)
|
|||
|
||||
#endif
|
||||
|
||||
/* Semaphore used to indicate completion of a query.
|
||||
This should ideally be defined further down. */
|
||||
static sem_t android_query_sem;
|
||||
|
||||
/* Set up the global event queue by initializing the mutex and two
|
||||
condition variables, and the linked list of events. This must be
|
||||
called before starting the Emacs thread. Also, initialize the
|
||||
|
|
@ -438,6 +444,7 @@ android_init_events (void)
|
|||
|
||||
sem_init (&android_pselect_sem, 0, 0);
|
||||
sem_init (&android_pselect_start_sem, 0, 0);
|
||||
sem_init (&android_query_sem, 0, 0);
|
||||
|
||||
event_queue.events.next = &event_queue.events;
|
||||
event_queue.events.last = &event_queue.events;
|
||||
|
|
@ -538,7 +545,7 @@ android_next_event (union android_event *event_return)
|
|||
pthread_mutex_unlock (&event_queue.mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
android_write_event (union android_event *event)
|
||||
{
|
||||
struct android_event_container *container;
|
||||
|
|
@ -576,6 +583,10 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds,
|
|||
static char byte;
|
||||
#endif
|
||||
|
||||
/* Check for and run anything the UI thread wants to run on the main
|
||||
thread. */
|
||||
android_check_query ();
|
||||
|
||||
pthread_mutex_lock (&event_queue.mutex);
|
||||
|
||||
if (event_queue.num_events)
|
||||
|
|
@ -634,6 +645,10 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds,
|
|||
if (nfds_return < 0)
|
||||
errno = EINTR;
|
||||
|
||||
/* Now check for and run anything the UI thread wants to run in the
|
||||
main thread. */
|
||||
android_check_query ();
|
||||
|
||||
return nfds_return;
|
||||
}
|
||||
|
||||
|
|
@ -1431,7 +1446,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
|
|||
|
||||
/* This may be called from multiple threads. setEmacsParams should
|
||||
only ever be called once. */
|
||||
if (__atomic_fetch_add (&emacs_initialized, -1, __ATOMIC_RELAXED))
|
||||
if (__atomic_fetch_add (&emacs_initialized, -1, __ATOMIC_SEQ_CST))
|
||||
{
|
||||
ANDROID_THROW (env, "java/lang/IllegalArgumentException",
|
||||
"Emacs was already initialized!");
|
||||
|
|
@ -1705,6 +1720,10 @@ android_init_emacs_service (void)
|
|||
FIND_METHOD (browse_url, "browseUrl", "(Ljava/lang/String;)"
|
||||
"Ljava/lang/String;");
|
||||
FIND_METHOD (restart_emacs, "restartEmacs", "()V");
|
||||
FIND_METHOD (update_ic, "updateIC",
|
||||
"(Lorg/gnu/emacs/EmacsWindow;IIII)V");
|
||||
FIND_METHOD (reset_ic, "resetIC",
|
||||
"(Lorg/gnu/emacs/EmacsWindow;I)V");
|
||||
#undef FIND_METHOD
|
||||
}
|
||||
|
||||
|
|
@ -1834,7 +1853,7 @@ android_init_emacs_window (void)
|
|||
#undef FIND_METHOD
|
||||
}
|
||||
|
||||
extern JNIEXPORT void JNICALL
|
||||
JNIEXPORT void JNICALL
|
||||
NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv,
|
||||
jobject dump_file_object, jint api_level)
|
||||
{
|
||||
|
|
@ -1928,19 +1947,19 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv,
|
|||
emacs_abort ();
|
||||
}
|
||||
|
||||
extern JNIEXPORT void JNICALL
|
||||
JNIEXPORT void JNICALL
|
||||
NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object)
|
||||
{
|
||||
emacs_abort ();
|
||||
}
|
||||
|
||||
extern JNIEXPORT void JNICALL
|
||||
JNIEXPORT void JNICALL
|
||||
NATIVE_NAME (quit) (JNIEnv *env, jobject object)
|
||||
{
|
||||
Vquit_flag = Qt;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
|
||||
jshort window, jlong time,
|
||||
jint x, jint y, jint width,
|
||||
|
|
@ -1961,7 +1980,7 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
|
||||
jshort window, jlong time,
|
||||
jint state, jint keycode,
|
||||
|
|
@ -1981,7 +2000,7 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
|
||||
jshort window, jlong time,
|
||||
jint state, jint keycode,
|
||||
|
|
@ -2001,7 +2020,7 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
|
||||
jshort window, jlong time)
|
||||
{
|
||||
|
|
@ -2016,7 +2035,7 @@ NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
|
||||
jshort window, jlong time)
|
||||
{
|
||||
|
|
@ -2031,7 +2050,7 @@ NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
|
|||
return ++event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
|
||||
jshort window, jint action)
|
||||
{
|
||||
|
|
@ -2046,7 +2065,7 @@ NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time)
|
||||
|
|
@ -2064,7 +2083,7 @@ NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time)
|
||||
|
|
@ -2082,7 +2101,7 @@ NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time)
|
||||
|
|
@ -2100,7 +2119,7 @@ NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time, jint state,
|
||||
|
|
@ -2121,7 +2140,7 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time, jint state,
|
||||
|
|
@ -2142,7 +2161,7 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time, jint pointer_id)
|
||||
|
|
@ -2161,7 +2180,7 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time, jint pointer_id)
|
||||
|
|
@ -2180,7 +2199,7 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time, jint pointer_id)
|
||||
|
|
@ -2199,7 +2218,7 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jlong time, jint state,
|
||||
|
|
@ -2221,7 +2240,7 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
|
||||
jshort window)
|
||||
{
|
||||
|
|
@ -2235,7 +2254,7 @@ NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object,
|
||||
jshort window)
|
||||
{
|
||||
|
|
@ -2249,7 +2268,7 @@ NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object,
|
||||
jshort window, jint menu_event_id)
|
||||
{
|
||||
|
|
@ -2264,7 +2283,7 @@ NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
extern JNIEXPORT jlong JNICALL
|
||||
JNIEXPORT jlong JNICALL
|
||||
NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object,
|
||||
jshort window, jint x, jint y,
|
||||
jint width, jint height)
|
||||
|
|
@ -2283,6 +2302,23 @@ NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object,
|
|||
return event_serial;
|
||||
}
|
||||
|
||||
/* Forward declarations of deadlock prevention functions. */
|
||||
|
||||
static void android_begin_query (void);
|
||||
static void android_end_query (void);
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
NATIVE_NAME (beginSynchronous) (JNIEnv *env, jobject object)
|
||||
{
|
||||
android_begin_query ();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
NATIVE_NAME (endSynchronous) (JNIEnv *env, jobject object)
|
||||
{
|
||||
android_end_query ();
|
||||
}
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#else
|
||||
|
|
@ -5155,6 +5191,215 @@ android_get_current_api_level (void)
|
|||
|
||||
|
||||
|
||||
/* Whether or not a query is currently being made. */
|
||||
static bool android_servicing_query;
|
||||
|
||||
/* Function that is waiting to be run in the Emacs thread. */
|
||||
static void (*android_query_function) (void *);
|
||||
|
||||
/* Context for that function. */
|
||||
static void *android_query_context;
|
||||
|
||||
/* Deadlock protection. The UI thread and the Emacs thread must
|
||||
sometimes make synchronous queries to each other, which are
|
||||
normally answered inside each thread's respective event loop.
|
||||
Deadlocks can happen when both threads simultaneously make such
|
||||
synchronous queries and block waiting for each others responses.
|
||||
|
||||
The Emacs thread can be interrupted to service any queries made by
|
||||
the UI thread, but is not possible the other way around.
|
||||
|
||||
To avoid such deadlocks, an atomic counter is provided. This
|
||||
counter is incremented every time a query starts, and is set to
|
||||
zerp every time one ends. If the UI thread tries to make a query
|
||||
and sees that the counter is non-zero, it simply returns so that
|
||||
its event loop can proceed to perform and respond to the query. If
|
||||
the Emacs thread sees the same thing, then it stops to service all
|
||||
queries being made by the input method, then proceeds to make its
|
||||
query. */
|
||||
|
||||
/* Run any function that the UI thread has asked to run, and then
|
||||
signal its completion. */
|
||||
|
||||
void
|
||||
android_check_query (void)
|
||||
{
|
||||
void (*proc) (void *);
|
||||
void *closure;
|
||||
|
||||
if (!__atomic_load_n (&android_servicing_query, __ATOMIC_SEQ_CST))
|
||||
return;
|
||||
|
||||
/* First, load the procedure and closure. */
|
||||
__atomic_load (&android_query_context, &closure, __ATOMIC_SEQ_CST);
|
||||
__atomic_load (&android_query_function, &proc, __ATOMIC_SEQ_CST);
|
||||
|
||||
if (!proc)
|
||||
return;
|
||||
|
||||
proc (closure);
|
||||
|
||||
/* Finish the query. */
|
||||
__atomic_store_n (&android_query_context, NULL, __ATOMIC_SEQ_CST);
|
||||
__atomic_store_n (&android_query_function, NULL, __ATOMIC_SEQ_CST);
|
||||
__atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST);
|
||||
|
||||
/* Signal completion. */
|
||||
sem_post (&android_query_sem);
|
||||
}
|
||||
|
||||
/* Notice that the Emacs thread will start blocking waiting for a
|
||||
response from the UI thread. Process any pending queries from the
|
||||
UI thread.
|
||||
|
||||
This function may be called from Java. */
|
||||
|
||||
static void
|
||||
android_begin_query (void)
|
||||
{
|
||||
if (__atomic_test_and_set (&android_servicing_query,
|
||||
__ATOMIC_SEQ_CST))
|
||||
{
|
||||
/* Answer the query that is currently being made. */
|
||||
assert (android_query_function != NULL);
|
||||
android_check_query ();
|
||||
|
||||
/* Wait for that query to complete. */
|
||||
while (__atomic_load_n (&android_servicing_query,
|
||||
__ATOMIC_SEQ_CST))
|
||||
;;
|
||||
}
|
||||
}
|
||||
|
||||
/* Notice that a query has stopped. This function may be called from
|
||||
Java. */
|
||||
|
||||
static void
|
||||
android_end_query (void)
|
||||
{
|
||||
__atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
/* Synchronously ask the Emacs thread to run the specified PROC with
|
||||
the given CLOSURE. Return if this fails, or once PROC is run.
|
||||
|
||||
PROC may be run from inside maybe_quit.
|
||||
|
||||
It is not okay to run Lisp code which signals or performs non
|
||||
trivial tasks inside PROC.
|
||||
|
||||
Return 1 if the Emacs thread is currently waiting for the UI thread
|
||||
to respond and PROC could not be run, or 0 otherwise. */
|
||||
|
||||
int
|
||||
android_run_in_emacs_thread (void (*proc) (void *), void *closure)
|
||||
{
|
||||
union android_event event;
|
||||
|
||||
event.xaction.type = ANDROID_WINDOW_ACTION;
|
||||
event.xaction.serial = ++event_serial;
|
||||
event.xaction.window = 0;
|
||||
event.xaction.action = 0;
|
||||
|
||||
/* Set android_query_function and android_query_context. */
|
||||
__atomic_store_n (&android_query_context, closure, __ATOMIC_SEQ_CST);
|
||||
__atomic_store_n (&android_query_function, proc, __ATOMIC_SEQ_CST);
|
||||
|
||||
/* Don't allow deadlocks to happen; make sure the Emacs thread is
|
||||
not waiting for something to be done. */
|
||||
|
||||
if (__atomic_test_and_set (&android_servicing_query,
|
||||
__ATOMIC_SEQ_CST))
|
||||
{
|
||||
__atomic_store_n (&android_query_context, NULL,
|
||||
__ATOMIC_SEQ_CST);
|
||||
__atomic_store_n (&android_query_function, NULL,
|
||||
__ATOMIC_SEQ_CST);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Send a dummy event. `android_check_query' will be called inside
|
||||
wait_reading_process_output after the event arrives.
|
||||
|
||||
Otherwise, android_select will call android_check_thread the next
|
||||
time it is entered. */
|
||||
android_write_event (&event);
|
||||
|
||||
/* Start waiting for the function to be executed. */
|
||||
while (sem_wait (&android_query_sem) < 0)
|
||||
;;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Input method related functions. */
|
||||
|
||||
/* Change WINDOW's active selection to the characters between
|
||||
SELECTION_START and SELECTION_END.
|
||||
|
||||
Also, update the composing region to COMPOSING_REGION_START and
|
||||
COMPOSING_REGION_END.
|
||||
|
||||
If any value cannot fit in jint, then the behavior of the input
|
||||
method is undefined. */
|
||||
|
||||
void
|
||||
android_update_ic (android_window window, ptrdiff_t selection_start,
|
||||
ptrdiff_t selection_end, ptrdiff_t composing_region_start,
|
||||
ptrdiff_t composing_region_end)
|
||||
{
|
||||
jobject object;
|
||||
|
||||
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
|
||||
|
||||
(*android_java_env)->CallVoidMethod (android_java_env,
|
||||
emacs_service,
|
||||
service_class.update_ic,
|
||||
object,
|
||||
(jint) selection_start,
|
||||
(jint) selection_end,
|
||||
(jint) composing_region_start,
|
||||
(jint) composing_region_end);
|
||||
android_exception_check ();
|
||||
}
|
||||
|
||||
/* Reinitialize any ongoing input method connection on WINDOW.
|
||||
|
||||
Any input method that is connected to WINDOW will invalidate its
|
||||
cache of the buffer contents.
|
||||
|
||||
MODE controls certain aspects of the input method's behavior:
|
||||
|
||||
- If MODE is ANDROID_IC_MODE_NULL, the input method will be
|
||||
deactivated, and an ASCII only keyboard will be displayed
|
||||
instead.
|
||||
|
||||
- If MODE is ANDROID_IC_MODE_ACTION, the input method will
|
||||
edit text normally, but send ``return'' as a key event.
|
||||
This is useful inside the mini buffer.
|
||||
|
||||
- If MODE is ANDROID_IC_MODE_TEXT, the input method is free
|
||||
to behave however it wants. */
|
||||
|
||||
void
|
||||
android_reset_ic (android_window window, enum android_ic_mode mode)
|
||||
{
|
||||
jobject object;
|
||||
|
||||
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
|
||||
|
||||
(*android_java_env)->CallVoidMethod (android_java_env,
|
||||
emacs_service,
|
||||
service_class.reset_ic,
|
||||
object, (jint) mode);
|
||||
android_exception_check ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#else /* ANDROID_STUBIFY */
|
||||
|
||||
/* X emulation functions for Android. */
|
||||
|
|
|
|||
|
|
@ -108,6 +108,16 @@ extern void android_closedir (struct android_dir *);
|
|||
|
||||
extern Lisp_Object android_browse_url (Lisp_Object);
|
||||
|
||||
|
||||
|
||||
/* Event loop functions. */
|
||||
|
||||
extern void android_check_query (void);
|
||||
extern int android_run_in_emacs_thread (void (*) (void *), void *);
|
||||
extern void android_write_event (union android_event *);
|
||||
|
||||
extern unsigned int event_serial;
|
||||
|
||||
#endif
|
||||
|
||||
/* JNI functions should not be built when Emacs is stubbed out for the
|
||||
|
|
|
|||
|
|
@ -235,6 +235,7 @@ enum android_event_type
|
|||
ANDROID_DEICONIFIED,
|
||||
ANDROID_CONTEXT_MENU,
|
||||
ANDROID_EXPOSE,
|
||||
ANDROID_INPUT_METHOD,
|
||||
};
|
||||
|
||||
struct android_any_event
|
||||
|
|
@ -419,6 +420,52 @@ struct android_menu_event
|
|||
int menu_event_id;
|
||||
};
|
||||
|
||||
enum android_ime_operation
|
||||
{
|
||||
ANDROID_IME_COMMIT_TEXT,
|
||||
ANDROID_IME_DELETE_SURROUNDING_TEXT,
|
||||
ANDROID_IME_FINISH_COMPOSING_TEXT,
|
||||
ANDROID_IME_SET_COMPOSING_TEXT,
|
||||
ANDROID_IME_SET_COMPOSING_REGION,
|
||||
ANDROID_IME_SET_POINT,
|
||||
ANDROID_IME_START_BATCH_EDIT,
|
||||
ANDROID_IME_END_BATCH_EDIT,
|
||||
};
|
||||
|
||||
struct android_ime_event
|
||||
{
|
||||
/* Type of the event. */
|
||||
enum android_event_type type;
|
||||
|
||||
/* The event serial. */
|
||||
unsigned long serial;
|
||||
|
||||
/* The associated window. */
|
||||
android_window window;
|
||||
|
||||
/* What operation is being performed. */
|
||||
enum android_ime_operation operation;
|
||||
|
||||
/* The details of the operation. START and END provide buffer
|
||||
indices, and may actually mean ``left'' and ``right''. */
|
||||
ptrdiff_t start, end, position;
|
||||
|
||||
/* The number of characters in TEXT. */
|
||||
size_t length;
|
||||
|
||||
/* TEXT is either NULL, or a pointer to LENGTH bytes of malloced
|
||||
UTF-16 encoded text that must be decoded by Emacs.
|
||||
|
||||
POSITION is where point should end up after the text is
|
||||
committed, relative to TEXT. If POSITION is less than 0, it is
|
||||
relative to TEXT's start; otherwise, it is relative to its
|
||||
end. */
|
||||
unsigned short *text;
|
||||
|
||||
/* Value to set the counter to after the operation completes. */
|
||||
unsigned long counter;
|
||||
};
|
||||
|
||||
union android_event
|
||||
{
|
||||
enum android_event_type type;
|
||||
|
|
@ -447,6 +494,9 @@ union android_event
|
|||
|
||||
/* This is only used to transmit selected menu items. */
|
||||
struct android_menu_event menu;
|
||||
|
||||
/* This is used to dispatch input method editing requests. */
|
||||
struct android_ime_event ime;
|
||||
};
|
||||
|
||||
enum
|
||||
|
|
@ -463,6 +513,13 @@ enum android_lookup_status
|
|||
ANDROID_LOOKUP_BOTH,
|
||||
};
|
||||
|
||||
enum android_ic_mode
|
||||
{
|
||||
ANDROID_IC_MODE_NULL = 0,
|
||||
ANDROID_IC_MODE_ACTION = 1,
|
||||
ANDROID_IC_MODE_TEXT = 2,
|
||||
};
|
||||
|
||||
extern int android_pending (void);
|
||||
extern void android_next_event (union android_event *);
|
||||
|
||||
|
|
@ -554,6 +611,9 @@ extern void android_sync (void);
|
|||
extern int android_wc_lookup_string (android_key_pressed_event *,
|
||||
wchar_t *, int, int *,
|
||||
enum android_lookup_status *);
|
||||
extern void android_update_ic (android_window, ptrdiff_t, ptrdiff_t,
|
||||
ptrdiff_t, ptrdiff_t);
|
||||
extern void android_reset_ic (android_window, enum android_ic_mode);
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
1037
src/androidterm.c
1037
src/androidterm.c
File diff suppressed because it is too large
Load diff
|
|
@ -11759,7 +11759,7 @@ syms_of_coding (void)
|
|||
DEFSYM (Qutf_8_unix, "utf-8-unix");
|
||||
DEFSYM (Qutf_8_emacs, "utf-8-emacs");
|
||||
|
||||
#if defined (WINDOWSNT) || defined (CYGWIN)
|
||||
#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
|
||||
/* No, not utf-16-le: that one has a BOM. */
|
||||
DEFSYM (Qutf_16le, "utf-16le");
|
||||
#endif
|
||||
|
|
|
|||
17
src/frame.c
17
src/frame.c
|
|
@ -997,6 +997,16 @@ make_frame (bool mini_p)
|
|||
f->select_mini_window_flag = false;
|
||||
/* This one should never be zero. */
|
||||
f->change_stamp = 1;
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
f->conversion.compose_region_start = Qnil;
|
||||
f->conversion.compose_region_end = Qnil;
|
||||
f->conversion.compose_region_overlay = Qnil;
|
||||
f->conversion.batch_edit_count = 0;
|
||||
f->conversion.batch_edit_flags = 0;
|
||||
f->conversion.actions = NULL;
|
||||
#endif
|
||||
|
||||
root_window = make_window ();
|
||||
rw = XWINDOW (root_window);
|
||||
if (mini_p)
|
||||
|
|
@ -2264,6 +2274,13 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
|
|||
f->terminal = 0; /* Now the frame is dead. */
|
||||
unblock_input ();
|
||||
|
||||
/* Clear markers and overlays set by F on behalf of an input
|
||||
method. */
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
if (FRAME_WINDOW_P (f))
|
||||
reset_frame_state (f);
|
||||
#endif
|
||||
|
||||
/* If needed, delete the terminal that this frame was on.
|
||||
(This must be done after the frame is killed.) */
|
||||
terminal->reference_count--;
|
||||
|
|
|
|||
62
src/frame.h
62
src/frame.h
|
|
@ -76,6 +76,63 @@ enum ns_appearance_type
|
|||
#endif
|
||||
#endif /* HAVE_WINDOW_SYSTEM */
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
|
||||
enum text_conversion_operation
|
||||
{
|
||||
TEXTCONV_START_BATCH_EDIT,
|
||||
TEXTCONV_END_BATCH_EDIT,
|
||||
TEXTCONV_COMMIT_TEXT,
|
||||
TEXTCONV_FINISH_COMPOSING_TEXT,
|
||||
TEXTCONV_SET_COMPOSING_TEXT,
|
||||
TEXTCONV_SET_COMPOSING_REGION,
|
||||
TEXTCONV_SET_POINT,
|
||||
TEXTCONV_DELETE_SURROUNDING_TEXT,
|
||||
};
|
||||
|
||||
/* Structure describing a single edit being performed by the input
|
||||
method that should be executed in the context of
|
||||
kbd_buffer_get_event. */
|
||||
|
||||
struct text_conversion_action
|
||||
{
|
||||
/* The next text conversion action. */
|
||||
struct text_conversion_action *next;
|
||||
|
||||
/* Any associated data. */
|
||||
Lisp_Object data;
|
||||
|
||||
/* The operation being performed. */
|
||||
enum text_conversion_operation operation;
|
||||
|
||||
/* Counter value. */
|
||||
unsigned long counter;
|
||||
};
|
||||
|
||||
/* Structure describing the text conversion state associated with a
|
||||
frame. */
|
||||
|
||||
struct text_conversion_state
|
||||
{
|
||||
/* List of text conversion actions associated with this frame. */
|
||||
struct text_conversion_action *actions;
|
||||
|
||||
/* Markers representing the composing region. */
|
||||
Lisp_Object compose_region_start, compose_region_end;
|
||||
|
||||
/* Overlay representing the composing region. */
|
||||
Lisp_Object compose_region_overlay;
|
||||
|
||||
/* The number of ongoing ``batch edits'' that are causing point
|
||||
reporting to be delayed. */
|
||||
int batch_edit_count;
|
||||
|
||||
/* Mask containing what must be updated after batch edits end. */
|
||||
int batch_edit_flags;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/* The structure representing a frame. */
|
||||
|
||||
struct frame
|
||||
|
|
@ -664,6 +721,11 @@ struct frame
|
|||
enum ns_appearance_type ns_appearance;
|
||||
bool_bf ns_transparent_titlebar;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
/* Text conversion state used by certain input methods. */
|
||||
struct text_conversion_state conversion;
|
||||
#endif
|
||||
} GCALIGNED_STRUCT;
|
||||
|
||||
/* Most code should use these functions to set Lisp fields in struct frame. */
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
|||
#include "atimer.h"
|
||||
#include "process.h"
|
||||
#include "menu.h"
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
#include "textconv.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef HAVE_PTHREAD
|
||||
|
|
@ -3020,6 +3025,10 @@ read_char (int commandflag, Lisp_Object map,
|
|||
{
|
||||
struct buffer *prev_buffer = current_buffer;
|
||||
last_input_event = c;
|
||||
|
||||
/* All a `text-conversion' event does is prevent Emacs from
|
||||
staying idle. It is not useful. */
|
||||
|
||||
call4 (Qcommand_execute, tem, Qnil, Fvector (1, &last_input_event), Qt);
|
||||
|
||||
if (CONSP (c) && !NILP (Fmemq (XCAR (c), Vwhile_no_input_ignore_events))
|
||||
|
|
@ -3582,6 +3591,11 @@ readable_events (int flags)
|
|||
return 1;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
if (detect_conversion_events ())
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
if (!(flags & READABLE_EVENTS_IGNORE_SQUEEZABLES) && some_mouse_moved ())
|
||||
return 1;
|
||||
if (single_kboard)
|
||||
|
|
@ -3914,6 +3928,11 @@ kbd_buffer_get_event (KBOARD **kbp,
|
|||
|
||||
had_pending_selection_requests = false;
|
||||
#endif
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
bool had_pending_conversion_events;
|
||||
|
||||
had_pending_conversion_events = false;
|
||||
#endif
|
||||
|
||||
#ifdef subprocesses
|
||||
if (kbd_on_hold_p () && kbd_buffer_nr_stored () < KBD_BUFFER_SIZE / 4)
|
||||
|
|
@ -3977,6 +3996,13 @@ kbd_buffer_get_event (KBOARD **kbp,
|
|||
had_pending_selection_requests = true;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
if (detect_conversion_events ())
|
||||
{
|
||||
had_pending_conversion_events = true;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (end_time)
|
||||
{
|
||||
|
|
@ -4024,6 +4050,14 @@ kbd_buffer_get_event (KBOARD **kbp,
|
|||
x_handle_pending_selection_requests ();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
/* Handle pending ``text conversion'' requests from an input
|
||||
method. */
|
||||
|
||||
if (had_pending_conversion_events)
|
||||
handle_pending_conversion_events ();
|
||||
#endif
|
||||
|
||||
if (CONSP (Vunread_command_events))
|
||||
{
|
||||
Lisp_Object first;
|
||||
|
|
@ -4380,12 +4414,25 @@ kbd_buffer_get_event (KBOARD **kbp,
|
|||
#ifdef HAVE_X_WINDOWS
|
||||
else if (had_pending_selection_requests)
|
||||
obj = Qnil;
|
||||
#endif
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
/* This is an internal event used to prevent Emacs from becoming
|
||||
idle immediately after a text conversion operation. */
|
||||
else if (had_pending_conversion_events)
|
||||
obj = Qtext_conversion;
|
||||
#endif
|
||||
else
|
||||
/* We were promised by the above while loop that there was
|
||||
something for us to read! */
|
||||
emacs_abort ();
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
/* While not implemented as keyboard commands, changes made by the
|
||||
input method still mean that Emacs is no longer idle. */
|
||||
if (had_pending_conversion_events)
|
||||
timer_stop_idle ();
|
||||
#endif
|
||||
|
||||
input_pending = readable_events (0);
|
||||
|
||||
Vlast_event_frame = internal_last_event_frame;
|
||||
|
|
@ -12902,6 +12949,9 @@ See also `pre-command-hook'. */);
|
|||
|
||||
DEFSYM (Qcoding, "coding");
|
||||
DEFSYM (Qtouchscreen, "touchscreen");
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
DEFSYM (Qtext_conversion, "text-conversion");
|
||||
#endif
|
||||
|
||||
Fset (Qecho_area_clear_hook, Qnil);
|
||||
|
||||
|
|
|
|||
|
|
@ -5230,7 +5230,10 @@ extern char *emacs_root_dir (void);
|
|||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
/* Defined in textconv.c. */
|
||||
extern void reset_frame_state (struct frame *);
|
||||
extern void report_selected_window_change (struct frame *);
|
||||
extern void report_point_change (struct frame *, struct window *,
|
||||
struct buffer *);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NATIVE_COMP
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ static struct rlimit nofile_limit;
|
|||
|
||||
#ifdef HAVE_ANDROID
|
||||
#include "android.h"
|
||||
#include "androidterm.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_WINDOW_SYSTEM
|
||||
|
|
|
|||
906
src/textconv.c
906
src/textconv.c
|
|
@ -25,7 +25,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
|||
ability to ``undo'' or ``edit'' previously composed text. This is
|
||||
most commonly seen in input methods for CJK laguages for X Windows,
|
||||
and is extensively used throughout Android by input methods for all
|
||||
kinds of scripts. */
|
||||
kinds of scripts.
|
||||
|
||||
In addition, these input methods may also need to make detailed
|
||||
edits to the content of a buffer. That is also handled here. */
|
||||
|
||||
#include <config.h>
|
||||
|
||||
|
|
@ -44,6 +47,15 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
|||
|
||||
static struct textconv_interface *text_interface;
|
||||
|
||||
/* Flags used to determine what must be sent after a batch edit
|
||||
ends. */
|
||||
|
||||
enum textconv_batch_edit_flags
|
||||
{
|
||||
PENDING_POINT_CHANGE = 1,
|
||||
PENDING_COMPOSE_CHANGE = 2,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* Copy the portion of the current buffer described by BEG, BEG_BYTE,
|
||||
|
|
@ -94,14 +106,18 @@ copy_buffer (ptrdiff_t beg, ptrdiff_t beg_byte,
|
|||
Then, either delete that text from the buffer if QUERY->operation
|
||||
is TEXTCONV_SUBSTITUTION, or return 0.
|
||||
|
||||
If FLAGS & TEXTCONV_SKIP_CONVERSION_REGION, then first move PT past
|
||||
the conversion region in the specified direction if it is inside.
|
||||
|
||||
Value is 0 if QUERY->operation was not TEXTCONV_SUBSTITUTION
|
||||
or if deleting the text was successful, and 1 otherwise. */
|
||||
|
||||
int
|
||||
textconv_query (struct frame *f, struct textconv_callback_struct *query)
|
||||
textconv_query (struct frame *f, struct textconv_callback_struct *query,
|
||||
int flags)
|
||||
{
|
||||
specpdl_ref count;
|
||||
ptrdiff_t pos, pos_byte, end, end_byte;
|
||||
ptrdiff_t pos, pos_byte, end, end_byte, start;
|
||||
ptrdiff_t temp, temp1;
|
||||
char *buffer;
|
||||
|
||||
|
|
@ -113,14 +129,46 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query)
|
|||
/* Inhibit quitting. */
|
||||
specbind (Qinhibit_quit, Qt);
|
||||
|
||||
/* Temporarily switch to F's selected window. */
|
||||
Fselect_window (f->selected_window, Qt);
|
||||
/* Temporarily switch to F's selected window at the time of the last
|
||||
redisplay. */
|
||||
Fselect_window ((WINDOW_LIVE_P (f->old_selected_window)
|
||||
? f->old_selected_window
|
||||
: f->selected_window), Qt);
|
||||
|
||||
/* Now find the appropriate text bounds for QUERY. First, move
|
||||
point QUERY->position steps forward or backwards. */
|
||||
|
||||
pos = PT;
|
||||
|
||||
/* Next, if POS lies within the conversion region and the caller
|
||||
asked for it to be moved away, move it away from the conversion
|
||||
region. */
|
||||
|
||||
if (flags & TEXTCONV_SKIP_CONVERSION_REGION
|
||||
&& MARKERP (f->conversion.compose_region_start))
|
||||
{
|
||||
start = marker_position (f->conversion.compose_region_start);
|
||||
end = marker_position (f->conversion.compose_region_end);
|
||||
|
||||
if (pos >= start && pos < end)
|
||||
{
|
||||
switch (query->direction)
|
||||
{
|
||||
case TEXTCONV_FORWARD_CHAR:
|
||||
case TEXTCONV_FORWARD_WORD:
|
||||
case TEXTCONV_CARET_DOWN:
|
||||
case TEXTCONV_NEXT_LINE:
|
||||
case TEXTCONV_LINE_START:
|
||||
pos = end;
|
||||
break;
|
||||
|
||||
default:
|
||||
pos = max (BEGV, start - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If pos is outside the accessible part of the buffer or if it
|
||||
overflows, move back to point or to the extremes of the
|
||||
accessible region. */
|
||||
|
|
@ -287,6 +335,828 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Reset F's text conversion state. Delete any overlays or
|
||||
markers inside. */
|
||||
|
||||
void
|
||||
reset_frame_state (struct frame *f)
|
||||
{
|
||||
struct text_conversion_action *last, *next;
|
||||
|
||||
/* Make the composition region markers point elsewhere. */
|
||||
|
||||
if (!NILP (f->conversion.compose_region_start))
|
||||
{
|
||||
Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
|
||||
Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
|
||||
f->conversion.compose_region_start = Qnil;
|
||||
f->conversion.compose_region_end = Qnil;
|
||||
}
|
||||
|
||||
/* Delete the composition region overlay. */
|
||||
|
||||
if (!NILP (f->conversion.compose_region_overlay))
|
||||
Fdelete_overlay (f->conversion.compose_region_overlay);
|
||||
|
||||
/* Delete each text conversion action queued up. */
|
||||
|
||||
next = f->conversion.actions;
|
||||
while (next)
|
||||
{
|
||||
last = next;
|
||||
next = next->next;
|
||||
|
||||
/* Say that the conversion is finished. */
|
||||
if (text_interface && text_interface->notify_conversion)
|
||||
text_interface->notify_conversion (last->counter);
|
||||
|
||||
xfree (last);
|
||||
}
|
||||
f->conversion.actions = NULL;
|
||||
|
||||
/* Clear batch edit state. */
|
||||
f->conversion.batch_edit_count = 0;
|
||||
f->conversion.batch_edit_flags = 0;
|
||||
}
|
||||
|
||||
/* Return whether or not there are pending edits from an input method
|
||||
on any frame. */
|
||||
|
||||
bool
|
||||
detect_conversion_events (void)
|
||||
{
|
||||
Lisp_Object tail, frame;
|
||||
|
||||
FOR_EACH_FRAME (tail, frame)
|
||||
{
|
||||
if (XFRAME (frame)->conversion.actions)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Restore the selected window WINDOW. */
|
||||
|
||||
static void
|
||||
restore_selected_window (Lisp_Object window)
|
||||
{
|
||||
/* FIXME: not sure what to do if WINDOW has been deleted. */
|
||||
Fselect_window (window, Qt);
|
||||
}
|
||||
|
||||
/* Commit the given text in the composing region. If there is no
|
||||
composing region, then insert the text after F's selected window's
|
||||
last point instead. Finally, remove the composing region. */
|
||||
|
||||
static void
|
||||
really_commit_text (struct frame *f, EMACS_INT position,
|
||||
Lisp_Object text)
|
||||
{
|
||||
specpdl_ref count;
|
||||
ptrdiff_t wanted, start, end;
|
||||
|
||||
/* If F's old selected window is no longer live, fail. */
|
||||
|
||||
if (!WINDOW_LIVE_P (f->old_selected_window))
|
||||
return;
|
||||
|
||||
count = SPECPDL_INDEX ();
|
||||
record_unwind_protect (restore_selected_window,
|
||||
selected_window);
|
||||
|
||||
/* Temporarily switch to F's selected window at the time of the last
|
||||
redisplay. */
|
||||
Fselect_window (f->old_selected_window, Qt);
|
||||
|
||||
/* Now detect whether or not there is a composing region.
|
||||
If there is, then replace it with TEXT. Don't do that
|
||||
otherwise. */
|
||||
|
||||
if (MARKERP (f->conversion.compose_region_start))
|
||||
{
|
||||
/* Replace its contents. */
|
||||
start = marker_position (f->conversion.compose_region_start);
|
||||
end = marker_position (f->conversion.compose_region_end);
|
||||
safe_del_range (start, end);
|
||||
Finsert (1, &text);
|
||||
|
||||
/* Move to a the position specified in POSITION. */
|
||||
|
||||
if (position < 0)
|
||||
{
|
||||
wanted
|
||||
= marker_position (f->conversion.compose_region_start);
|
||||
|
||||
if (INT_SUBTRACT_WRAPV (wanted, position, &wanted)
|
||||
|| wanted < BEGV)
|
||||
wanted = BEGV;
|
||||
|
||||
if (wanted > ZV)
|
||||
wanted = ZV;
|
||||
|
||||
set_point (wanted);
|
||||
}
|
||||
else
|
||||
{
|
||||
wanted
|
||||
= marker_position (f->conversion.compose_region_end);
|
||||
|
||||
if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
|
||||
|| wanted > ZV)
|
||||
wanted = ZV;
|
||||
|
||||
if (wanted < BEGV)
|
||||
wanted = BEGV;
|
||||
|
||||
set_point (wanted);
|
||||
}
|
||||
|
||||
/* Make the composition region markers point elsewhere. */
|
||||
|
||||
if (!NILP (f->conversion.compose_region_start))
|
||||
{
|
||||
Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
|
||||
Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
|
||||
f->conversion.compose_region_start = Qnil;
|
||||
f->conversion.compose_region_end = Qnil;
|
||||
}
|
||||
|
||||
/* Delete the composition region overlay. */
|
||||
|
||||
if (!NILP (f->conversion.compose_region_overlay))
|
||||
Fdelete_overlay (f->conversion.compose_region_overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise, move the text and point to an appropriate
|
||||
location. */
|
||||
wanted = PT;
|
||||
Finsert (1, &text);
|
||||
|
||||
if (position < 0)
|
||||
{
|
||||
if (INT_SUBTRACT_WRAPV (wanted, position, &wanted)
|
||||
|| wanted < BEGV)
|
||||
wanted = BEGV;
|
||||
|
||||
if (wanted > ZV)
|
||||
wanted = ZV;
|
||||
|
||||
set_point (wanted);
|
||||
}
|
||||
else
|
||||
{
|
||||
wanted = PT;
|
||||
|
||||
if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
|
||||
|| wanted > ZV)
|
||||
wanted = ZV;
|
||||
|
||||
if (wanted < BEGV)
|
||||
wanted = BEGV;
|
||||
|
||||
set_point (wanted);
|
||||
}
|
||||
}
|
||||
|
||||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
/* Remove the composition region on the frame F, while leaving its
|
||||
contents intact. */
|
||||
|
||||
static void
|
||||
really_finish_composing_text (struct frame *f)
|
||||
{
|
||||
if (!NILP (f->conversion.compose_region_start))
|
||||
{
|
||||
Fset_marker (f->conversion.compose_region_start, Qnil, Qnil);
|
||||
Fset_marker (f->conversion.compose_region_end, Qnil, Qnil);
|
||||
f->conversion.compose_region_start = Qnil;
|
||||
f->conversion.compose_region_end = Qnil;
|
||||
}
|
||||
|
||||
/* Delete the composition region overlay. */
|
||||
|
||||
if (!NILP (f->conversion.compose_region_overlay))
|
||||
Fdelete_overlay (f->conversion.compose_region_overlay);
|
||||
}
|
||||
|
||||
/* Set the composing text on F to TEXT. Then, move point to an
|
||||
appropriate position relative to POSITION, and call
|
||||
`compose_region_changed' in the text conversion interface should
|
||||
point not have been changed relative to F's old selected window's
|
||||
last point. */
|
||||
|
||||
static void
|
||||
really_set_composing_text (struct frame *f, ptrdiff_t position,
|
||||
Lisp_Object text)
|
||||
{
|
||||
specpdl_ref count;
|
||||
ptrdiff_t start, wanted, end;
|
||||
struct window *w;
|
||||
|
||||
/* If F's old selected window is no longer live, fail. */
|
||||
|
||||
if (!WINDOW_LIVE_P (f->old_selected_window))
|
||||
return;
|
||||
|
||||
count = SPECPDL_INDEX ();
|
||||
record_unwind_protect (restore_selected_window,
|
||||
selected_window);
|
||||
|
||||
/* Temporarily switch to F's selected window at the time of the last
|
||||
redisplay. */
|
||||
w = XWINDOW (f->old_selected_window);
|
||||
Fselect_window (f->old_selected_window, Qt);
|
||||
|
||||
/* Now set up the composition region if necessary. */
|
||||
|
||||
if (!MARKERP (f->conversion.compose_region_start))
|
||||
{
|
||||
f->conversion.compose_region_start = Fmake_marker ();
|
||||
f->conversion.compose_region_end = Fmake_marker ();
|
||||
Fset_marker (f->conversion.compose_region_start,
|
||||
Fpoint (), Qnil);
|
||||
Fset_marker (f->conversion.compose_region_end,
|
||||
Fpoint (), Qnil);
|
||||
Fset_marker_insertion_type (f->conversion.compose_region_end,
|
||||
Qt);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Delete the text between the start of the composition region
|
||||
and its end. TODO: avoid this widening. */
|
||||
record_unwind_protect (save_restriction_restore,
|
||||
save_restriction_save ());
|
||||
Fwiden ();
|
||||
start = marker_position (f->conversion.compose_region_start);
|
||||
end = marker_position (f->conversion.compose_region_end);
|
||||
safe_del_range (start, end);
|
||||
set_point (start);
|
||||
}
|
||||
|
||||
/* Insert the new text. */
|
||||
Finsert (1, &text);
|
||||
|
||||
/* Now move point to an appropriate location. */
|
||||
if (position < 0)
|
||||
{
|
||||
wanted = start;
|
||||
|
||||
if (INT_SUBTRACT_WRAPV (wanted, position, &wanted)
|
||||
|| wanted < BEGV)
|
||||
wanted = BEGV;
|
||||
|
||||
if (wanted > ZV)
|
||||
wanted = ZV;
|
||||
}
|
||||
else
|
||||
{
|
||||
end = marker_position (f->conversion.compose_region_end);
|
||||
wanted = end;
|
||||
|
||||
/* end should be PT after the edit. */
|
||||
eassert (end == PT);
|
||||
|
||||
if (INT_ADD_WRAPV (wanted, position - 1, &wanted)
|
||||
|| wanted > ZV)
|
||||
wanted = ZV;
|
||||
|
||||
if (wanted < BEGV)
|
||||
wanted = BEGV;
|
||||
}
|
||||
|
||||
set_point (wanted);
|
||||
|
||||
/* If PT hasn't changed, the conversion region definitely has.
|
||||
Otherwise, redisplay will update the input method instead. */
|
||||
|
||||
if (PT == w->last_point
|
||||
&& text_interface
|
||||
&& text_interface->compose_region_changed)
|
||||
{
|
||||
if (f->conversion.batch_edit_count > 0)
|
||||
f->conversion.batch_edit_flags |= PENDING_COMPOSE_CHANGE;
|
||||
else
|
||||
text_interface->compose_region_changed (f);
|
||||
}
|
||||
|
||||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
/* Set the composing region to START by END. Make it that it is not
|
||||
already set. */
|
||||
|
||||
static void
|
||||
really_set_composing_region (struct frame *f, ptrdiff_t start,
|
||||
ptrdiff_t end)
|
||||
{
|
||||
specpdl_ref count;
|
||||
|
||||
/* If F's old selected window is no longer live, fail. */
|
||||
|
||||
if (!WINDOW_LIVE_P (f->old_selected_window))
|
||||
return;
|
||||
|
||||
/* If MAX (0, start) == end, then this should behave the same as
|
||||
really_finish_composing_text. */
|
||||
|
||||
if (max (0, start) == max (0, end))
|
||||
{
|
||||
really_finish_composing_text (f);
|
||||
return;
|
||||
}
|
||||
|
||||
count = SPECPDL_INDEX ();
|
||||
record_unwind_protect (restore_selected_window,
|
||||
selected_window);
|
||||
|
||||
/* Temporarily switch to F's selected window at the time of the last
|
||||
redisplay. */
|
||||
Fselect_window (f->old_selected_window, Qt);
|
||||
|
||||
/* Now set up the composition region if necessary. */
|
||||
|
||||
if (!MARKERP (f->conversion.compose_region_start))
|
||||
{
|
||||
f->conversion.compose_region_start = Fmake_marker ();
|
||||
f->conversion.compose_region_end = Fmake_marker ();
|
||||
Fset_marker_insertion_type (f->conversion.compose_region_end,
|
||||
Qt);
|
||||
}
|
||||
|
||||
Fset_marker (f->conversion.compose_region_start,
|
||||
make_fixnum (start), Qnil);
|
||||
Fset_marker (f->conversion.compose_region_end,
|
||||
make_fixnum (end), Qnil);
|
||||
|
||||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
/* Delete LEFT and RIGHT chars around point. */
|
||||
|
||||
static void
|
||||
really_delete_surrounding_text (struct frame *f, ptrdiff_t left,
|
||||
ptrdiff_t right)
|
||||
{
|
||||
specpdl_ref count;
|
||||
ptrdiff_t start, end;
|
||||
|
||||
/* If F's old selected window is no longer live, fail. */
|
||||
|
||||
if (!WINDOW_LIVE_P (f->old_selected_window))
|
||||
return;
|
||||
|
||||
count = SPECPDL_INDEX ();
|
||||
record_unwind_protect (restore_selected_window,
|
||||
selected_window);
|
||||
|
||||
/* Temporarily switch to F's selected window at the time of the last
|
||||
redisplay. */
|
||||
Fselect_window (f->old_selected_window, Qt);
|
||||
|
||||
start = max (BEGV, PT - left);
|
||||
end = min (ZV, PT + right);
|
||||
|
||||
safe_del_range (start, end);
|
||||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
/* Set point in F to POSITION.
|
||||
|
||||
If it has not changed, signal an update through the text input
|
||||
interface, which is necessary for the IME to acknowledge that the
|
||||
change has completed. */
|
||||
|
||||
static void
|
||||
really_set_point (struct frame *f, ptrdiff_t point)
|
||||
{
|
||||
specpdl_ref count;
|
||||
|
||||
/* If F's old selected window is no longer live, fail. */
|
||||
|
||||
if (!WINDOW_LIVE_P (f->old_selected_window))
|
||||
return;
|
||||
|
||||
count = SPECPDL_INDEX ();
|
||||
record_unwind_protect (restore_selected_window,
|
||||
selected_window);
|
||||
|
||||
/* Temporarily switch to F's selected window at the time of the last
|
||||
redisplay. */
|
||||
Fselect_window (f->old_selected_window, Qt);
|
||||
|
||||
if (point == PT)
|
||||
{
|
||||
if (f->conversion.batch_edit_count > 0)
|
||||
f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
|
||||
else
|
||||
text_interface->point_changed (f,
|
||||
XWINDOW (f->old_selected_window),
|
||||
current_buffer);
|
||||
}
|
||||
else
|
||||
/* Set the point. */
|
||||
Fgoto_char (make_fixnum (point));
|
||||
|
||||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
/* Complete the edit specified by the counter value inside *TOKEN. */
|
||||
|
||||
static void
|
||||
complete_edit (void *token)
|
||||
{
|
||||
if (text_interface && text_interface->notify_conversion)
|
||||
text_interface->notify_conversion (*(unsigned long *) token);
|
||||
}
|
||||
|
||||
/* Process and free the text conversion ACTION. F must be the frame
|
||||
on which ACTION will be performed. */
|
||||
|
||||
static void
|
||||
handle_pending_conversion_events_1 (struct frame *f,
|
||||
struct text_conversion_action *action)
|
||||
{
|
||||
Lisp_Object data;
|
||||
enum text_conversion_operation operation;
|
||||
struct buffer *buffer;
|
||||
struct window *w;
|
||||
specpdl_ref count;
|
||||
unsigned long token;
|
||||
|
||||
/* Next, process this action and free it. */
|
||||
|
||||
data = action->data;
|
||||
operation = action->operation;
|
||||
token = action->counter;
|
||||
xfree (action);
|
||||
|
||||
/* Make sure completion is signalled. */
|
||||
count = SPECPDL_INDEX ();
|
||||
record_unwind_protect_ptr (complete_edit, &token);
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case TEXTCONV_START_BATCH_EDIT:
|
||||
f->conversion.batch_edit_count++;
|
||||
break;
|
||||
|
||||
case TEXTCONV_END_BATCH_EDIT:
|
||||
if (f->conversion.batch_edit_count > 0)
|
||||
f->conversion.batch_edit_count--;
|
||||
|
||||
if (!WINDOW_LIVE_P (f->old_selected_window))
|
||||
break;
|
||||
|
||||
if (f->conversion.batch_edit_flags & PENDING_POINT_CHANGE)
|
||||
{
|
||||
w = XWINDOW (f->old_selected_window);
|
||||
buffer = XBUFFER (WINDOW_BUFFER (w));
|
||||
|
||||
text_interface->point_changed (f, w, buffer);
|
||||
}
|
||||
|
||||
if (f->conversion.batch_edit_flags & PENDING_COMPOSE_CHANGE)
|
||||
text_interface->compose_region_changed (f);
|
||||
|
||||
f->conversion.batch_edit_flags = 0;
|
||||
break;
|
||||
|
||||
case TEXTCONV_COMMIT_TEXT:
|
||||
really_commit_text (f, XFIXNUM (XCAR (data)), XCDR (data));
|
||||
break;
|
||||
|
||||
case TEXTCONV_FINISH_COMPOSING_TEXT:
|
||||
really_finish_composing_text (f);
|
||||
break;
|
||||
|
||||
case TEXTCONV_SET_COMPOSING_TEXT:
|
||||
really_set_composing_text (f, XFIXNUM (XCAR (data)),
|
||||
XCDR (data));
|
||||
break;
|
||||
|
||||
case TEXTCONV_SET_COMPOSING_REGION:
|
||||
really_set_composing_region (f, XFIXNUM (XCAR (data)),
|
||||
XFIXNUM (XCDR (data)));
|
||||
break;
|
||||
|
||||
case TEXTCONV_SET_POINT:
|
||||
really_set_point (f, XFIXNUM (data));
|
||||
break;
|
||||
|
||||
case TEXTCONV_DELETE_SURROUNDING_TEXT:
|
||||
really_delete_surrounding_text (f, XFIXNUM (XCAR (data)),
|
||||
XFIXNUM (XCDR (data)));
|
||||
break;
|
||||
}
|
||||
|
||||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
/* Process any outstanding text conversion events.
|
||||
This may run Lisp or signal. */
|
||||
|
||||
void
|
||||
handle_pending_conversion_events (void)
|
||||
{
|
||||
struct frame *f;
|
||||
Lisp_Object tail, frame;
|
||||
struct text_conversion_action *action, *next;
|
||||
bool handled;
|
||||
|
||||
handled = false;
|
||||
|
||||
FOR_EACH_FRAME (tail, frame)
|
||||
{
|
||||
f = XFRAME (frame);
|
||||
|
||||
/* Test if F has any outstanding conversion events. Then
|
||||
process them in bottom to up order. */
|
||||
for (action = f->conversion.actions; action; action = next)
|
||||
{
|
||||
/* Redisplay in between if there is more than one
|
||||
action. */
|
||||
|
||||
if (handled)
|
||||
redisplay ();
|
||||
|
||||
/* Unlink this action. */
|
||||
next = action->next;
|
||||
f->conversion.actions = next;
|
||||
|
||||
/* Handle and free the action. */
|
||||
handle_pending_conversion_events_1 (f, action);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Start a ``batch edit'' in F. During a batch edit, point_changed
|
||||
will not be called until the batch edit ends.
|
||||
|
||||
Process the actual operation in the event loop in keyboard.c; then,
|
||||
call `notify_conversion' in the text conversion interface with
|
||||
COUNTER. */
|
||||
|
||||
void
|
||||
start_batch_edit (struct frame *f, unsigned long counter)
|
||||
{
|
||||
struct text_conversion_action *action, **last;
|
||||
|
||||
action = xmalloc (sizeof *action);
|
||||
action->operation = TEXTCONV_START_BATCH_EDIT;
|
||||
action->data = Qnil;
|
||||
action->next = NULL;
|
||||
action->counter = counter;
|
||||
for (last = &f->conversion.actions; *last; last = &(*last)->next)
|
||||
;;
|
||||
*last = action;
|
||||
input_pending = true;
|
||||
}
|
||||
|
||||
/* End a ``batch edit''. It is ok to call this function even if a
|
||||
batch edit has not yet started, in which case it does nothing.
|
||||
|
||||
COUNTER means the same as in `start_batch_edit'. */
|
||||
|
||||
void
|
||||
end_batch_edit (struct frame *f, unsigned long counter)
|
||||
{
|
||||
struct text_conversion_action *action, **last;
|
||||
|
||||
action = xmalloc (sizeof *action);
|
||||
action->operation = TEXTCONV_END_BATCH_EDIT;
|
||||
action->data = Qnil;
|
||||
action->next = NULL;
|
||||
action->counter = counter;
|
||||
for (last = &f->conversion.actions; *last; last = &(*last)->next)
|
||||
;;
|
||||
*last = action;
|
||||
input_pending = true;
|
||||
}
|
||||
|
||||
/* Insert the specified STRING into F's current buffer's composition
|
||||
region, and set point to POSITION relative to STRING.
|
||||
|
||||
COUNTER means the same as in `start_batch_edit'. */
|
||||
|
||||
void
|
||||
commit_text (struct frame *f, Lisp_Object string,
|
||||
ptrdiff_t position, unsigned long counter)
|
||||
{
|
||||
struct text_conversion_action *action, **last;
|
||||
|
||||
action = xmalloc (sizeof *action);
|
||||
action->operation = TEXTCONV_COMMIT_TEXT;
|
||||
action->data = Fcons (make_fixnum (position), string);
|
||||
action->next = NULL;
|
||||
action->counter = counter;
|
||||
for (last = &f->conversion.actions; *last; last = &(*last)->next)
|
||||
;;
|
||||
*last = action;
|
||||
input_pending = true;
|
||||
}
|
||||
|
||||
/* Remove the composition region and its overlay from F's current
|
||||
buffer. Leave the text being composed intact.
|
||||
|
||||
COUNTER means the same as in `start_batch_edit'. */
|
||||
|
||||
void
|
||||
finish_composing_text (struct frame *f, unsigned long counter)
|
||||
{
|
||||
struct text_conversion_action *action, **last;
|
||||
|
||||
action = xmalloc (sizeof *action);
|
||||
action->operation = TEXTCONV_FINISH_COMPOSING_TEXT;
|
||||
action->data = Qnil;
|
||||
action->next = NULL;
|
||||
action->counter = counter;
|
||||
for (last = &f->conversion.actions; *last; last = &(*last)->next)
|
||||
;;
|
||||
*last = action;
|
||||
input_pending = true;
|
||||
}
|
||||
|
||||
/* Insert the given STRING and make it the currently active
|
||||
composition.
|
||||
|
||||
If there is currently no composing region, then the new value of
|
||||
point is used as the composing region.
|
||||
|
||||
Then, the composing region is replaced with the text in the
|
||||
specified string.
|
||||
|
||||
Finally, move point to new_point, which is relative to either the
|
||||
start or the end of OBJECT depending on whether or not it is less
|
||||
than zero.
|
||||
|
||||
COUNTER means the same as in `start_batch_edit'. */
|
||||
|
||||
void
|
||||
set_composing_text (struct frame *f, Lisp_Object object,
|
||||
ptrdiff_t new_point, unsigned long counter)
|
||||
{
|
||||
struct text_conversion_action *action, **last;
|
||||
|
||||
action = xmalloc (sizeof *action);
|
||||
action->operation = TEXTCONV_SET_COMPOSING_TEXT;
|
||||
action->data = Fcons (make_fixnum (new_point),
|
||||
object);
|
||||
action->next = NULL;
|
||||
action->counter = counter;
|
||||
for (last = &f->conversion.actions; *last; last = &(*last)->next)
|
||||
;;
|
||||
*last = action;
|
||||
input_pending = true;
|
||||
}
|
||||
|
||||
/* Make the region between START and END the currently active
|
||||
``composing region''.
|
||||
|
||||
The ``composing region'' is a region of text in the buffer that is
|
||||
about to undergo editing by the input method. */
|
||||
|
||||
void
|
||||
set_composing_region (struct frame *f, ptrdiff_t start,
|
||||
ptrdiff_t end, unsigned long counter)
|
||||
{
|
||||
struct text_conversion_action *action, **last;
|
||||
|
||||
if (start > MOST_POSITIVE_FIXNUM)
|
||||
start = MOST_POSITIVE_FIXNUM;
|
||||
|
||||
if (end > MOST_POSITIVE_FIXNUM)
|
||||
end = MOST_POSITIVE_FIXNUM;
|
||||
|
||||
action = xmalloc (sizeof *action);
|
||||
action->operation = TEXTCONV_SET_COMPOSING_REGION;
|
||||
action->data = Fcons (make_fixnum (start),
|
||||
make_fixnum (end));
|
||||
action->next = NULL;
|
||||
action->counter = counter;
|
||||
for (last = &f->conversion.actions; *last; last = &(*last)->next)
|
||||
;;
|
||||
*last = action;
|
||||
input_pending = true;
|
||||
}
|
||||
|
||||
/* Move point in F's selected buffer to POINT.
|
||||
|
||||
COUNTER means the same as in `start_batch_edit'. */
|
||||
|
||||
void
|
||||
textconv_set_point (struct frame *f, ptrdiff_t point,
|
||||
unsigned long counter)
|
||||
{
|
||||
struct text_conversion_action *action, **last;
|
||||
|
||||
if (point > MOST_POSITIVE_FIXNUM)
|
||||
point = MOST_POSITIVE_FIXNUM;
|
||||
|
||||
action = xmalloc (sizeof *action);
|
||||
action->operation = TEXTCONV_SET_POINT;
|
||||
action->data = make_fixnum (point);
|
||||
action->next = NULL;
|
||||
action->counter = counter;
|
||||
for (last = &f->conversion.actions; *last; last = &(*last)->next)
|
||||
;;
|
||||
*last = action;
|
||||
input_pending = true;
|
||||
}
|
||||
|
||||
/* Delete LEFT and RIGHT characters around point in F's old selected
|
||||
window. */
|
||||
|
||||
void
|
||||
delete_surrounding_text (struct frame *f, ptrdiff_t left,
|
||||
ptrdiff_t right, unsigned long counter)
|
||||
{
|
||||
struct text_conversion_action *action, **last;
|
||||
|
||||
action = xmalloc (sizeof *action);
|
||||
action->operation = TEXTCONV_DELETE_SURROUNDING_TEXT;
|
||||
action->data = Fcons (make_fixnum (left),
|
||||
make_fixnum (right));
|
||||
action->next = NULL;
|
||||
action->counter = counter;
|
||||
for (last = &f->conversion.actions; *last; last = &(*last)->next)
|
||||
;;
|
||||
*last = action;
|
||||
input_pending = true;
|
||||
}
|
||||
|
||||
/* Return N characters of text around point in F's old selected
|
||||
window.
|
||||
|
||||
Set *N to the actual number of characters returned, *START_RETURN
|
||||
to the position of the first character returned, *OFFSET to the
|
||||
offset of point within that text, *LENGTH to the actual number of
|
||||
characters returned, and *BYTES to the actual number of bytes
|
||||
returned.
|
||||
|
||||
Value is NULL upon failure, and a malloced string upon success. */
|
||||
|
||||
char *
|
||||
get_extracted_text (struct frame *f, ptrdiff_t n,
|
||||
ptrdiff_t *start_return,
|
||||
ptrdiff_t *offset, ptrdiff_t *length,
|
||||
ptrdiff_t *bytes)
|
||||
{
|
||||
specpdl_ref count;
|
||||
ptrdiff_t start, end, start_byte, end_byte;
|
||||
char *buffer;
|
||||
|
||||
if (!WINDOW_LIVE_P (f->old_selected_window))
|
||||
return NULL;
|
||||
|
||||
/* Save the excursion, as there will be extensive changes to the
|
||||
selected window. */
|
||||
count = SPECPDL_INDEX ();
|
||||
record_unwind_protect_excursion ();
|
||||
|
||||
/* Inhibit quitting. */
|
||||
specbind (Qinhibit_quit, Qt);
|
||||
|
||||
/* Temporarily switch to F's selected window at the time of the last
|
||||
redisplay. */
|
||||
Fselect_window (f->old_selected_window, Qt);
|
||||
|
||||
/* Figure out the bounds of the text to return. */
|
||||
start = PT - n / 2;
|
||||
end = PT + n - n / 2;
|
||||
start = max (start, BEGV);
|
||||
end = min (end, ZV);
|
||||
buffer = NULL;
|
||||
|
||||
/* Detect overflow. */
|
||||
|
||||
if (!(start <= PT <= end))
|
||||
goto finish;
|
||||
|
||||
/* Convert the character positions to byte positions. */
|
||||
start_byte = CHAR_TO_BYTE (start);
|
||||
end_byte = CHAR_TO_BYTE (end);
|
||||
|
||||
/* Extract the text from the buffer. */
|
||||
buffer = xmalloc (end_byte - start_byte);
|
||||
copy_buffer (start, start_byte, end, end_byte,
|
||||
buffer);
|
||||
|
||||
/* Return the offsets. */
|
||||
*start_return = start;
|
||||
*offset = PT - start;
|
||||
*length = end - start;
|
||||
*bytes = end_byte - start_byte;
|
||||
|
||||
finish:
|
||||
unbind_to (count, Qnil);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Window system interface. These are called from the rest of
|
||||
|
|
@ -298,16 +1168,40 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query)
|
|||
void
|
||||
report_selected_window_change (struct frame *f)
|
||||
{
|
||||
reset_frame_state (f);
|
||||
|
||||
if (!text_interface)
|
||||
return;
|
||||
|
||||
text_interface->reset (f);
|
||||
}
|
||||
|
||||
/* Notice that the point in F's selected window's current buffer has
|
||||
changed.
|
||||
|
||||
F is the frame whose selected window was changed, W is the window
|
||||
in question, and BUFFER is that window's current buffer.
|
||||
|
||||
Tell the text conversion interface about the change; it will likely
|
||||
pass the information on to the system input method. */
|
||||
|
||||
void
|
||||
report_point_change (struct frame *f, struct window *window,
|
||||
struct buffer *buffer)
|
||||
{
|
||||
if (!text_interface || !text_interface->point_changed)
|
||||
return;
|
||||
|
||||
if (f->conversion.batch_edit_count > 0)
|
||||
f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
|
||||
else
|
||||
text_interface->point_changed (f, window, buffer);
|
||||
}
|
||||
|
||||
/* Register INTERFACE as the text conversion interface. */
|
||||
|
||||
void
|
||||
register_texconv_interface (struct textconv_interface *interface)
|
||||
register_textconv_interface (struct textconv_interface *interface)
|
||||
{
|
||||
text_interface = interface;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,20 @@ struct textconv_interface
|
|||
happen if the window is deleted or switches buffers, or an
|
||||
unexpected buffer change occurs.) */
|
||||
void (*reset) (struct frame *);
|
||||
|
||||
/* Notice that point has moved in the specified frame's selected
|
||||
window's selected buffer. The second argument is the window
|
||||
whose point changed, and the third argument is the buffer. */
|
||||
void (*point_changed) (struct frame *, struct window *,
|
||||
struct buffer *);
|
||||
|
||||
/* Notice that the preconversion region has changed without point
|
||||
being moved. */
|
||||
void (*compose_region_changed) (struct frame *);
|
||||
|
||||
/* Notice that an asynch conversion identified by COUNTER has
|
||||
completed. */
|
||||
void (*notify_conversion) (unsigned long);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -103,7 +117,27 @@ struct textconv_callback_struct
|
|||
struct textconv_conversion_text text;
|
||||
};
|
||||
|
||||
extern int textconv_query (struct frame *, struct textconv_callback_struct *);
|
||||
extern void register_texconv_interface (struct textconv_interface *);
|
||||
#define TEXTCONV_SKIP_CONVERSION_REGION (1 << 0)
|
||||
|
||||
extern int textconv_query (struct frame *, struct textconv_callback_struct *,
|
||||
int);
|
||||
extern bool detect_conversion_events (void);
|
||||
extern void handle_pending_conversion_events (void);
|
||||
extern void start_batch_edit (struct frame *, unsigned long);
|
||||
extern void end_batch_edit (struct frame *, unsigned long);
|
||||
extern void commit_text (struct frame *, Lisp_Object, ptrdiff_t,
|
||||
unsigned long);
|
||||
extern void finish_composing_text (struct frame *, unsigned long);
|
||||
extern void set_composing_text (struct frame *, Lisp_Object,
|
||||
ptrdiff_t, unsigned long);
|
||||
extern void set_composing_region (struct frame *, ptrdiff_t, ptrdiff_t,
|
||||
unsigned long);
|
||||
extern void textconv_set_point (struct frame *, ptrdiff_t, unsigned long);
|
||||
extern void delete_surrounding_text (struct frame *, ptrdiff_t,
|
||||
ptrdiff_t, unsigned long);
|
||||
extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *,
|
||||
ptrdiff_t *, ptrdiff_t *, ptrdiff_t *);
|
||||
|
||||
extern void register_textconv_interface (struct textconv_interface *);
|
||||
|
||||
#endif /* _TEXTCONV_H_ */
|
||||
|
|
|
|||
28
src/xdisp.c
28
src/xdisp.c
|
|
@ -17267,6 +17267,9 @@ static void
|
|||
mark_window_display_accurate_1 (struct window *w, bool accurate_p)
|
||||
{
|
||||
struct buffer *b = XBUFFER (w->contents);
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
ptrdiff_t prev_point;
|
||||
#endif
|
||||
|
||||
w->last_modified = accurate_p ? BUF_MODIFF (b) : 0;
|
||||
w->last_overlay_modified = accurate_p ? BUF_OVERLAY_MODIFF (b) : 0;
|
||||
|
|
@ -17296,11 +17299,36 @@ mark_window_display_accurate_1 (struct window *w, bool accurate_p)
|
|||
w->last_cursor_vpos = w->cursor.vpos;
|
||||
w->last_cursor_off_p = w->cursor_off_p;
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
prev_point = w->last_point;
|
||||
#endif
|
||||
|
||||
if (w == XWINDOW (selected_window))
|
||||
w->last_point = BUF_PT (b);
|
||||
else
|
||||
w->last_point = marker_position (w->pointm);
|
||||
|
||||
#ifdef HAVE_TEXT_CONVERSION
|
||||
/* Point motion is only propagated to the input method for use
|
||||
in text conversion during a redisplay. While this can lead
|
||||
to inconsistencies when point has moved but the change has
|
||||
not yet been displayed, it leads to better results most of
|
||||
the time, as point often changes within calls to
|
||||
`save-excursion', and the only way to detect such calls is to
|
||||
observe that the next redisplay never ends with those changes
|
||||
applied.
|
||||
|
||||
Changes to buffer text are immediately propagated to the
|
||||
input method, and the position of point is also updated
|
||||
during such a change, so the consequences are not that
|
||||
severe. */
|
||||
|
||||
if (prev_point != w->last_point
|
||||
&& FRAME_WINDOW_P (WINDOW_XFRAME (w))
|
||||
&& w == XWINDOW (WINDOW_XFRAME (w)->selected_window))
|
||||
report_point_change (WINDOW_XFRAME (w), w, b);
|
||||
#endif
|
||||
|
||||
w->window_end_valid = true;
|
||||
w->update_mode_line = false;
|
||||
w->preserve_vscroll_p = false;
|
||||
|
|
|
|||
|
|
@ -3861,7 +3861,7 @@ xic_string_conversion_callback (XIC ic, XPointer client_data,
|
|||
request.operation = TEXTCONV_RETRIEVAL;
|
||||
|
||||
/* Now perform the string conversion. */
|
||||
rc = textconv_query (f, &request);
|
||||
rc = textconv_query (f, &request, 0);
|
||||
|
||||
if (rc)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31615,7 +31615,7 @@ init_xterm (void)
|
|||
#endif
|
||||
|
||||
#ifdef HAVE_X_I18N
|
||||
register_texconv_interface (&text_conversion_interface);
|
||||
register_textconv_interface (&text_conversion_interface);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue