@code{android.permission.TRANSMIT_IR}
@item
@code{android.permission.WAKE_LOCK}
+@item
+@code{android.permission.POST_NOTIFICATIONS}
@end itemize
Other permissions must be granted by the user through the system
--- /dev/null
+/* 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;
+ }
+}
--- /dev/null
+/* 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.InputMethodManager;
+import android.view.inputmethod.SurroundingText;
+import android.view.KeyEvent;
+
+import android.text.Editable;
+
+import android.util.Log;
+
+/* Android input methods, take number six.
+
+ See EmacsEditable for more details. */
+
+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;
+
+ public
+ EmacsInputConnection (EmacsView view)
+ {
+ super (view, false);
+ this.view = view;
+ this.editable = new EmacsEditable (this);
+ }
+
+ @Override
+ public Editable
+ getEditable ()
+ {
+ return editable;
+ }
+
+ @Override
+ public boolean
+ setComposingText (CharSequence text, int newCursorPosition)
+ {
+ editable.compositionStart ();
+ super.setComposingText (text, newCursorPosition);
+ return true;
+ }
+
+ @Override
+ 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 + "\"");
+
+ 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 ()
+ {
+ switch (currentLargeOffset)
+ {
+ case 0:
+ currentLargeOffset = 100000;
+ return 100000;
+
+ case 100000:
+ currentLargeOffset = 30000;
+ return 30000;
+
+ case 30000:
+ currentLargeOffset = 0;
+ return 0;
+ }
+
+ currentLargeOffset = 0;
+ return -1;
+ }
+}
public class EmacsNative
{
+ /* List of native libraries that must be loaded during class
+ initialization. */
+ private static final String[] libraryDeps;
+
/* Obtain the fingerprint of this build of Emacs. The fingerprint
can be used to determine the dump file name. */
public static native String getFingerprint ();
Every time you add a new shared library dependency to Emacs,
please add it here as well. */
- try
- {
- System.loadLibrary ("png_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("selinux_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("crypto_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("pcre_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("packagelistparser_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("gnutls_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("gmp_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("nettle_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("p11-kit_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("tasn1_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("hogweed_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("jansson_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("jpeg_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("tiff_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("xml2_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
- }
-
- try
- {
- System.loadLibrary ("icuuc_emacs");
- }
- catch (UnsatisfiedLinkError exception)
- {
- /* Ignore this exception. */
+ libraryDeps = new String[] { "png_emacs", "selinux_emacs",
+ "crypto_emacs", "pcre_emacs",
+ "packagelistparser_emacs",
+ "gnutls_emacs", "gmp_emacs",
+ "nettle_emacs", "p11-kit_emacs",
+ "tasn1_emacs", "hogweed_emacs",
+ "jansson_emacs", "jpeg_emacs",
+ "tiff_emacs", "xml2_emacs",
+ "icuuc_emacs", };
+
+ for (String dependency : libraryDeps)
+ {
+ try
+ {
+ System.loadLibrary (dependency);
+ }
+ catch (UnsatisfiedLinkError exception)
+ {
+ /* Ignore this exception. */
+ }
}
System.loadLibrary ("emacs");
private long lastClipSerial;
/* The InputMethodManager for this view's context. */
- private InputMethodManager imManager;
+ public InputMethodManager imManager;
/* Whether or not this view is attached to a window. */
public boolean isAttachedToWindow;
box that obscures Emacs. */
info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
- /* But don't return an InputConnection, in order to force the on
- screen keyboard to work correctly. */
+ /* Set a reasonable inputType. */
+ info.inputType = InputType.TYPE_NULL;
+
return null;
}
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
import android.content.Context;
/* The button state and keyboard modifier mask at the time of the
last button press or release event. */
- private int lastButtonState, lastModifiers;
+ public int lastButtonState, lastModifiers;
/* Whether or not the window is mapped, and whether or not it is
deiconified. */
to quit Emacs. */
private long lastVolumeButtonRelease;
+ /* Linked list of character strings which were recently sent as
+ events. */
+ public LinkedHashMap<Integer, String> eventStrings;
+
public
EmacsWindow (short handle, final EmacsWindow parent, int x, int y,
int width, int height, boolean overrideRedirect)
}
scratchGC = new EmacsGC ((short) 0);
+
+ /* Create the map of input method-committed strings. Keep at most
+ ten strings in the map. */
+
+ eventStrings
+ = new LinkedHashMap<Integer, String> () {
+ @Override
+ protected boolean
+ removeEldestEntry (Map.Entry<Integer, String> entry)
+ {
+ return size () > 10;
+ }
+ };
}
public void
return view.getBitmap ();
}
+ /* event.getCharacters is used because older input methods still
+ require it. */
+ @SuppressWarnings ("deprecation")
+ public int
+ getEventUnicodeChar (KeyEvent event, int state)
+ {
+ String characters;
+
+ if (event.getUnicodeChar (state) != 0)
+ return event.getUnicodeChar (state);
+
+ characters = event.getCharacters ();
+
+ if (characters != null && characters.length () == 1)
+ return characters.charAt (0);
+
+ return characters == null ? 0 : -1;
+ }
+
+ public void
+ saveUnicodeString (int serial, String string)
+ {
+ eventStrings.put (serial, string);
+ }
+
+ /* event.getCharacters is used because older input methods still
+ require it. */
+ @SuppressWarnings ("deprecation")
public void
onKeyDown (int keyCode, KeyEvent event)
{
int state, state_1;
+ long serial;
+ String characters;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
state = event.getModifiers ();
state_1
= state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK);
- EmacsNative.sendKeyPress (this.handle,
- event.getEventTime (),
- state, keyCode,
- event.getUnicodeChar (state_1));
- lastModifiers = state;
+ synchronized (eventStrings)
+ {
+ serial
+ = EmacsNative.sendKeyPress (this.handle,
+ event.getEventTime (),
+ state, keyCode,
+ getEventUnicodeChar (event,
+ state_1));
+ lastModifiers = state;
+
+ characters = event.getCharacters ();
+
+ if (characters != null && characters.length () > 1)
+ saveUnicodeString ((int) serial, characters);
+ }
}
public void
onKeyUp (int keyCode, KeyEvent event)
{
int state, state_1;
- long time;
+ long time, serial;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
state = event.getModifiers ();
state_1
= state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK);
- EmacsNative.sendKeyRelease (this.handle,
- event.getEventTime (),
- state, keyCode,
- event.getUnicodeChar (state_1));
+ serial
+ = EmacsNative.sendKeyRelease (this.handle,
+ event.getEventTime (),
+ state, keyCode,
+ getEventUnicodeChar (event,
+ state_1));
lastModifiers = state;
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
}
});
}
+
+ public String
+ lookupString (int eventSerial)
+ {
+ String any;
+
+ synchronized (eventStrings)
+ {
+ any = eventStrings.remove (eventSerial);
+ }
+
+ return any;
+ }
};
(android-set-clipboard string))
((eq type 'PRIMARY)
(setq android-primary-selection string)))))
+\f
+;;; Character composition display.
+
+(defvar android-preedit-overlay nil
+ "The overlay currently used to display preedit text from a compose sequence.")
+
+;; With some input methods, text gets inserted before Emacs is told to
+;; remove any preedit text that was displayed, which causes both the
+;; preedit overlay and the text to be visible for a brief period of
+;; time. This pre-command-hook clears the overlay before any command
+;; and should be set whenever a preedit overlay is visible.
+(defun android-clear-preedit-text ()
+ "Clear the pre-edit overlay and remove itself from pre-command-hook.
+This function should be installed in `pre-command-hook' whenever
+preedit text is displayed."
+ (when android-preedit-overlay
+ (delete-overlay android-preedit-overlay)
+ (setq android-preedit-overlay nil))
+ (remove-hook 'pre-command-hook #'android-clear-preedit-text))
+
+(defun android-preedit-text (event)
+ "Display preedit text from a compose sequence in EVENT.
+EVENT is a preedit-text event."
+ (interactive "e")
+ (when android-preedit-overlay
+ (delete-overlay android-preedit-overlay)
+ (setq android-preedit-overlay nil)
+ (remove-hook 'pre-command-hook #'android-clear-preedit-text))
+ (when (nth 1 event)
+ (let ((string (propertize (nth 1 event) 'face '(:underline t))))
+ (setq android-preedit-overlay (make-overlay (point) (point)))
+ (add-hook 'pre-command-hook #'android-clear-preedit-text)
+ (overlay-put android-preedit-overlay 'window (selected-window))
+ (overlay-put android-preedit-overlay 'before-string string))))
+
+(define-key special-event-map [preedit-text] 'android-preedit-text)
(provide 'android-win)
;; android-win.el ends here.
jclass class;
jmethodID swap_buffers;
jmethodID toggle_on_screen_keyboard;
+ jmethodID lookup_string;
};
/* The API level of the current device. */
FIND_METHOD (swap_buffers, "swapBuffers", "()V");
FIND_METHOD (toggle_on_screen_keyboard,
"toggleOnScreenKeyboard", "(Z)V");
+ FIND_METHOD (lookup_string, "lookupString", "(I)Ljava/lang/String;");
#undef FIND_METHOD
}
android_exception_check ();
}
+int
+android_wc_lookup_string (android_key_pressed_event *event,
+ wchar_t *buffer_return, int wchars_buffer,
+ int *keysym_return,
+ enum android_lookup_status *status_return)
+{
+ enum android_lookup_status status;
+ int rc;
+ jobject window, string;
+ const jchar *characters;
+ jsize size;
+ size_t i;
+
+ status = ANDROID_LOOKUP_NONE;
+ rc = 0;
+
+ /* See if an actual lookup has to be made. Note that while
+ BUFFER_RETURN is wchar_t, the returned characters are always in
+ UCS. */
+
+ if (event->unicode_char != (uint32_t) -1)
+ {
+ if (event->unicode_char)
+ {
+ if (wchars_buffer < 1)
+ {
+ *status_return = ANDROID_BUFFER_OVERFLOW;
+ return 0;
+ }
+ else
+ {
+ buffer_return[0] = event->unicode_char;
+ status = ANDROID_LOOKUP_CHARS;
+ rc = 1;
+ }
+ }
+
+ *keysym_return = event->keycode;
+
+ if (status == ANDROID_LOOKUP_CHARS)
+ status = ANDROID_LOOKUP_BOTH;
+ else
+ {
+ status = ANDROID_LOOKUP_KEYSYM;
+ rc = 0;
+ }
+
+ *status_return = status;
+
+ return rc;
+ }
+
+ /* Now look up the window. */
+ rc = 0;
+
+ if (!android_handles[event->window].handle
+ || (android_handles[event->window].type
+ != ANDROID_HANDLE_WINDOW))
+ status = ANDROID_LOOKUP_NONE;
+ else
+ {
+ window = android_handles[event->window].handle;
+ string
+ = (*android_java_env)->CallObjectMethod (android_java_env, window,
+ window_class.lookup_string,
+ (jint) event->serial);
+ android_exception_check ();
+
+ if (!string)
+ status = ANDROID_LOOKUP_NONE;
+ else
+ {
+ /* Now return this input method string. */
+ characters = (*android_java_env)->GetStringChars (android_java_env,
+ string, NULL);
+ android_exception_check ();
+
+ /* Figure out how big the string is. */
+ size = (*android_java_env)->GetStringLength (android_java_env,
+ string);
+
+ /* Copy over the string data. */
+ for (i = 0; i < MIN ((unsigned int) wchars_buffer, size); ++i)
+ buffer_return[i] = characters[i];
+
+ if (i < size)
+ status = ANDROID_BUFFER_OVERFLOW;
+ else
+ status = ANDROID_LOOKUP_CHARS;
+
+ /* Return the number of characters that should have been
+ written. */
+
+ if (size > INT_MAX)
+ rc = INT_MAX;
+ else
+ rc = size;
+
+ (*android_java_env)->ReleaseStringChars (android_java_env, string,
+ characters);
+ android_exception_check ();
+ ANDROID_DELETE_LOCAL_REF (string);
+ }
+ }
+
+ *status_return = status;
+ return rc;
+}
+
\f
/* Low level drawing primitives. */
#ifndef ANDROID_STUBIFY
/* Universal NULL handle. */
-static const int ANDROID_NONE;
+static const int ANDROID_NONE, ANDROID_NO_SYMBOL;
/* Keep these as conceptually close to X as possible: that makes
synchronizing code between the ports much easier. */
android_time time;
unsigned int state;
unsigned int keycode;
+
+ /* If this field is -1, then android_lookup_string should be called
+ to retrieve the associated individual characters. */
unsigned int unicode_char;
};
+typedef struct android_key_event android_key_pressed_event;
+
/* These hard coded values are Android modifier keycodes derived
through experimentation. */
ANDROID_CURRENT_TIME = 0L,
};
+enum android_lookup_status
+ {
+ ANDROID_BUFFER_OVERFLOW,
+ ANDROID_LOOKUP_NONE,
+ ANDROID_LOOKUP_CHARS,
+ ANDROID_LOOKUP_KEYSYM,
+ ANDROID_LOOKUP_BOTH,
+ };
+
extern int android_pending (void);
extern void android_next_event (union android_event *);
int, int *, int *);
extern void android_sync (void);
+extern int android_wc_lookup_string (android_key_pressed_event *,
+ wchar_t *, int, int *,
+ enum android_lookup_status *);
+
#endif
\f
Lisp_Object window;
int scroll_height;
double scroll_unit;
+ int keysym;
+ ptrdiff_t nchars, i;
/* It is okay for this to not resemble handle_one_xevent so much.
Differences in event handling code are much less nasty than
hlinfo = &dpyinfo->mouse_highlight;
*finish = ANDROID_EVENT_NORMAL;
any = android_window_to_frame (dpyinfo, event->xany.window);
+ nchars = 0;
if (any && any->wait_event_type == event->type)
any->wait_event_type = 0; /* Indicates we got it. */
android_flush_dirty_back_buffer_on (mouse_frame);
}
+ if (!f)
+ goto OTHER;
+
+ wchar_t copy_buffer[129];
+ wchar_t *copy_bufptr = copy_buffer;
+ int copy_bufsiz = 128 * sizeof (wchar_t);
+
event->xkey.state
|= android_emacs_to_android_modifiers (dpyinfo,
extra_keyboard_modifiers);
= android_android_to_emacs_modifiers (dpyinfo, modifiers);
inev.ie.timestamp = event->xkey.time;
- /* First deal with keysyms which have defined translations to
- characters. */
+ keysym = event->xkey.keycode;
+
+ {
+ enum android_lookup_status status_return;
+
+ nchars = android_wc_lookup_string (&event->xkey, copy_bufptr,
+ copy_bufsiz, &keysym,
+ &status_return);
+
+ /* android_lookup_string can't be called twice, so there's no
+ way to recover from buffer overflow. */
+ if (status_return == ANDROID_BUFFER_OVERFLOW)
+ goto done_keysym;
+ else if (status_return == ANDROID_LOOKUP_NONE)
+ {
+ /* Don't skip preedit text events. */
+ if (event->xkey.keycode != (uint32_t) -1)
+ goto done_keysym;
+ }
+ else if (status_return == ANDROID_LOOKUP_CHARS)
+ keysym = ANDROID_NO_SYMBOL;
+ else if (status_return != ANDROID_LOOKUP_KEYSYM
+ && status_return != ANDROID_LOOKUP_BOTH)
+ emacs_abort ();
+
+ /* Deal with pre-edit text events. On Android, these are
+ simply encoded as events with associated strings and a
+ keycode set to ``-1''. */
+
+ if (event->xkey.keycode == (uint32_t) -1)
+ {
+ inev.ie.kind = PREEDIT_TEXT_EVENT;
+ inev.ie.arg = Qnil;
+
+ /* If text was looked up, decode it and make it the
+ preedit text. */
+
+ if (status_return == ANDROID_LOOKUP_CHARS && nchars)
+ {
+ copy_bufptr[nchars] = 0;
+ inev.ie.arg = from_unicode_buffer (copy_bufptr);
+ }
- if (event->xkey.unicode_char >= 32
- && event->xkey.unicode_char < 128)
+ goto done_keysym;
+ }
+ }
+
+ if (nchars == 1 && copy_bufptr[0] >= 32)
{
- inev.ie.kind = ASCII_KEYSTROKE_EVENT;
- inev.ie.code = event->xkey.unicode_char;
+ /* Deal with characters. */
+
+ if (copy_bufptr[0] < 128)
+ inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+ else
+ inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
+
+ inev.ie.code = copy_bufptr[0];
}
- else if (event->xkey.unicode_char < 32)
+ else if (nchars < 2 && keysym)
{
/* If the key is a modifier key, just return. */
- if (ANDROID_IS_MODIFIER_KEY (event->xkey.keycode))
+ if (ANDROID_IS_MODIFIER_KEY (keysym))
goto done_keysym;
/* Next, deal with special ``characters'' by giving the
keycode to keyboard.c. */
inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
- inev.ie.code = event->xkey.keycode;
+ inev.ie.code = keysym;
}
else
{
- /* Finally, deal with Unicode characters. */
- inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
- inev.ie.code = event->xkey.unicode_char;
+ /* Finally, deal with strings. */
+
+ for (i = 0; i < nchars; ++i)
+ {
+ inev.ie.kind = (SINGLE_BYTE_CHAR_P (copy_bufptr[i])
+ ? ASCII_KEYSTROKE_EVENT
+ : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+ inev.ie.code = copy_bufptr[i];
+
+ /* If the character is actually '\n', then change this
+ to RET. */
+
+ if (copy_bufptr[i] == '\n')
+ {
+ inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+ inev.ie.code = 66;
+ }
+
+ kbd_buffer_store_buffered_event (&inev, hold_quit);
+ }
+
+ count += nchars;
+ inev.ie.kind = NO_EVENT; /* Already stored above. */
}
goto done_keysym;
return CODING_ID_NAME (id);
}
-#if defined (WINDOWSNT) || defined (CYGWIN)
+#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
Lisp_Object
from_unicode (Lisp_Object str)
Lisp_Object
from_unicode_buffer (const wchar_t *wstr)
{
- /* We get one of the two final null bytes for free. */
- ptrdiff_t len = 1 + sizeof (wchar_t) * wcslen (wstr);
- AUTO_STRING_WITH_LEN (str, (char *) wstr, len);
- return from_unicode (str);
+ if (sizeof (wchar_t) == 2)
+ {
+ /* We get one of the two final null bytes for free. */
+ ptrdiff_t len = 1 + sizeof (wchar_t) * wcslen (wstr);
+ AUTO_STRING_WITH_LEN (str, (char *) wstr, len);
+ return from_unicode (str);
+ }
+ else
+ {
+ /* This code is used only on Android, where little endian UTF-16
+ strings are extended to 32-bit wchar_t. */
+
+ uint16_t *words;
+ size_t length, i;
+
+ length = wcslen (wstr) + 1;
+
+ USE_SAFE_ALLOCA;
+ SAFE_NALLOCA (words, sizeof *words, length);
+
+ for (i = 0; i < length - 1; ++i)
+ words[i] = wstr[i];
+
+ words[i] = '\0';
+ AUTO_STRING_WITH_LEN (str, (char *) words,
+ (length - 1) * sizeof *words);
+ return unbind_to (sa_count, from_unicode (str));
+ }
}
wchar_t *
return WCSDATA (*buf);
}
-#endif /* WINDOWSNT || CYGWIN */
+#endif /* WINDOWSNT || CYGWIN || HAVE_ANDROID */
\f
/*** 8. Emacs Lisp library functions ***/
/* Defined in this file. */
INLINE int surrogates_to_codepoint (int, int);
-#if defined (WINDOWSNT) || defined (CYGWIN)
+#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID
/* These functions use Lisp string objects to store the UTF-16LE
strings that modern versions of Windows expect. These strings are
/* Convert WSTR to an Emacs string. */
extern Lisp_Object from_unicode_buffer (const wchar_t *wstr);
-#endif /* WINDOWSNT || CYGWIN */
+#endif /* WINDOWSNT || CYGWIN || HAVE_ANDROID */
/* Macros for backward compatibility. */
coordinates, and incrementing the Y axis by SFNT_POLY_STEP instead
of 1. SFNT_POLY_STEP is chosen to always keep Y aligned to a grid
placed such that there are always 1 << SFNT_POLY_SHIFT positions
- available for each integral pixel coordinate.
-
- Moving upwards is performed using Bresenham's algorithm. Prior to
- an edge being created, a single slope error is computed. This is
- how much X should increase for each increase in Y.
-
- Upon each increase in Y, X initially does not move. Instead, the
- ``slope error'' is accumulated; once it exceeds the sample size,
- one sample is subtracted from the accumulator, and X is increased
- by one step. */
+ available for each integral pixel coordinate. */
static void
sfnt_poly_edges (struct sfnt_edge *edges, size_t size,