diff options
Diffstat (limited to 'src/androidselect.c')
-rw-r--r-- | src/androidselect.c | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/src/androidselect.c b/src/androidselect.c new file mode 100644 index 00000000000..54c712ca93b --- /dev/null +++ b/src/androidselect.c @@ -0,0 +1,499 @@ +/* Communication module for Android terminals. + +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/>. */ + +#include <config.h> +#include <assert.h> +#include <minmax.h> +#include <unistd.h> + +#include "lisp.h" +#include "blockinput.h" +#include "coding.h" +#include "android.h" +#include "androidterm.h" + +/* Selection support on Android is confined to copying and pasting of + plain text and MIME data from the clipboard. There is no primary + selection. + + While newer versions of Android are supposed to have the necessary + interfaces for transferring other kinds of selection data, doing so + is too complicated, and involves registering ``content providers'' + and all kinds of other stuff; for this reason, Emacs does not + support setting the clipboard contents to anything other than plain + text. */ + + + +/* Structure describing the EmacsClipboard class. */ + +struct android_emacs_clipboard +{ + jclass class; + jmethodID set_clipboard; + jmethodID owns_clipboard; + jmethodID clipboard_exists; + jmethodID get_clipboard; + jmethodID make_clipboard; + jmethodID get_clipboard_targets; + jmethodID get_clipboard_data; +}; + +/* Methods associated with the EmacsClipboard class. */ +static struct android_emacs_clipboard clipboard_class; + +/* Reference to the EmacsClipboard object. */ +static jobject clipboard; + + + +static void +android_init_emacs_clipboard (void) +{ + jclass old; + + clipboard_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsClipboard"); + eassert (clipboard_class.class); + + old = clipboard_class.class; + clipboard_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!clipboard_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + clipboard_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + clipboard_class.class, \ + name, signature); \ + assert (clipboard_class.c_name); + + FIND_METHOD (set_clipboard, "setClipboard", "([B)V"); + FIND_METHOD (owns_clipboard, "ownsClipboard", "()I"); + FIND_METHOD (clipboard_exists, "clipboardExists", "()Z"); + FIND_METHOD (get_clipboard, "getClipboard", "()[B"); + FIND_METHOD (get_clipboard_targets, "getClipboardTargets", + "()[[B"); + FIND_METHOD (get_clipboard_data, "getClipboardData", + "([B)[J"); + + clipboard_class.make_clipboard + = (*android_java_env)->GetStaticMethodID (android_java_env, + clipboard_class.class, + "makeClipboard", + "()Lorg/gnu/emacs/" + "EmacsClipboard;"); + assert (clipboard_class.make_clipboard); + +#undef FIND_METHOD +} + + + + +DEFUN ("android-clipboard-owner-p", Fandroid_clipboard_owner_p, + Sandroid_clipboard_owner_p, 0, 0, 0, + doc: /* Return whether or not Emacs owns the clipboard. +Alternatively, return the symbol `lambda' if that could not be +determined. */) + (void) +{ + jint rc; + + if (!android_init_gui) + error ("Accessing clipboard without display connection"); + + block_input (); + rc = (*android_java_env)->CallIntMethod (android_java_env, + clipboard, + clipboard_class.owns_clipboard); + android_exception_check (); + unblock_input (); + + /* If rc is 0 or 1, then Emacs knows whether or not it owns the + clipboard. If rc is -1, then Emacs does not. */ + + if (rc < 0) + return Qlambda; + + return rc ? Qt : Qnil; +} + +DEFUN ("android-set-clipboard", Fandroid_set_clipboard, + Sandroid_set_clipboard, 1, 1, 0, + doc: /* Set the clipboard text to STRING. */) + (Lisp_Object string) +{ + jarray bytes; + + if (!android_init_gui) + error ("Accessing clipboard without display connection"); + + CHECK_STRING (string); + string = ENCODE_UTF_8 (string); + + bytes = (*android_java_env)->NewByteArray (android_java_env, + SBYTES (string)); + android_exception_check (); + + (*android_java_env)->SetByteArrayRegion (android_java_env, bytes, + 0, SBYTES (string), + (jbyte *) SDATA (string)); + (*android_java_env)->CallVoidMethod (android_java_env, + clipboard, + clipboard_class.set_clipboard, + bytes); + android_exception_check_1 (bytes); + + ANDROID_DELETE_LOCAL_REF (bytes); + return Qnil; +} + +DEFUN ("android-get-clipboard", Fandroid_get_clipboard, + Sandroid_get_clipboard, 0, 0, 0, + doc: /* Return the current contents of the clipboard. +Value is a multibyte string containing decoded clipboard +text. +Alternatively, return nil if the clipboard is empty. */) + (void) +{ + Lisp_Object string; + jarray bytes; + jmethodID method; + size_t length; + jbyte *data; + + if (!android_init_gui) + error ("No Android display connection!"); + + method = clipboard_class.get_clipboard; + bytes + = (*android_java_env)->CallObjectMethod (android_java_env, + clipboard, + method); + android_exception_check (); + + length = (*android_java_env)->GetArrayLength (android_java_env, + bytes); + data = (*android_java_env)->GetByteArrayElements (android_java_env, + bytes, NULL); + android_exception_check_nonnull (data, bytes); + + string = make_unibyte_string ((char *) data, length); + + (*android_java_env)->ReleaseByteArrayElements (android_java_env, + bytes, data, + JNI_ABORT); + ANDROID_DELETE_LOCAL_REF (bytes); + + /* Now decode the resulting string. */ + return code_convert_string_norecord (string, Qutf_8, Qnil); +} + +DEFUN ("android-clipboard-exists-p", Fandroid_clipboard_exists_p, + Sandroid_clipboard_exists_p, 0, 0, 0, + doc: /* Return whether or not clipboard contents exist. */) + (void) +{ + jboolean rc; + jmethodID method; + + if (!android_init_gui) + error ("No Android display connection"); + + method = clipboard_class.clipboard_exists; + rc = (*android_java_env)->CallBooleanMethod (android_java_env, + clipboard, + method); + android_exception_check (); + + return rc ? Qt : Qnil; +} + +DEFUN ("android-browse-url", Fandroid_browse_url, + Sandroid_browse_url, 1, 1, 0, + doc: /* Start the system web browser. +Then, point the web browser to URL, which should be a URL-encoded +URL with a scheme specified. Signal an error upon failure. */) + (Lisp_Object url) +{ + Lisp_Object value; + + if (!android_init_gui) + error ("No Android display connection!"); + + CHECK_STRING (url); + value = android_browse_url (url); + + /* Signal an error upon failure. */ + if (!NILP (value)) + signal_error ("Error browsing URL", value); + + return Qnil; +} + + + +/* MIME clipboard support. This provides support for reading MIME + data (but not text) from the clipboard. */ + +DEFUN ("android-get-clipboard-targets", Fandroid_get_clipboard_targets, + Sandroid_get_clipboard_targets, 0, 0, 0, + doc: /* Return a list of data types in the clipboard. +Value is a list of MIME types as strings, each defining a single extra +data type available from the clipboard. */) + (void) +{ + jarray bytes_array; + jbyteArray bytes; + jmethodID method; + size_t length, length1, i; + jbyte *data; + Lisp_Object targets, tem; + + if (!android_init_gui) + error ("No Android display connection!"); + + targets = Qnil; + block_input (); + method = clipboard_class.get_clipboard_targets; + bytes_array = (*android_java_env)->CallObjectMethod (android_java_env, + clipboard, method); + android_exception_check (); + + if (!bytes_array) + goto fail; + + length = (*android_java_env)->GetArrayLength (android_java_env, + bytes_array); + for (i = 0; i < length; ++i) + { + /* Retireve the MIME type. */ + bytes + = (*android_java_env)->GetObjectArrayElement (android_java_env, + bytes_array, i); + android_exception_check_nonnull (bytes, bytes_array); + + /* Cons it onto the list of targets. */ + length1 = (*android_java_env)->GetArrayLength (android_java_env, + bytes); + data = (*android_java_env)->GetByteArrayElements (android_java_env, + bytes, NULL); + android_exception_check_nonnull_1 (data, bytes, bytes_array); + + /* Decode the string. */ + tem = make_unibyte_string ((char *) data, length1); + tem = code_convert_string_norecord (tem, Qutf_8, Qnil); + targets = Fcons (tem, targets); + + /* Delete the retrieved data. */ + (*android_java_env)->ReleaseByteArrayElements (android_java_env, + bytes, data, + JNI_ABORT); + ANDROID_DELETE_LOCAL_REF (bytes); + } + unblock_input (); + + ANDROID_DELETE_LOCAL_REF (bytes_array); + return Fnreverse (targets); + + fail: + unblock_input (); + return Qnil; +} + +/* Free the memory inside PTR, a pointer to a char pointer. */ + +static void +android_xfree_inside (void *ptr) +{ + xfree (*(char **) ptr); +} + +DEFUN ("android-get-clipboard-data", Fandroid_get_clipboard_data, + Sandroid_get_clipboard_data, 1, 1, 0, + doc: /* Return the clipboard data of the given MIME TYPE. +Value is a unibyte string containing the entire contents of the +clipboard, after its owner has converted the data to the given +MIME type. Value is nil if the conversion fails, or if the data +is not present. + +Value is also nil if the clipboard data consists of a single URL which +does not have any corresponding data. In that case, use +`android-get-clipboard' instead. */) + (Lisp_Object type) +{ + jlongArray array; + jbyteArray bytes; + jmethodID method; + int fd; + ptrdiff_t rc; + jlong offset, length, *longs; + specpdl_ref ref; + char *buffer, *start; + + if (!android_init_gui) + error ("No Android display connection!"); + + /* Encode the string as UTF-8. */ + CHECK_STRING (type); + type = ENCODE_UTF_8 (type); + + /* Then give it to the selection code. */ + block_input (); + bytes = (*android_java_env)->NewByteArray (android_java_env, + SBYTES (type)); + (*android_java_env)->SetByteArrayRegion (android_java_env, bytes, + 0, SBYTES (type), + (jbyte *) SDATA (type)); + android_exception_check (); + + method = clipboard_class.get_clipboard_data; + array = (*android_java_env)->CallObjectMethod (android_java_env, + clipboard, method, + bytes); + android_exception_check_1 (bytes); + ANDROID_DELETE_LOCAL_REF (bytes); + + if (!array) + goto fail; + + longs = (*android_java_env)->GetLongArrayElements (android_java_env, + array, NULL); + android_exception_check_nonnull (longs, array); + + /* longs[0] is the file descriptor. + longs[1] is an offset to apply to the file. + longs[2] is either -1, or the number of bytes to read from the + file. */ + fd = longs[0]; + offset = longs[1]; + length = longs[2]; + + (*android_java_env)->ReleaseLongArrayElements (android_java_env, + array, longs, + JNI_ABORT); + ANDROID_DELETE_LOCAL_REF (array); + unblock_input (); + + /* Now begin reading from longs[0]. */ + ref = SPECPDL_INDEX (); + record_unwind_protect_int (close_file_unwind, fd); + + if (length != -1) + { + buffer = xmalloc (MIN (length, PTRDIFF_MAX)); + record_unwind_protect_ptr (xfree, buffer); + + rc = emacs_read_quit (fd, buffer, + MIN (length, PTRDIFF_MAX)); + + /* Return nil upon an IO problem. */ + if (rc < 0) + return unbind_to (ref, Qnil); + + /* Return the data as a unibyte string. */ + return unbind_to (ref, make_unibyte_string (buffer, rc)); + } + + /* Otherwise, read BUFSIZ bytes at a time. */ + buffer = xmalloc (BUFSIZ); + length = 0; + start = buffer; + + record_unwind_protect_ptr (android_xfree_inside, &buffer); + + /* Seek to the start of the data. */ + + if (offset) + { + if (lseek (fd, offset, SEEK_SET) < 0) + return unbind_to (ref, Qnil); + } + + while (true) + { + rc = emacs_read_quit (fd, start, BUFSIZ); + + if (!INT_ADD_OK (rc, length, &length) + || PTRDIFF_MAX - length < BUFSIZ) + memory_full (PTRDIFF_MAX); + + if (rc < 0) + return unbind_to (ref, Qnil); + + if (rc < BUFSIZ) + break; + + buffer = xrealloc (buffer, length + BUFSIZ); + start = buffer + length; + } + + return unbind_to (ref, make_unibyte_string (buffer, rc)); + + fail: + unblock_input (); + return Qnil; +} + + + +void +init_androidselect (void) +{ + jobject tem; + jmethodID make_clipboard; + + if (!android_init_gui) + return; + + android_init_emacs_clipboard (); + + make_clipboard = clipboard_class.make_clipboard; + tem + = (*android_java_env)->CallStaticObjectMethod (android_java_env, + clipboard_class.class, + make_clipboard); + if (!tem) + emacs_abort (); + + clipboard = (*android_java_env)->NewGlobalRef (android_java_env, tem); + + if (!clipboard) + emacs_abort (); + + ANDROID_DELETE_LOCAL_REF (tem); +} + +void +syms_of_androidselect (void) +{ + defsubr (&Sandroid_clipboard_owner_p); + defsubr (&Sandroid_set_clipboard); + defsubr (&Sandroid_get_clipboard); + defsubr (&Sandroid_clipboard_exists_p); + defsubr (&Sandroid_browse_url); + defsubr (&Sandroid_get_clipboard_targets); + defsubr (&Sandroid_get_clipboard_data); +} |