Files
emacs/java/org/gnu/emacs/EmacsInputConnection.java
Po Lu 189a91bfb6 Update Android port
* java/org/gnu/emacs/EmacsInputConnection.java
(EmacsInputConnection): Apply workarounds on Vivo devices as
well.
* src/android.c (sendKeyPress, sendKeyRelease): Clear counter.
* src/androidgui.h (struct android_key_event): New field
`counter'.
* src/androidterm.c (handle_one_android_event): Generate
barriers as appropriate.
(JNICALL): Set `counter'.
* src/frame.h (enum text_conversion_operation):
* src/textconv.c (detect_conversion_events)
(really_set_composing_text, handle_pending_conversion_events_1)
(handle_pending_conversion_events, textconv_barrier):
* src/textconv.h: Implement text conversion barriers and fix
various typos.
2023-06-02 13:31:40 +08:00

411 lines
10 KiB
Java
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.TextSnapshot;
import android.view.KeyEvent;
import android.os.Build;
import android.util.Log;
/* Android input methods, take number six. See textconv.c for more
details; this is more-or-less a thin wrapper around that file. */
public final class EmacsInputConnection extends BaseInputConnection
{
private static final String TAG = "EmacsInputConnection";
private EmacsView view;
private short windowHandle;
/* Whether or not to synchronize and call `updateIC' with the
selection position after committing text.
This helps with on screen keyboard programs found in some vendor
versions of Android, which rely on immediate updates to the point
position after text is commited in order to place the cursor
within that text. */
private static boolean syncAfterCommit;
/* Whether or not to return empty text with the offset set to zero
if a request arrives that has no flags set and has requested no
characters at all.
This is necessary with on screen keyboard programs found in some
vendor versions of Android which don't rely on the documented
meaning of `ExtractedText.startOffset', and instead take the
selection offset inside at face value. */
private static boolean extractAbsoluteOffsets;
static
{
if (Build.MANUFACTURER.equalsIgnoreCase ("Huawei")
|| Build.MANUFACTURER.equalsIgnoreCase ("Honor"))
extractAbsoluteOffsets = syncAfterCommit = true;
/* The Samsung and Vivo keyboards take `selectionStart' at face
value if some text is returned, and also searches for words
solely within that text. However, when no text is returned, it
falls back to getTextAfterCursor and getTextBeforeCursor. */
if (Build.MANUFACTURER.equalsIgnoreCase ("Samsung")
|| Build.MANUFACTURER.equalsIgnoreCase ("Vivo"))
extractAbsoluteOffsets = true;
};
public
EmacsInputConnection (EmacsView view)
{
super (view, true);
this.view = view;
this.windowHandle = view.window.handle;
}
@Override
public boolean
beginBatchEdit ()
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "beginBatchEdit");
EmacsNative.beginBatchEdit (windowHandle);
return true;
}
@Override
public boolean
endBatchEdit ()
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "endBatchEdit");
EmacsNative.endBatchEdit (windowHandle);
return true;
}
@Override
public boolean
commitCompletion (CompletionInfo info)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "commitCompletion: " + info);
EmacsNative.commitCompletion (windowHandle,
info.getText ().toString (),
info.getPosition ());
return true;
}
@Override
public boolean
commitText (CharSequence text, int newCursorPosition)
{
int[] selection;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
EmacsNative.commitText (windowHandle, text.toString (),
newCursorPosition);
if (syncAfterCommit)
{
/* Synchronize with the Emacs thread, obtain the new
selection, and report it immediately. */
selection = EmacsNative.getSelection (windowHandle);
if (EmacsService.DEBUG_IC && selection != null)
Log.d (TAG, "commitText: new selection is " + selection[0]
+ ", by " + selection[1]);
if (selection != null)
/* N.B. that the composing region is removed after text is
committed. */
view.imManager.updateSelection (view, selection[0],
selection[1], -1, -1);
}
return true;
}
@Override
public boolean
deleteSurroundingText (int leftLength, int rightLength)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("deleteSurroundingText: "
+ leftLength + " " + rightLength));
EmacsNative.deleteSurroundingText (windowHandle, leftLength,
rightLength);
return true;
}
@Override
public boolean
finishComposingText ()
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "finishComposingText");
EmacsNative.finishComposingText (windowHandle);
return true;
}
@Override
public String
getSelectedText (int flags)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getSelectedText: " + flags);
return EmacsNative.getSelectedText (windowHandle, flags);
}
@Override
public String
getTextAfterCursor (int length, int flags)
{
String string;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
string = EmacsNative.getTextAfterCursor (windowHandle, length,
flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, " --> " + string);
return string;
}
@Override
public String
getTextBeforeCursor (int length, int flags)
{
String string;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
string = EmacsNative.getTextBeforeCursor (windowHandle, length,
flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, " --> " + string);
return string;
}
@Override
public boolean
setComposingText (CharSequence text, int newCursorPosition)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("setComposingText: "
+ text + " ## " + newCursorPosition));
EmacsNative.setComposingText (windowHandle, text.toString (),
newCursorPosition);
return true;
}
@Override
public boolean
setComposingRegion (int start, int end)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "setComposingRegion: " + start + " " + end);
EmacsNative.setComposingRegion (windowHandle, start, end);
return true;
}
@Override
public boolean
performEditorAction (int editorAction)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "performEditorAction: " + editorAction);
EmacsNative.performEditorAction (windowHandle, editorAction);
return true;
}
@Override
public boolean
performContextMenuAction (int contextMenuAction)
{
int action;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "performContextMenuAction: " + contextMenuAction);
/* Translate the action in Java code. That way, a great deal of
JNI boilerplate can be avoided. */
switch (contextMenuAction)
{
case android.R.id.selectAll:
action = 0;
break;
case android.R.id.startSelectingText:
action = 1;
break;
case android.R.id.stopSelectingText:
action = 2;
break;
case android.R.id.cut:
action = 3;
break;
case android.R.id.copy:
action = 4;
break;
case android.R.id.paste:
action = 5;
break;
default:
return true;
}
EmacsNative.performContextMenuAction (windowHandle, action);
return true;
}
@Override
public ExtractedText
getExtractedText (ExtractedTextRequest request, int flags)
{
ExtractedText text;
int[] selection;
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getExtractedText: " + request.hintMaxChars + ", "
+ request.hintMaxLines + " " + flags);
/* If a request arrives with hintMaxChars, hintMaxLines and flags
set to 0, and the system is known to be buggy, return an empty
extracted text object with the absolute selection positions. */
if (extractAbsoluteOffsets
&& request.hintMaxChars == 0
&& request.hintMaxLines == 0
&& flags == 0)
{
/* Obtain the selection. */
selection = EmacsNative.getSelection (windowHandle);
if (selection == null)
return null;
/* Create the workaround extracted text. */
text = new ExtractedText ();
text.partialStartOffset = -1;
text.partialEndOffset = -1;
text.text = "";
text.selectionStart = selection[0];
text.selectionEnd = selection[1];
}
else
text = EmacsNative.getExtractedText (windowHandle, request,
flags);
if (text == null)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getExtractedText: text is NULL");
return null;
}
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getExtractedText: " + text.text + " @"
+ text.startOffset + ":" + text.selectionStart
+ ", " + text.selectionEnd);
return text;
}
@Override
public boolean
setSelection (int start, int end)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "setSelection: " + start + " " + end);
EmacsNative.setSelection (windowHandle, start, end);
return true;
}
@Override
public boolean
sendKeyEvent (KeyEvent key)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "sendKeyEvent: " + key);
return super.sendKeyEvent (key);
}
@Override
public boolean
deleteSurroundingTextInCodePoints (int beforeLength, int afterLength)
{
/* This can be implemented the same way as
deleteSurroundingText. */
return this.deleteSurroundingText (beforeLength, afterLength);
}
@Override
public boolean
requestCursorUpdates (int cursorUpdateMode)
{
if (EmacsService.DEBUG_IC)
Log.d (TAG, "requestCursorUpdates: " + cursorUpdateMode);
EmacsNative.requestCursorUpdates (windowHandle, cursorUpdateMode);
return true;
}
/* Override functions which are not implemented. */
@Override
public TextSnapshot
takeSnapshot ()
{
Log.d (TAG, "takeSnapshot");
return null;
}
}