root/src/androidselect.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. android_init_emacs_clipboard
  2. DEFUN
  3. DEFUN
  4. DEFUN
  5. DEFUN
  6. DEFUN
  7. android_xfree_inside
  8. DEFUN
  9. init_androidselect
  10. syms_of_androidselect

     1 /* Communication module for Android terminals.
     2 
     3 Copyright (C) 2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 #include <config.h>
    21 #include <assert.h>
    22 #include <minmax.h>
    23 #include <unistd.h>
    24 
    25 #include "lisp.h"
    26 #include "blockinput.h"
    27 #include "coding.h"
    28 #include "android.h"
    29 #include "androidterm.h"
    30 
    31 /* Selection support on Android is confined to copying and pasting of
    32    plain text and MIME data from the clipboard.  There is no primary
    33    selection.
    34 
    35    While newer versions of Android are supposed to have the necessary
    36    interfaces for transferring other kinds of selection data, doing so
    37    is too complicated, and involves registering ``content providers''
    38    and all kinds of other stuff; for this reason, Emacs does not
    39    support setting the clipboard contents to anything other than plain
    40    text.  */
    41 
    42 
    43 
    44 /* Structure describing the EmacsClipboard class.  */
    45 
    46 struct android_emacs_clipboard
    47 {
    48   jclass class;
    49   jmethodID set_clipboard;
    50   jmethodID owns_clipboard;
    51   jmethodID clipboard_exists;
    52   jmethodID get_clipboard;
    53   jmethodID make_clipboard;
    54   jmethodID get_clipboard_targets;
    55   jmethodID get_clipboard_data;
    56 };
    57 
    58 /* Methods associated with the EmacsClipboard class.  */
    59 static struct android_emacs_clipboard clipboard_class;
    60 
    61 /* Reference to the EmacsClipboard object.  */
    62 static jobject clipboard;
    63 
    64 
    65 
    66 static void
    67 android_init_emacs_clipboard (void)
    68 {
    69   jclass old;
    70 
    71   clipboard_class.class
    72     = (*android_java_env)->FindClass (android_java_env,
    73                                       "org/gnu/emacs/EmacsClipboard");
    74   eassert (clipboard_class.class);
    75 
    76   old = clipboard_class.class;
    77   clipboard_class.class
    78     = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
    79                                                   old);
    80   ANDROID_DELETE_LOCAL_REF (old);
    81 
    82   if (!clipboard_class.class)
    83     emacs_abort ();
    84 
    85 #define FIND_METHOD(c_name, name, signature)                    \
    86   clipboard_class.c_name                                        \
    87     = (*android_java_env)->GetMethodID (android_java_env,       \
    88                                         clipboard_class.class,  \
    89                                         name, signature);       \
    90   assert (clipboard_class.c_name);
    91 
    92   FIND_METHOD (set_clipboard, "setClipboard", "([B)V");
    93   FIND_METHOD (owns_clipboard, "ownsClipboard", "()I");
    94   FIND_METHOD (clipboard_exists, "clipboardExists", "()Z");
    95   FIND_METHOD (get_clipboard, "getClipboard", "()[B");
    96   FIND_METHOD (get_clipboard_targets, "getClipboardTargets",
    97                "()[[B");
    98   FIND_METHOD (get_clipboard_data, "getClipboardData",
    99                "([B)[J");
   100 
   101   clipboard_class.make_clipboard
   102     = (*android_java_env)->GetStaticMethodID (android_java_env,
   103                                               clipboard_class.class,
   104                                               "makeClipboard",
   105                                               "()Lorg/gnu/emacs/"
   106                                               "EmacsClipboard;");
   107   assert (clipboard_class.make_clipboard);
   108 
   109 #undef FIND_METHOD
   110 }
   111 
   112 
   113 
   114 
   115 DEFUN ("android-clipboard-owner-p", Fandroid_clipboard_owner_p,
   116        Sandroid_clipboard_owner_p, 0, 0, 0,
   117        doc: /* Return whether or not Emacs owns the clipboard.
   118 Alternatively, return the symbol `lambda' if that could not be
   119 determined.  */)
   120   (void)
   121 {
   122   jint rc;
   123 
   124   if (!android_init_gui)
   125     error ("Accessing clipboard without display connection");
   126 
   127   block_input ();
   128   rc = (*android_java_env)->CallIntMethod (android_java_env,
   129                                            clipboard,
   130                                            clipboard_class.owns_clipboard);
   131   android_exception_check ();
   132   unblock_input ();
   133 
   134   /* If rc is 0 or 1, then Emacs knows whether or not it owns the
   135      clipboard.  If rc is -1, then Emacs does not.  */
   136 
   137   if (rc < 0)
   138     return Qlambda;
   139 
   140   return rc ? Qt : Qnil;
   141 }
   142 
   143 DEFUN ("android-set-clipboard", Fandroid_set_clipboard,
   144        Sandroid_set_clipboard, 1, 1, 0,
   145        doc: /* Set the clipboard text to STRING.  */)
   146   (Lisp_Object string)
   147 {
   148   jarray bytes;
   149 
   150   if (!android_init_gui)
   151     error ("Accessing clipboard without display connection");
   152 
   153   CHECK_STRING (string);
   154   string = ENCODE_UTF_8 (string);
   155 
   156   bytes = (*android_java_env)->NewByteArray (android_java_env,
   157                                              SBYTES (string));
   158   android_exception_check ();
   159 
   160   (*android_java_env)->SetByteArrayRegion (android_java_env, bytes,
   161                                            0, SBYTES (string),
   162                                            (jbyte *) SDATA (string));
   163   (*android_java_env)->CallVoidMethod (android_java_env,
   164                                        clipboard,
   165                                        clipboard_class.set_clipboard,
   166                                        bytes);
   167   android_exception_check_1 (bytes);
   168 
   169   ANDROID_DELETE_LOCAL_REF (bytes);
   170   return Qnil;
   171 }
   172 
   173 DEFUN ("android-get-clipboard", Fandroid_get_clipboard,
   174        Sandroid_get_clipboard, 0, 0, 0,
   175        doc: /* Return the current contents of the clipboard.
   176 Value is a multibyte string containing decoded clipboard
   177 text.
   178 Alternatively, return nil if the clipboard is empty.  */)
   179   (void)
   180 {
   181   Lisp_Object string;
   182   jarray bytes;
   183   jmethodID method;
   184   size_t length;
   185   jbyte *data;
   186 
   187   if (!android_init_gui)
   188     error ("No Android display connection!");
   189 
   190   method = clipboard_class.get_clipboard;
   191   bytes
   192     = (*android_java_env)->CallObjectMethod (android_java_env,
   193                                              clipboard,
   194                                              method);
   195   android_exception_check ();
   196 
   197   if (!bytes)
   198     return Qnil;
   199 
   200   length = (*android_java_env)->GetArrayLength (android_java_env,
   201                                                 bytes);
   202   data = (*android_java_env)->GetByteArrayElements (android_java_env,
   203                                                     bytes, NULL);
   204   android_exception_check_nonnull (data, bytes);
   205 
   206   string = make_unibyte_string ((char *) data, length);
   207 
   208   (*android_java_env)->ReleaseByteArrayElements (android_java_env,
   209                                                  bytes, data,
   210                                                  JNI_ABORT);
   211   ANDROID_DELETE_LOCAL_REF (bytes);
   212 
   213   /* Now decode the resulting string.  */
   214   return code_convert_string_norecord (string, Qutf_8, false);
   215 }
   216 
   217 DEFUN ("android-clipboard-exists-p", Fandroid_clipboard_exists_p,
   218        Sandroid_clipboard_exists_p, 0, 0, 0,
   219        doc: /* Return whether or not clipboard contents exist.  */)
   220   (void)
   221 {
   222   jboolean rc;
   223   jmethodID method;
   224 
   225   if (!android_init_gui)
   226     error ("No Android display connection");
   227 
   228   method = clipboard_class.clipboard_exists;
   229   rc = (*android_java_env)->CallBooleanMethod (android_java_env,
   230                                                clipboard,
   231                                                method);
   232   android_exception_check ();
   233 
   234   return rc ? Qt : Qnil;
   235 }
   236 
   237 DEFUN ("android-browse-url", Fandroid_browse_url,
   238        Sandroid_browse_url, 1, 2, 0,
   239        doc: /* Open URL in an external application.  URL should be a
   240 URL-encoded URL with a scheme specified unless SEND is non-nil.
   241 Signal an error upon failure.
   242 
   243 If SEND is nil, start a program that is able to display the URL, such
   244 as a web browser.  Otherwise, try to share URL using programs such as
   245 email clients.  */)
   246   (Lisp_Object url, Lisp_Object send)
   247 {
   248   Lisp_Object value;
   249 
   250   if (!android_init_gui)
   251     error ("No Android display connection!");
   252 
   253   CHECK_STRING (url);
   254   value = android_browse_url (url, send);
   255 
   256   /* Signal an error upon failure.  */
   257   if (!NILP (value))
   258     signal_error ("Error browsing URL", value);
   259 
   260   return Qnil;
   261 }
   262 
   263 
   264 
   265 /* MIME clipboard support.  This provides support for reading MIME
   266    data (but not text) from the clipboard.  */
   267 
   268 DEFUN ("android-get-clipboard-targets", Fandroid_get_clipboard_targets,
   269        Sandroid_get_clipboard_targets, 0, 0, 0,
   270        doc: /* Return a list of data types in the clipboard.
   271 Value is a list of MIME types as strings, each defining a single extra
   272 data type available from the clipboard.  */)
   273   (void)
   274 {
   275   jarray bytes_array;
   276   jbyteArray bytes;
   277   jmethodID method;
   278   size_t length, length1, i;
   279   jbyte *data;
   280   Lisp_Object targets, tem;
   281 
   282   if (!android_init_gui)
   283     error ("No Android display connection!");
   284 
   285   targets = Qnil;
   286   block_input ();
   287   method = clipboard_class.get_clipboard_targets;
   288   bytes_array = (*android_java_env)->CallObjectMethod (android_java_env,
   289                                                        clipboard, method);
   290   android_exception_check ();
   291 
   292   if (!bytes_array)
   293     goto fail;
   294 
   295   length = (*android_java_env)->GetArrayLength (android_java_env,
   296                                                 bytes_array);
   297   for (i = 0; i < length; ++i)
   298     {
   299       /* Retireve the MIME type.  */
   300       bytes
   301         = (*android_java_env)->GetObjectArrayElement (android_java_env,
   302                                                       bytes_array, i);
   303       android_exception_check_nonnull (bytes, bytes_array);
   304 
   305       /* Cons it onto the list of targets.  */
   306       length1 = (*android_java_env)->GetArrayLength (android_java_env,
   307                                                      bytes);
   308       data = (*android_java_env)->GetByteArrayElements (android_java_env,
   309                                                         bytes, NULL);
   310       android_exception_check_nonnull_1 (data, bytes, bytes_array);
   311 
   312       /* Decode the string.  */
   313       tem = make_unibyte_string ((char *) data, length1);
   314       tem = code_convert_string_norecord (tem, Qutf_8, false);
   315       targets = Fcons (tem, targets);
   316 
   317       /* Delete the retrieved data.  */
   318       (*android_java_env)->ReleaseByteArrayElements (android_java_env,
   319                                                      bytes, data,
   320                                                      JNI_ABORT);
   321       ANDROID_DELETE_LOCAL_REF (bytes);
   322     }
   323   unblock_input ();
   324 
   325   ANDROID_DELETE_LOCAL_REF (bytes_array);
   326   return Fnreverse (targets);
   327 
   328  fail:
   329   unblock_input ();
   330   return Qnil;
   331 }
   332 
   333 /* Free the memory inside PTR, a pointer to a char pointer.  */
   334 
   335 static void
   336 android_xfree_inside (void *ptr)
   337 {
   338   xfree (*(char **) ptr);
   339 }
   340 
   341 DEFUN ("android-get-clipboard-data", Fandroid_get_clipboard_data,
   342        Sandroid_get_clipboard_data, 1, 1, 0,
   343        doc: /* Return the clipboard data of the given MIME TYPE.
   344 Value is a unibyte string containing the entire contents of the
   345 clipboard, after its owner has converted the data to the given
   346 MIME type.  Value is nil if the conversion fails, or if the data
   347 is not present.
   348 
   349 Value is also nil if the clipboard data consists of a single URL which
   350 does not have any corresponding data.  In that case, use
   351 `android-get-clipboard' instead.  */)
   352   (Lisp_Object type)
   353 {
   354   jlongArray array;
   355   jbyteArray bytes;
   356   jmethodID method;
   357   int fd;
   358   ptrdiff_t rc;
   359   jlong offset, length, *longs;
   360   specpdl_ref ref;
   361   char *buffer, *start;
   362 
   363   if (!android_init_gui)
   364     error ("No Android display connection!");
   365 
   366   /* Encode the string as UTF-8.  */
   367   CHECK_STRING (type);
   368   type = ENCODE_UTF_8 (type);
   369 
   370   /* Then give it to the selection code.  */
   371   block_input ();
   372   bytes = (*android_java_env)->NewByteArray (android_java_env,
   373                                              SBYTES (type));
   374   (*android_java_env)->SetByteArrayRegion (android_java_env, bytes,
   375                                            0, SBYTES (type),
   376                                            (jbyte *) SDATA (type));
   377   android_exception_check ();
   378 
   379   method = clipboard_class.get_clipboard_data;
   380   array = (*android_java_env)->CallObjectMethod (android_java_env,
   381                                                  clipboard, method,
   382                                                  bytes);
   383   android_exception_check_1 (bytes);
   384   ANDROID_DELETE_LOCAL_REF (bytes);
   385 
   386   if (!array)
   387     goto fail;
   388 
   389   longs = (*android_java_env)->GetLongArrayElements (android_java_env,
   390                                                      array, NULL);
   391   android_exception_check_nonnull (longs, array);
   392 
   393   /* longs[0] is the file descriptor.
   394      longs[1] is an offset to apply to the file.
   395      longs[2] is either -1, or the number of bytes to read from the
   396      file.  */
   397   fd = longs[0];
   398   offset = longs[1];
   399   length = longs[2];
   400 
   401   (*android_java_env)->ReleaseLongArrayElements (android_java_env,
   402                                                  array, longs,
   403                                                  JNI_ABORT);
   404   ANDROID_DELETE_LOCAL_REF (array);
   405   unblock_input ();
   406 
   407   /* Now begin reading from longs[0].  */
   408   ref = SPECPDL_INDEX ();
   409   record_unwind_protect_int (close_file_unwind, fd);
   410 
   411   if (length != -1)
   412     {
   413       buffer = xmalloc (MIN (length, PTRDIFF_MAX));
   414       record_unwind_protect_ptr (xfree, buffer);
   415 
   416       rc = emacs_read_quit (fd, buffer,
   417                             MIN (length, PTRDIFF_MAX));
   418 
   419       /* Return nil upon an IO problem.  */
   420       if (rc < 0)
   421         return unbind_to (ref, Qnil);
   422 
   423       /* Return the data as a unibyte string.  */
   424       return unbind_to (ref, make_unibyte_string (buffer, rc));
   425     }
   426 
   427   /* Otherwise, read BUFSIZ bytes at a time.  */
   428   buffer = xmalloc (BUFSIZ);
   429   length = 0;
   430   start = buffer;
   431 
   432   record_unwind_protect_ptr (android_xfree_inside, &buffer);
   433 
   434   /* Seek to the start of the data.  */
   435 
   436   if (offset)
   437     {
   438       if (lseek (fd, offset, SEEK_SET) < 0)
   439         return unbind_to (ref, Qnil);
   440     }
   441 
   442   while (true)
   443     {
   444       rc = emacs_read_quit (fd, start, BUFSIZ);
   445 
   446       if (!INT_ADD_OK (rc, length, &length)
   447           || PTRDIFF_MAX - length < BUFSIZ)
   448         memory_full (PTRDIFF_MAX);
   449 
   450       if (rc < 0)
   451         return unbind_to (ref, Qnil);
   452 
   453       if (rc < BUFSIZ)
   454         break;
   455 
   456       buffer = xrealloc (buffer, length + BUFSIZ);
   457       start = buffer + length;
   458     }
   459 
   460   return unbind_to (ref, make_unibyte_string (buffer, rc));
   461 
   462  fail:
   463   unblock_input ();
   464   return Qnil;
   465 }
   466 
   467 
   468 
   469 void
   470 init_androidselect (void)
   471 {
   472   jobject tem;
   473   jmethodID make_clipboard;
   474 
   475   if (!android_init_gui)
   476     return;
   477 
   478   android_init_emacs_clipboard ();
   479 
   480   make_clipboard = clipboard_class.make_clipboard;
   481   tem
   482     = (*android_java_env)->CallStaticObjectMethod (android_java_env,
   483                                                    clipboard_class.class,
   484                                                    make_clipboard);
   485   if (!tem)
   486     emacs_abort ();
   487 
   488   clipboard = (*android_java_env)->NewGlobalRef (android_java_env, tem);
   489 
   490   if (!clipboard)
   491     emacs_abort ();
   492 
   493   ANDROID_DELETE_LOCAL_REF (tem);
   494 }
   495 
   496 void
   497 syms_of_androidselect (void)
   498 {
   499   defsubr (&Sandroid_clipboard_owner_p);
   500   defsubr (&Sandroid_set_clipboard);
   501   defsubr (&Sandroid_get_clipboard);
   502   defsubr (&Sandroid_clipboard_exists_p);
   503   defsubr (&Sandroid_browse_url);
   504   defsubr (&Sandroid_get_clipboard_targets);
   505   defsubr (&Sandroid_get_clipboard_data);
   506 }

/* [<][>][^][v][top][bottom][index][help] */