1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-11-21 06:55:39 +00:00

Update Android port

* .gitignore: Don't ignore verbose.mk.android.
* doc/emacs/Makefile.in (EMACSSOURCES): Add android.texi and
input.texi.
* doc/emacs/android.texi (Android): Document support for the
on-screen keyboard.
(Android Startup): Document how to start Emacs with -Q on
Android.
(Android Environment): Document how Emacs works around the
system ``task killer''.  Document changes to frame deletion
behavior.
* doc/emacs/emacs.texi (Top):
* doc/emacs/input.texi (Other Input Devices, On-Screen
Keyboards): Document how to use Emacs with virtual keyboards.
* doc/lispref/commands.texi (Touchscreen Events): Document
changes to `touch-screen-track-drag'.
* doc/lispref/frames.texi (Frames, On-Screen Keyboards): New
node.
* java/AndroidManifest.xml.in: Add settings activity and
appropriate OSK adjustment mode.
* java/org/gnu/emacs/EmacsActivity.java (onCreate): Allow
creating Emacs with -Q.
(onDestroy): Don't remove if killed by the system.
* java/org/gnu/emacs/EmacsContextMenu.java (inflateMenuItems):
Fix context menus again.
* java/org/gnu/emacs/EmacsNative.java (EmacsNative): Make all
event sending functions return long.
* java/org/gnu/emacs/EmacsPreferencesActivity.java
(EmacsPreferencesActivity): New class.
* java/org/gnu/emacs/EmacsService.java (EmacsService)
(onStartCommand, onCreate, startEmacsService): Start as a
foreground service if necessary to bypass system restrictions.
* java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
* java/org/gnu/emacs/EmacsThread.java (EmacsThread, run):
* java/org/gnu/emacs/EmacsView.java (EmacsView, onLayout)
(onDetachedFromWindow):
* java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, viewLayout):
Implement frame resize synchronization..
* java/org/gnu/emacs/EmacsWindowAttachmentManager.java
(EmacsWindowAttachmentManager, removeWindowConsumer): Adjust
accordingly for changes to frame deletion behavior.
* lisp/frame.el (android-toggle-on-screen-keyboard)
(frame-toggle-on-screen-keyboard): New function.
* lisp/minibuffer.el (minibuffer-setup-on-screen-keyboard)
(minibuffer-exit-on-screen-keyboard): New functions.
(minibuffer-setup-hook, minibuffer-exit-hook): Add new functions
to hooks.

* lisp/touch-screen.el (touch-screen-relative-xy): Accept new
value of window `frame'.  Return frame coordinates in that case.
(touch-screen-set-point-commands): New variable.
(touch-screen-handle-point-up): Respect that variable.
(touch-screen-track-drag): Return `no-drag' where appropriate.
(touch-screen-drag-mode-line-1, touch-screen-drag-mode-line):
Refactor to use `no-drag'.

* src/android.c (struct android_emacs_window): New methods.
Make all event sending functions return the event serial.
(android_toggle_on_screen_keyboard, android_window_updated): New
functions.
* src/android.h: Update prototypes.
* src/androidfns.c (Fandroid_toggle_on_screen_keyboard)
(syms_of_androidfns): New function.
* src/androidgui.h (struct android_any_event)
(struct android_key_event, struct android_configure_event)
(struct android_focus_event, struct android_window_action_event)
(struct android_crossing_event, struct android_motion_event)
(struct android_button_event, struct android_touch_event)
(struct android_wheel_event, struct android_iconify_event)
(struct android_menu_event): Add `serial' fields.

* src/androidterm.c (handle_one_android_event)
(android_frame_up_to_date):
* src/androidterm.h (struct android_output): Implement frame
resize synchronization.
This commit is contained in:
Po Lu 2023-01-20 19:06:32 +08:00
parent e07b58dc35
commit d44b60c2f0
27 changed files with 895 additions and 200 deletions

1
.gitignore vendored
View File

@ -55,6 +55,7 @@ src/emacs-module.h
# Built by recursive call to `configure'.
*.android
!INSTALL.android
!verbose.mk.android
# Built by `java'.
java/install_temp/*

View File

@ -146,6 +146,8 @@ EMACSSOURCES= \
${srcdir}/glossary.texi \
${srcdir}/ack.texi \
${srcdir}/kmacro.texi \
${srcdir}/android.texi \
${srcdir}/input.texi \
$(EMACS_XTRA)
## Disable implicit rules.

View File

@ -10,8 +10,8 @@ Alliance. This section describes the peculiarities of using Emacs on
an Android device running Android 2.2 or later.
Android devices commonly rely on user input through a touch screen
or digitizer device. For more information about using them with
Emacs, @pxref{other Input Devices}.
or digitizer device and on-screen keyboard. For more information
about using such devices with Emacs, @pxref{Other Input Devices}.
@menu
* What is Android?:: Preamble.
@ -82,6 +82,16 @@ command on that other system:
$ adb logcat | grep -E "(android_run_debug_thread|[Ee]macs)"
@end example
@cindex emacs -Q, android
Since Android has no command line, there is normally no way to specify
command-line arguments. However, Emacs can be started with the
equivalent of the @code{--quick} option (@pxref{Initial Options})
through a special preferences screen, which can be accessed through
the Emacs ``app info'' page in the system settings application.
Consult the manufacturer of your device for more details, as how to do
this varies by device.
@node Android File System
@section What files Emacs can access under Android
@cindex /assets directory, android
@ -149,7 +159,7 @@ which is the app data directory (@pxref{Android File System}.)
directories, and the app data directories of other applications. In
recent versions of Android, the system also prohibits, for security
reasons, even Emacs itself from running executables inside the app
data directory!
data directory.
Emacs comes with several binaries. While being executable files,
they are packaged as libraries in the library directory, because
@ -173,9 +183,28 @@ within itself.
Application processes are treated as disposable entities by the
system. When all Emacs frames move to the background, Emacs is liable
to be killed by the system at any time, for the purpose of saving
resources. There is currently no easy way to bypass these
restrictions, aside from keeping Emacs constantly running in the
foreground.
system resources.
On Android 7.1 and earlier, Emacs tells the system to treat it as a
``background service''. The system will try to avoid killing Emacs
unless the device is under memory stress.
Android 8.0 removed the ability for background services to receive
such special treatment. However, Emacs applies a workaround: the
system considers applications that create a permanent notification to
be performing active work, and will avoid killing such applications.
Thus, on those systems, Emacs displays a permanant notification for as
long as it is running. Once the notification is displayed, it can be
safely hidden through the system settings without resulting in Emacs
being killed.
However, it is not guaranteed that the system will not kill Emacs,
even if the notification is being displayed. While the Open Handset
Alliance's sample implementation of Android behaves correctly, many
manufacturers place additional restrictions on program execution in
the background in their proprietary versions of Android. There is a
list of such troublesome manufacturers and sometimes workarounds, at
@url{https://dontkillmyapp.com/}.
@section Android permissions
@cindex external storage, android
@ -272,14 +301,14 @@ maximized or full-screen, and only one window can be displayed at a
time. On larger devices, the system allows up to four windows to be
tiled on the screen at any time.
Windows on Android do not continue to exist indefinitely after they
Windows on Android do not continue to exist indefinitely after they
are created. Instead, the system may choose to terminate windows that
are not on screen in order to save memory, with the assumption that
the program will save its contents to disk and restore them later,
when the user asks to open it again. As this is obvious not possible
with Emacs, Emacs separates a frame from a system window.
Each system window created (including the initial window created
Each system window created (including the initial window created
during Emacs startup) is appended to a list of windows that do not
have associated frames. When a frame is created, Emacs looks up any
window within that list, and displays the contents of the frame
@ -287,11 +316,28 @@ within; if there is no window at all, then one is created. Likewise,
when a new window is created by the system, Emacs places the contents
of any frame that is not already displayed within a window inside.
When a frame is closed, the corresponding system window is also
closed.
closed. Upon startup, the system creates a window itself (within
which Emacs displays the first window system frame shortly
thereafter.) Emacs differentiates between that window and windows
created on behalf of other frames to determine what to do when the
system window associated with a frame is closed:
This strategy works as long as one window is in the foreground.
Otherwise, Emacs can only run in the background for a limited amount
of time before the process is killed completely.
@itemize @bullet
@item
When the system closes the window created during application startup
in order to save memory, Emacs retains the frame for when that window
is created later.
@item
When the user closes the window created during application startup,
and the window was not previously closed by the system in order to
save resources, Emacs deletes any frame displayed within that window.
@item
When the user or the system closes any window created by Emacs on
behalf of a specific frame, Emacs deletes the frame displayed within
that window.
@end itemize
@cindex windowing limitations, android
@cindex frame parameters, android

View File

@ -1269,6 +1269,7 @@ Emacs and Android
Emacs and unconventional input devices
* Touchscreens:: Using Emacs on touchscreens.
* On-Screen Keyboards:: Using Emacs with virtual keyboards.
Emacs and Microsoft Windows/MS-DOS

View File

@ -16,6 +16,7 @@ input devices, which is detailed here.
@menu
* Touchscreens:: Using Emacs on touchscreens.
* On-Screen Keyboards:: Using Emacs with virtual keyboards.
@end menu
@node Touchscreens
@ -58,3 +59,38 @@ were to be held down. @xref{Mouse Commands}.
By default, Emacs considers a tool as having been left on the
display for a while after 0.7 seconds, but this can be changed by
customizing the variable @code{touch-screen-delay}.
@node On-Screen Keyboards
@section Using Emacs with virtual keyboards
@cindex virtual keyboards
@cindex on-screen keyboards
When there is no physical keyboard attached to a system, the
windowing system typically provides an on-screen keyboard, more often
known as a ``virtual keyboard'', containing rows of clickable buttons
that send keyboard input to the application, much like a real keyboard
would. This virtual keyboard is hidden by default, as it uses up
valuable on-screen real estate, and must be opened once the program
being used is ready to accept keyboard input.
Under the X Window System, the client that provides the on-screen
keyboard typically detects when the application is ready to accept
keyboard input through a set of complex heuristics, and automatically
displays the keyboard when necessary.
On other systems such as Android, Emacs must tell the system when it
is ready to accept keyboard input. Typically, this is done in
response to a touchscreen ``tap'' gesture (@pxref{Touchscreens}), or
once to the minibuffer becomes in use (@pxref{Minibuffer}.)
@vindex touch-screen-set-point-commands
When a ``tap'' gesture results in a command being executed, Emacs
checks to see whether or not the command is supposed to set the point
by looking for it in the list @code{touch-screen-set-point-commands}.
If it is, then Emacs looks up whether or not the text under the point
is read-only; if not, it activates the on-screen keyboard, assuming
that the user is about to enter text in to the current buffer.
Emacs also provides a set of functions to show or hide the on-screen
keyboard. For more details, @pxref{On-Screen Keyboards,,, elisp, The
Emacs Lisp Reference Manual}.

View File

@ -2055,9 +2055,11 @@ The caller should not perform any action in that case.
@defun touch-screen-track-drag event update &optional data
This function is used to track a single ``drag'' gesture originating
from the @code{touchscreen-begin} event @code{event}. Currently, it
behaves identically to @code{touch-screen-track-tap}, but differences
are anticipated in the future.
from the @code{touchscreen-begin} event @code{event}.
It behaves like @code{touch-screen-track-tap}, except that it returns
@code{no-drag} if the touchpoint in @code{event} did not move far
enough to qualify as an actual drag.
@end defun
@node Focus Events

View File

@ -104,6 +104,7 @@ window of another Emacs frame. @xref{Child Frames}.
* Mouse Tracking:: Getting events that say when the mouse moves.
* Mouse Position:: Asking where the mouse is, or moving it.
* Pop-Up Menus:: Displaying a menu for the user to select from.
* On-Screen Keyboards:: Displaying the virtual keyboard.
* Dialog Boxes:: Displaying a box to ask yes or no.
* Pointer Shape:: Specifying the shape of the mouse pointer.
* Window System Selections:: Transferring text to and from other X clients.
@ -3812,6 +3813,26 @@ keymap. It won't be called if @code{x-popup-menu} returns for some
other reason without displaying a pop-up menu.
@end defvar
@node On-Screen Keyboards
@section On-Screen Keyboards
An on-screen keyboard is a special kind of pop up provided by the
system, with rows of clickable buttons that act as a real keyboard.
On certain systems (@pxref{On-Screen Keyboards,,,emacs, The Emacs
Manual}), Emacs is supposed to display and hide the on screen keyboard
depending on whether or not the user is about to type something.
@defun frame-toggle-on-screen-keyboard frame hide
This function displays or hides the on-screen keyboard on behalf of
the frame @var{frame}. If @var{hide} is non-@code{nil}, then the
on-screen keyboard is hidden; otherwise, it is displayed.
This has no effect if the system automatically detects when to display
the on-screen keyboard, or when it does not provide any on-screen
keyboard.
@end defun
@node Dialog Boxes
@section Dialog Boxes
@cindex dialog boxes

View File

@ -62,8 +62,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. -->
android:theme="@android:style/Theme"
android:debuggable="true"
android:extractNativeLibs="true">
<activity android:name="org.gnu.emacs.EmacsActivity"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -73,8 +75,18 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. -->
</activity>
<activity android:name="org.gnu.emacs.EmacsMultitaskActivity"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"/>
<activity android:autoRemoveFromRecents="true"
android:label="Emacs options"
android:name=".EmacsPreferencesActivity">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service android:name="org.gnu.emacs.EmacsService"
android:directBootAware="false"
android:enabled="true"

View File

@ -161,6 +161,13 @@ public class EmacsActivity extends Activity
onCreate (Bundle savedInstanceState)
{
FrameLayout.LayoutParams params;
Intent intent;
/* See if Emacs should be started with -Q. */
intent = getIntent ();
EmacsService.needDashQ
= intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q",
false);
/* Set the theme to one without a title bar. */
@ -179,9 +186,8 @@ public class EmacsActivity extends Activity
/* Set it as the content view. */
setContentView (layout);
if (EmacsService.SERVICE == null)
/* Start the Emacs service now. */
startService (new Intent (this, EmacsService.class));
/* Maybe start the Emacs service if necessary. */
EmacsService.startEmacsService (this);
/* Add this activity to the list of available activities. */
EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
@ -193,10 +199,16 @@ public class EmacsActivity extends Activity
public void
onDestroy ()
{
EmacsWindowAttachmentManager manager;
boolean isMultitask;
manager = EmacsWindowAttachmentManager.MANAGER;
/* The activity will die shortly hereafter. If there is a window
attached, close it now. */
Log.d (TAG, "onDestroy " + this);
EmacsWindowAttachmentManager.MANAGER.removeWindowConsumer (this);
isMultitask = this instanceof EmacsMultitaskActivity;
manager.removeWindowConsumer (this, isMultitask || isFinishing ());
focusedActivities.remove (this);
invalidateFocus ();
super.onDestroy ();

View File

@ -174,6 +174,7 @@ private class Item implements MenuItem.OnMenuItemClickListener
support doing so, create the submenu and add the
contents of the menu to it. */
submenu = menu.addSubMenu (item.itemName);
item.subMenu.inflateMenuItems (submenu);
}
catch (UnsupportedOperationException exception)
{

View File

@ -61,75 +61,76 @@ public static native void setEmacsParams (AssetManager assetManager,
/* Abort and generate a native core dump. */
public static native void emacsAbort ();
/* Send an ANDROID_CONFIGURE_NOTIFY event. */
public static native void sendConfigureNotify (short window, long time,
/* Send an ANDROID_CONFIGURE_NOTIFY event. The values of all the
functions below are the serials of the events sent. */
public static native long sendConfigureNotify (short window, long time,
int x, int y, int width,
int height);
/* Send an ANDROID_KEY_PRESS event. */
public static native void sendKeyPress (short window, long time, int state,
public static native long sendKeyPress (short window, long time, int state,
int keyCode, int unicodeChar);
/* Send an ANDROID_KEY_RELEASE event. */
public static native void sendKeyRelease (short window, long time, int state,
public static native long sendKeyRelease (short window, long time, int state,
int keyCode, int unicodeChar);
/* Send an ANDROID_FOCUS_IN event. */
public static native void sendFocusIn (short window, long time);
public static native long sendFocusIn (short window, long time);
/* Send an ANDROID_FOCUS_OUT event. */
public static native void sendFocusOut (short window, long time);
public static native long sendFocusOut (short window, long time);
/* Send an ANDROID_WINDOW_ACTION event. */
public static native void sendWindowAction (short window, int action);
public static native long sendWindowAction (short window, int action);
/* Send an ANDROID_ENTER_NOTIFY event. */
public static native void sendEnterNotify (short window, int x, int y,
public static native long sendEnterNotify (short window, int x, int y,
long time);
/* Send an ANDROID_LEAVE_NOTIFY event. */
public static native void sendLeaveNotify (short window, int x, int y,
public static native long sendLeaveNotify (short window, int x, int y,
long time);
/* Send an ANDROID_MOTION_NOTIFY event. */
public static native void sendMotionNotify (short window, int x, int y,
public static native long sendMotionNotify (short window, int x, int y,
long time);
/* Send an ANDROID_BUTTON_PRESS event. */
public static native void sendButtonPress (short window, int x, int y,
public static native long sendButtonPress (short window, int x, int y,
long time, int state,
int button);
/* Send an ANDROID_BUTTON_RELEASE event. */
public static native void sendButtonRelease (short window, int x, int y,
public static native long sendButtonRelease (short window, int x, int y,
long time, int state,
int button);
/* Send an ANDROID_TOUCH_DOWN event. */
public static native void sendTouchDown (short window, int x, int y,
public static native long sendTouchDown (short window, int x, int y,
long time, int pointerID);
/* Send an ANDROID_TOUCH_UP event. */
public static native void sendTouchUp (short window, int x, int y,
public static native long sendTouchUp (short window, int x, int y,
long time, int pointerID);
/* Send an ANDROID_TOUCH_MOVE event. */
public static native void sendTouchMove (short window, int x, int y,
public static native long sendTouchMove (short window, int x, int y,
long time, int pointerID);
/* Send an ANDROID_WHEEL event. */
public static native void sendWheel (short window, int x, int y,
public static native long sendWheel (short window, int x, int y,
long time, int state,
float xDelta, float yDelta);
/* Send an ANDROID_ICONIFIED event. */
public static native void sendIconified (short window);
public static native long sendIconified (short window);
/* Send an ANDROID_DEICONIFIED event. */
public static native void sendDeiconified (short window);
public static native long sendDeiconified (short window);
/* Send an ANDROID_CONTEXT_MENU event. */
public static native void sendContextMenu (short window, int menuEventID);
public static native long sendContextMenu (short window, int menuEventID);
static
{

View File

@ -0,0 +1,98 @@
/* 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.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.R;
/* This module provides a ``preferences'' display for Emacs. It is
supposed to be launched from inside the Settings application to
perform various actions, such as starting Emacs with the ``-Q''
option, which would not be possible otherwise, as there is no
command line on Android. */
public class EmacsPreferencesActivity extends Activity
{
/* The linear layout associated with the activity. */
private LinearLayout layout;
/* Restart Emacs with -Q. Call EmacsThread.exit to kill Emacs now, and
tell the system to EmacsActivity with some parameters later. */
private void
startEmacsQ ()
{
Intent intent;
intent = new Intent (this, EmacsActivity.class);
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra ("org.gnu.emacs.START_DASH_Q", true);
startActivity (intent);
System.exit (0);
}
@Override
public void
onCreate (Bundle savedInstanceState)
{
LinearLayout layout;
TextView textView;
LinearLayout.LayoutParams params;
int resid;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
setTheme (R.style.Theme_DeviceDefault_Settings);
else if (Build.VERSION.SDK_INT
>= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
setTheme (R.style.Theme_DeviceDefault);
layout = new LinearLayout (this);
layout.setOrientation (LinearLayout.VERTICAL);
setContentView (layout);
textView = new TextView (this);
textView.setPadding (8, 20, 20, 8);
params = new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
textView.setLayoutParams (params);
textView.setText ("(Re)start Emacs with -Q");
textView.setOnClickListener (new View.OnClickListener () {
@Override
public void
onClick (View view)
{
startEmacsQ ();
}
});
layout.addView (textView);
super.onCreate (savedInstanceState);
}
};

View File

@ -32,7 +32,13 @@
import android.view.KeyEvent;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
@ -63,6 +69,7 @@ public class EmacsService extends Service
public static final String TAG = "EmacsService";
public static final int MAX_PENDING_REQUESTS = 256;
public static volatile EmacsService SERVICE;
public static boolean needDashQ;
private EmacsThread thread;
private Handler handler;
@ -74,6 +81,31 @@ public class EmacsService extends Service
public int
onStartCommand (Intent intent, int flags, int startId)
{
Notification notification;
NotificationManager manager;
NotificationChannel channel;
String infoBlurb;
Object tem;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
tem = getSystemService (Context.NOTIFICATION_SERVICE);
manager = (NotificationManager) tem;
infoBlurb = ("See (emacs)Android Environment for more"
+ " details about this notification.");
channel
= new NotificationChannel ("emacs", "Emacs persistent notification",
NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel (channel);
notification = (new Notification.Builder (this, "emacs")
.setContentTitle ("Emacs")
.setContentText (infoBlurb)
.setSmallIcon (android.R.drawable.sym_def_app_icon)
.build ());
manager.notify (1, notification);
startForeground (1, notification);
}
return START_NOT_STICKY;
}
@ -137,7 +169,7 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
this);
/* Start the thread that runs Emacs. */
thread = new EmacsThread (this);
thread = new EmacsThread (this, needDashQ);
thread.start ();
}
catch (IOException exception)
@ -444,4 +476,32 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
}
}
}
/* Start the Emacs service if necessary. On Android 26 and up,
start Emacs as a foreground service with a notification, to avoid
it being killed by the system.
On older systems, simply start it as a normal background
service. */
public static void
startEmacsService (Context context)
{
PendingIntent intent;
if (EmacsService.SERVICE == null)
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
/* Start the Emacs service now. */
context.startService (new Intent (context,
EmacsService.class));
else
/* Display the permanant notification and start Emacs as a
foreground service. */
context.startForegroundService (new Intent (context,
EmacsService.class));
}
}
};

View File

@ -32,53 +32,106 @@
public class EmacsSurfaceView extends SurfaceView
{
private static final String TAG = "EmacsSurfaceView";
public Object surfaceChangeLock;
private boolean created;
private EmacsView view;
/* This is the callback used on Android 8 to 25. */
private class Callback implements SurfaceHolder.Callback
{
@Override
public void
surfaceChanged (SurfaceHolder holder, int format,
int width, int height)
{
Log.d (TAG, "surfaceChanged: " + view + ", " + view.pendingConfigure);
/* Make sure not to swap buffers if there is pending
configuration, because otherwise the redraw callback will not
run correctly. */
if (view.pendingConfigure == 0)
view.swapBuffers ();
}
@Override
public void
surfaceCreated (SurfaceHolder holder)
{
synchronized (surfaceChangeLock)
{
Log.d (TAG, "surfaceCreated: " + view);
created = true;
}
/* Drop the lock when doing this, or a deadlock can
result. */
view.swapBuffers ();
}
@Override
public void
surfaceDestroyed (SurfaceHolder holder)
{
synchronized (surfaceChangeLock)
{
Log.d (TAG, "surfaceDestroyed: " + view);
created = false;
}
}
}
/* And this is the callback used on Android 26 and later. It is
used because it can tell the system when drawing completes. */
private class Callback2 extends Callback implements SurfaceHolder.Callback2
{
@Override
public void
surfaceRedrawNeeded (SurfaceHolder holder)
{
/* This version is not supported. */
return;
}
@Override
public void
surfaceRedrawNeededAsync (SurfaceHolder holder,
Runnable drawingFinished)
{
Runnable old;
Log.d (TAG, "surfaceRedrawNeededAsync: " + view.pendingConfigure);
/* The system calls this function when it wants to know whether
or not Emacs is still configuring itself in response to a
resize.
If the view did not send an outstanding ConfigureNotify
event, then call drawingFinish immediately. Else, give it to
the view to execute after drawing completes. */
if (view.pendingConfigure == 0)
drawingFinished.run ();
else
/* And set this runnable to run once drawing completes. */
view.drawingFinished = drawingFinished;
}
}
public
EmacsSurfaceView (final EmacsView view)
{
super (view.getContext ());
surfaceChangeLock = new Object ();
this.surfaceChangeLock = new Object ();
this.view = view;
getHolder ().addCallback (new SurfaceHolder.Callback () {
@Override
public void
surfaceChanged (SurfaceHolder holder, int format,
int width, int height)
{
Log.d (TAG, "surfaceChanged: " + view);
view.swapBuffers ();
}
@Override
public void
surfaceCreated (SurfaceHolder holder)
{
synchronized (surfaceChangeLock)
{
Log.d (TAG, "surfaceCreated: " + view);
created = true;
}
/* Drop the lock when doing this, or a deadlock can
result. */
view.swapBuffers ();
}
@Override
public void
surfaceDestroyed (SurfaceHolder holder)
{
synchronized (surfaceChangeLock)
{
Log.d (TAG, "surfaceDestroyed: " + view);
created = false;
}
}
});
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
getHolder ().addCallback (new Callback ());
else
getHolder ().addCallback (new Callback2 ());
}
public boolean

View File

@ -23,12 +23,13 @@
public class EmacsThread extends Thread
{
EmacsService context;
/* Whether or not Emacs should be started -Q. */
private boolean startDashQ;
public
EmacsThread (EmacsService service)
EmacsThread (EmacsService service, boolean startDashQ)
{
context = service;
this.startDashQ = startDashQ;
}
public void
@ -36,7 +37,10 @@ public class EmacsThread extends Thread
{
String args[];
args = new String[] { "libandroid-emacs.so", };
if (!startDashQ)
args = new String[] { "libandroid-emacs.so", };
else
args = new String[] { "libandroid-emacs.so", "-Q", };
/* Run the native code now. */
EmacsNative.initEmacs (args);

View File

@ -19,6 +19,7 @@
package org.gnu.emacs;
import android.content.Context;
import android.content.res.ColorStateList;
import android.view.ContextMenu;
@ -27,6 +28,8 @@
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
@ -86,11 +89,23 @@ public class EmacsView extends ViewGroup
/* The serial of the last clip rectangle change. */
private long lastClipSerial;
/* The InputMethodManager for this view's context. */
private InputMethodManager imManager;
/* Runnable that will run once drawing completes. */
public Runnable drawingFinished;
/* Serial of the last ConfigureNotify event sent that Emacs has not
yet responded to. 0 if there is no such outstanding event. */
public long pendingConfigure;
public
EmacsView (EmacsWindow window)
{
super (EmacsService.SERVICE);
Object tem;
this.window = window;
this.damageRegion = new Region ();
this.paint = new Paint ();
@ -111,6 +126,10 @@ public class EmacsView extends ViewGroup
/* Get rid of the default focus highlight. */
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
setDefaultFocusHighlightEnabled (false);
/* Obtain the input method manager. */
tem = getContext ().getSystemService (Context.INPUT_METHOD_SERVICE);
imManager = (InputMethodManager) tem;
}
private void
@ -259,7 +278,8 @@ else if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.AT_MOST
if (changed || mustReportLayout)
{
mustReportLayout = false;
window.viewLayout (left, top, right, bottom);
pendingConfigure
= window.viewLayout (left, top, right, bottom);
}
measuredWidth = right - left;
@ -538,4 +558,37 @@ else if (child.getVisibility () != GONE)
Runtime.getRuntime ().gc ();
}
}
public void
showOnScreenKeyboard ()
{
/* Specifying no flags at all tells the system the user asked for
the input method to be displayed. */
imManager.showSoftInput (this, 0);
}
public void
hideOnScreenKeyboard ()
{
imManager.hideSoftInputFromWindow (this.getWindowToken (),
0);
}
public void
windowUpdated (long serial)
{
Log.d (TAG, "windowUpdated: serial is " + serial);
if (pendingConfigure <= serial
/* Detect wraparound. */
|| pendingConfigure - serial >= 0x7fffffff)
{
pendingConfigure = 0;
if (drawingFinished != null)
drawingFinished.run ();
drawingFinished = null;
}
}
};

View File

@ -233,7 +233,7 @@ private class Coordinate
return attached;
}
public void
public long
viewLayout (int left, int top, int right, int bottom)
{
int rectWidth, rectHeight;
@ -249,10 +249,10 @@ private class Coordinate
rectWidth = right - left;
rectHeight = bottom - top;
EmacsNative.sendConfigureNotify (this.handle,
System.currentTimeMillis (),
left, top, rectWidth,
rectHeight);
return EmacsNative.sendConfigureNotify (this.handle,
System.currentTimeMillis (),
left, top, rectWidth,
rectHeight);
}
public void
@ -589,11 +589,20 @@ private class Coordinate
EmacsActivity.invalidateFocus ();
}
/* Notice that the activity has been detached or destroyed.
ISFINISHING is set if the activity is not the main activity, or
if the activity was not destroyed in response to explicit user
action. */
public void
onActivityDetached ()
onActivityDetached (boolean isFinishing)
{
/* Destroy the associated frame when the activity is detached. */
EmacsNative.sendWindowAction (this.handle, 0);
/* Destroy the associated frame when the activity is detached in
response to explicit user action. */
if (isFinishing)
EmacsNative.sendWindowAction (this.handle, 0);
}
/* Look through the button state to determine what button EVENT was
@ -1064,4 +1073,37 @@ else if (EmacsWindow.this.isMapped)
/* Return the resulting coordinates. */
return array;
}
public void
toggleOnScreenKeyboard (final boolean on)
{
EmacsService.SERVICE.runOnUiThread (new Runnable () {
@Override
public void
run ()
{
if (on)
view.showOnScreenKeyboard ();
else
view.hideOnScreenKeyboard ();
}
});
}
/* Notice that outstanding configure events have been processed.
SERIAL is checked in the UI thread to verify that no new
configure events have been generated in the mean time. */
public void
windowUpdated (final long serial)
{
EmacsService.SERVICE.runOnUiThread (new Runnable () {
@Override
public void
run ()
{
view.windowUpdated (serial);
}
});
}
};

View File

@ -134,7 +134,7 @@ public interface WindowConsumer
}
public void
removeWindowConsumer (WindowConsumer consumer)
removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
{
EmacsWindow window;
@ -147,7 +147,7 @@ public interface WindowConsumer
Log.d (TAG, "removeWindowConsumer: detaching " + window);
consumer.detachWindow ();
window.onActivityDetached ();
window.onActivityDetached (isFinishing);
}
Log.d (TAG, "removeWindowConsumer: removing " + consumer);

View File

@ -2553,6 +2553,23 @@ symbols."
((string= name "Virtual core keyboard")
'core-keyboard))))))
;;;; On-screen keyboard management.
(declare-function android-toggle-on-screen-keyboard "androidfns.c")
(defun frame-toggle-on-screen-keyboard (frame hide)
"Display or hide the on-screen keyboard.
On systems with an on-screen keyboard, display the on screen
keyboard on behalf of the frame FRAME if HIDE is nil. Else, hide
the on screen keyboard.
FRAME must already have the input focus for this to work
reliably."
(let ((frame-type (framep-on-display frame)))
(cond ((eq frame-type 'android)
(android-toggle-on-screen-keyboard frame hide)))))
;;;; Frame geometry values

View File

@ -4576,6 +4576,29 @@ is included in the return value."
default)))
": "))
;;; On screen keyboard support.
;; Try to display the on screen keyboard whenever entering the
;; mini-buffer, and hide it whenever leaving.
(defun minibuffer-setup-on-screen-keyboard ()
"Maybe display the on-screen keyboard in the current frame.
Display the on-screen keyboard in the current frame if the
last device to have sent an input event is not a keyboard.
This is run upon minibuffer setup."
(when (not (memq (device-class last-event-frame
last-event-device)
'(keyboard core-keyboard)))
(frame-toggle-on-screen-keyboard (selected-frame) nil)))
(defun minibuffer-exit-on-screen-keyboard ()
"Hide the on-screen keyboard if it was displayed.
This is run upon minibuffer exit."
(frame-toggle-on-screen-keyboard (selected-frame) t))
(add-hook 'minibuffer-setup-hook #'minibuffer-setup-on-screen-keyboard)
(add-hook 'minibuffer-exit-hook #'minibuffer-exit-on-screen-keyboard)
(provide 'minibuffer)
;;; minibuffer.el ends here

View File

@ -38,6 +38,12 @@ touch point, and the initial position of the touchpoint. See
`touch-screen-handle-point-update' for the meanings of the fourth
element.")
(defvar touch-screen-set-point-commands '(mouse-set-point)
"List of commands known to set the point.
This is used to determine whether or not to display the on-screen
keyboard after a mouse command is executed in response to a
`touchscreen-end' event.")
(defvar touch-screen-current-timer nil
"Timer used to track long-presses.
This is always cleared upon any significant state change.")
@ -54,20 +60,28 @@ However, return the coordinates relative to WINDOW.
If (posn-window posn) is the same as window, simply return the
coordinates in POSN. Otherwise, convert them to the frame, and
then back again."
(if (eq (posn-window posn) window)
then back again.
If WINDOW is the symbol `frame', simply convert the coordinates
to the frame that they belong in."
(if (or (eq (posn-window posn) window)
(and (eq window 'frame)
(framep (posn-window posn))))
(posn-x-y posn)
(let ((xy (posn-x-y posn))
(edges (window-inside-pixel-edges window)))
(edges (and (windowp window)
(window-inside-pixel-edges window))))
;; Make the X and Y positions frame relative.
(when (windowp (posn-window posn))
(let ((edges (window-inside-pixel-edges
(posn-window posn))))
(setq xy (cons (+ (car xy) (car edges))
(+ (cdr xy) (cadr edges))))))
;; Make the X and Y positions window relative again.
(cons (- (car xy) (car edges))
(- (cdr xy) (cadr edges))))))
(if (eq window 'frame)
xy
;; Make the X and Y positions window relative again.
(cons (- (car xy) (car edges))
(- (cdr xy) (cadr edges)))))))
(defun touch-screen-handle-scroll (dx dy)
"Scroll the display assuming that a touch point has moved by DX and DY."
@ -252,7 +266,12 @@ If the fourth argument of `touch-screen-current-tool' is nil,
move point to the position of POINT, selecting the window under
POINT as well, and deactivate the mark; if there is a button or
link at POINT, call the command bound to `mouse-2' there.
Otherwise, call the command bound to `mouse-1'."
Otherwise, call the command bound to `mouse-1'.
If the command being executed is listed in
`touch-screen-set-point-commands' also display the on-screen
keyboard if the current buffer and the character at the new point
is not read-only."
(let ((what (nth 3 touch-screen-current-tool)))
(cond ((null what)
(when (windowp (posn-window (cdr point)))
@ -283,9 +302,17 @@ Otherwise, call the command bound to `mouse-1'."
(deactivate-mark)
;; This is necessary for following links.
(goto-char (posn-point (cdr point)))
;; Figure out if the on screen keyboard needs to be
;; displayed.
(when command
(call-interactively command nil
(vector event)))))))))
(vector event))
(when (memq command touch-screen-set-point-commands)
(if (not (or buffer-read-only
(get-text-property (point) 'read-only)))
(frame-toggle-on-screen-keyboard (selected-frame) nil)
;; Otherwise, hide the on screen keyboard now.
(frame-toggle-on-screen-keyboard (selected-frame) t))))))))))
(defun touch-screen-handle-touch (event)
"Handle a single touch EVENT, and perform associated actions.
@ -379,22 +406,34 @@ touch point with the same ID as in EVENT, call UPDATE with the
touch point in event and DATA.
Return nil immediately if any other kind of event is received;
otherwise, return t once the `touchscreen-end' event arrives."
(catch 'finish
(while t
(let ((new-event (read-event)))
(cond
((eq (car-safe new-event) 'touchscreen-update)
(let ((tool (assq (caadr event) (nth 1 new-event))))
(when (and update tool)
(funcall update new-event data))))
((eq (car-safe new-event) 'touchscreen-end)
(throw 'finish
;; Now determine whether or not the `touchscreen-end'
;; event has the same ID as EVENT. If it doesn't,
;; then this is another touch, so return nil.
(eq (caadr event) (caadr new-event))))
(t (throw 'finish nil)))))))
otherwise, return either t or `no-drag' once the
`touchscreen-end' event arrives; return `no-drag' returned if the
touch point in EVENT did not move significantly, and t otherwise."
(let ((return-value 'no-drag)
(start-xy (touch-screen-relative-xy (cdadr event)
'frame)))
(catch 'finish
(while t
(let ((new-event (read-event)))
(cond
((eq (car-safe new-event) 'touchscreen-update)
(when-let* ((tool (assq (caadr event) (nth 1 new-event)))
(xy (touch-screen-relative-xy (cdr tool) 'frame)))
(when (or (> (- (car xy) (car start-xy)) 5)
(< (- (car xy) (car start-xy)) -5)
(> (- (cdr xy) (cdr start-xy)) 5)
(< (- (cdr xy) (cdr start-xy)) -5))
(setq return-value t))
(when (and update tool)
(funcall update new-event data))))
((eq (car-safe new-event) 'touchscreen-end)
(throw 'finish
;; Now determine whether or not the `touchscreen-end'
;; event has the same ID as EVENT. If it doesn't,
;; then this is another touch, so return nil.
(and (eq (caadr event) (caadr new-event))
return-value)))
(t (throw 'finish nil))))))))
@ -402,45 +441,8 @@ otherwise, return t once the `touchscreen-end' event arrives."
(defun touch-screen-drag-mode-line-1 (event)
"Internal helper for `touch-screen-drag-mode-line'.
This is called when that function determines it need not execute
any keymaps on the mode line at that particular spot."
;; Find the window that should be dragged and the starting position.
(let* ((window (posn-window (cdadr event)))
(relative-xy (touch-screen-relative-xy
(cdadr event) window))
(last-position (cdr relative-xy)))
(when (window-resizable window 0)
(touch-screen-track-drag
event (lambda (new-event &optional _data)
;; Find the position of the touchpoint in NEW-EVENT.
(let* ((touchpoint (assq (caadr event) (cadr new-event)))
(new-relative-xy
(touch-screen-relative-xy (cdr touchpoint)
window))
(position (cdr new-relative-xy))
growth)
;; Now set the new height of the window.
;; If new-relative-y is above relative-xy, then
;; make the window that much shorter. Otherwise,
;; make it bigger.
(unless (or (zerop (setq growth (- position last-position)))
(and (> growth 0)
(< position (+ (window-pixel-top window)
(window-pixel-height window))))
(and (< growth 0)
(> position (+ (window-pixel-top window)
(window-pixel-height window)))))
(adjust-window-trailing-edge window growth nil t))
(setq last-position position)))))))
(defun touch-screen-drag-mode-line (event)
"Begin dragging the mode line in response to a touch EVENT.
If EVENT lies on top of text with a mouse command bound, run
that command instead.
Change the height of the window based on where the touch point
in EVENT moves."
(interactive "e")
This is called when that function determines that no drag really
happened. EVENT is the same as in `touch-screen-drag-mode-line'."
;; If there is an object at EVENT, then look either a keymap bound
;; to [down-mouse-1] or a command bound to [mouse-1]. Then, if a
;; keymap was found, pop it up as a menu. Otherwise, wait for a tap
@ -457,21 +459,66 @@ in EVENT moves."
(keymap (lookup-key object-keymap [mode-line down-mouse-1]))
(command (or (lookup-key object-keymap [mode-line mouse-1])
keymap)))
(if (or (keymapp keymap) command)
(if (keymapp keymap)
(when (touch-screen-track-tap event)
(when-let* ((command (x-popup-menu event keymap))
(tem (lookup-key keymap
(if (consp command)
(apply #'vector command)
(vector command))
t)))
(call-interactively tem)))
(when (and (commandp command)
(touch-screen-track-tap event))
(call-interactively command nil
(vector (list 'mouse-1 (cdadr event))))))
(touch-screen-drag-mode-line-1 event))))
(when (or (keymapp keymap) command)
(if (keymapp keymap)
(when-let* ((command (x-popup-menu event keymap))
(tem (lookup-key keymap
(if (consp command)
(apply #'vector command)
(vector command))
t)))
(call-interactively tem))
(when (commandp command)
(call-interactively command nil
(vector (list 'mouse-1 (cdadr event)))))))))
(defun touch-screen-drag-mode-line (event)
"Begin dragging the mode line in response to a touch EVENT.
Change the height of the window based on where the touch point in
EVENT moves.
If it does not actually move anywhere and the touch point is
removed, and EVENT lies on top of text with a mouse command
bound, run that command instead."
(interactive "e")
;; Find the window that should be dragged and the starting position.
(let* ((window (posn-window (cdadr event)))
(relative-xy (touch-screen-relative-xy
(cdadr event) window))
(last-position (cdr relative-xy)))
(when (window-resizable window 0)
(when (eq
(touch-screen-track-drag
event (lambda (new-event &optional _data)
;; Find the position of the touchpoint in
;; NEW-EVENT.
(let* ((touchpoint (assq (caadr event)
(cadr new-event)))
(new-relative-xy
(touch-screen-relative-xy (cdr touchpoint)
window))
(position (cdr new-relative-xy))
growth)
;; Now set the new height of the window. If
;; new-relative-y is above relative-xy, then
;; make the window that much shorter.
;; Otherwise, make it bigger.
(unless (or (zerop (setq growth
(- position last-position)))
(and (> growth 0)
(< position
(+ (window-pixel-top window)
(window-pixel-height window))))
(and (< growth 0)
(> position
(+ (window-pixel-top window)
(window-pixel-height window)))))
(adjust-window-trailing-edge window growth nil t))
(setq last-position position))))
'no-drag)
;; Dragging did not actually happen, so try to run any command
;; necessary.
(touch-screen-drag-mode-line-1 event)))))
(global-set-key [mode-line touchscreen-begin]
#'touch-screen-drag-mode-line)

View File

@ -121,6 +121,8 @@ struct android_emacs_window
{
jclass class;
jmethodID swap_buffers;
jmethodID toggle_on_screen_keyboard;
jmethodID window_updated;
};
/* The asset manager being used. */
@ -193,6 +195,10 @@ static struct android_emacs_drawable drawable_class;
/* Various methods associated with the EmacsWindow class. */
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;
/* Event handling functions. Events are stored on a (circular) queue
@ -1435,6 +1441,9 @@ android_init_emacs_window (void)
assert (window_class.c_name);
FIND_METHOD (swap_buffers, "swapBuffers", "()V");
FIND_METHOD (toggle_on_screen_keyboard,
"toggleOnScreenKeyboard", "(Z)V");
FIND_METHOD (window_updated, "windowUpdated", "(J)V");
#undef FIND_METHOD
}
@ -1497,7 +1506,7 @@ NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object)
emacs_abort ();
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint x, jint y, jint width,
@ -1506,6 +1515,7 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
union android_event event;
event.xconfigure.type = ANDROID_CONFIGURE_NOTIFY;
event.xconfigure.serial = ++event_serial;
event.xconfigure.window = window;
event.xconfigure.time = time;
event.xconfigure.x = x;
@ -1514,9 +1524,10 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
event.xconfigure.height = height;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode,
@ -1525,6 +1536,7 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
union android_event event;
event.xkey.type = ANDROID_KEY_PRESS;
event.xkey.serial = ++event_serial;
event.xkey.window = window;
event.xkey.time = time;
event.xkey.state = state;
@ -1532,9 +1544,10 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
event.xkey.unicode_char = unicode_char;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode,
@ -1543,6 +1556,7 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
union android_event event;
event.xkey.type = ANDROID_KEY_RELEASE;
event.xkey.serial = ++event_serial;
event.xkey.window = window;
event.xkey.time = time;
event.xkey.state = state;
@ -1550,48 +1564,55 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
event.xkey.unicode_char = unicode_char;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
jshort window, jlong time)
{
union android_event event;
event.xkey.type = ANDROID_FOCUS_IN;
event.xkey.window = window;
event.xkey.time = time;
event.xfocus.type = ANDROID_FOCUS_IN;
event.xfocus.serial = ++event_serial;
event.xfocus.window = window;
event.xfocus.time = time;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
jshort window, jlong time)
{
union android_event event;
event.xkey.type = ANDROID_FOCUS_OUT;
event.xkey.window = window;
event.xkey.time = time;
event.xfocus.type = ANDROID_FOCUS_OUT;
event.xfocus.serial = ++event_serial;
event.xfocus.window = window;
event.xfocus.time = time;
android_write_event (&event);
return ++event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
jshort window, jint action)
{
union android_event event;
event.xaction.type = ANDROID_WINDOW_ACTION;
event.xaction.serial = ++event_serial;
event.xaction.window = window;
event.xaction.action = action;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
@ -1599,15 +1620,17 @@ NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
union android_event event;
event.xcrossing.type = ANDROID_ENTER_NOTIFY;
event.xcrossing.serial = ++event_serial;
event.xcrossing.window = window;
event.xcrossing.x = x;
event.xcrossing.y = y;
event.xcrossing.time = time;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
@ -1615,15 +1638,17 @@ NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
union android_event event;
event.xcrossing.type = ANDROID_LEAVE_NOTIFY;
event.xcrossing.serial = ++event_serial;
event.xcrossing.window = window;
event.xcrossing.x = x;
event.xcrossing.y = y;
event.xcrossing.time = time;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
@ -1631,15 +1656,17 @@ NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
union android_event event;
event.xmotion.type = ANDROID_MOTION_NOTIFY;
event.xmotion.serial = ++event_serial;
event.xmotion.window = window;
event.xmotion.x = x;
event.xmotion.y = y;
event.xmotion.time = time;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
@ -1648,6 +1675,7 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
union android_event event;
event.xbutton.type = ANDROID_BUTTON_PRESS;
event.xbutton.serial = ++event_serial;
event.xbutton.window = window;
event.xbutton.x = x;
event.xbutton.y = y;
@ -1656,9 +1684,10 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
event.xbutton.button = button;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
@ -1667,6 +1696,7 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
union android_event event;
event.xbutton.type = ANDROID_BUTTON_RELEASE;
event.xbutton.serial = ++event_serial;
event.xbutton.window = window;
event.xbutton.x = x;
event.xbutton.y = y;
@ -1675,9 +1705,10 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
event.xbutton.button = button;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
@ -1685,6 +1716,7 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
union android_event event;
event.touch.type = ANDROID_TOUCH_DOWN;
event.touch.serial = ++event_serial;
event.touch.window = window;
event.touch.x = x;
event.touch.y = y;
@ -1692,9 +1724,10 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
event.touch.pointer_id = pointer_id;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
@ -1702,6 +1735,7 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
union android_event event;
event.touch.type = ANDROID_TOUCH_UP;
event.touch.serial = ++event_serial;
event.touch.window = window;
event.touch.x = x;
event.touch.y = y;
@ -1709,9 +1743,10 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
event.touch.pointer_id = pointer_id;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
@ -1719,6 +1754,7 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
union android_event event;
event.touch.type = ANDROID_TOUCH_MOVE;
event.touch.serial = ++event_serial;
event.touch.window = window;
event.touch.x = x;
event.touch.y = y;
@ -1726,9 +1762,10 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
event.touch.pointer_id = pointer_id;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
@ -1737,6 +1774,7 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
union android_event event;
event.wheel.type = ANDROID_WHEEL;
event.wheel.serial = ++event_serial;
event.wheel.window = window;
event.wheel.x = x;
event.wheel.y = y;
@ -1746,43 +1784,50 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
event.wheel.y_delta = y_delta;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
jshort window)
{
union android_event event;
event.iconified.type = ANDROID_ICONIFIED;
event.iconified.serial = ++event_serial;
event.iconified.window = window;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object,
jshort window)
{
union android_event event;
event.iconified.type = ANDROID_DEICONIFIED;
event.iconified.serial = ++event_serial;
event.iconified.window = window;
android_write_event (&event);
return event_serial;
}
extern JNIEXPORT void JNICALL
extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object,
jshort window, jint menu_event_id)
{
union android_event event;
event.menu.type = ANDROID_CONTEXT_MENU;
event.menu.serial = ++event_serial;
event.menu.window = window;
event.menu.menu_event_id = menu_event_id;
android_write_event (&event);
return event_serial;
}
#ifdef __clang__
@ -3599,6 +3644,15 @@ android_translate_coordinates (android_window src, int x,
ANDROID_DELETE_LOCAL_REF (coordinates);
}
void
android_sync (void)
{
(*android_java_env)->CallVoidMethod (android_java_env,
emacs_service,
service_class.sync);
android_exception_check ();
}
/* Low level drawing primitives. */
@ -3795,12 +3849,45 @@ android_get_keysym_name (int keysym, char *name_return, size_t size)
ANDROID_DELETE_LOCAL_REF (string);
}
/* Display the on screen keyboard on window WINDOW, or hide it if SHOW
is false. Ask the system to bring up or hide the on-screen
keyboard on behalf of WINDOW. The request may be rejected by the
system, especially when the window does not have the input
focus. */
void
android_sync (void)
android_toggle_on_screen_keyboard (android_window window, bool show)
{
(*android_java_env)->CallVoidMethod (android_java_env,
emacs_service,
service_class.sync);
jobject object;
jmethodID method;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
method = window_class.toggle_on_screen_keyboard;
/* Now display the on screen keyboard. */
(*android_java_env)->CallVoidMethod (android_java_env, object,
method, (jboolean) show);
/* Check for out of memory errors. */
android_exception_check ();
}
/* Tell the window system that all configure events sent to WINDOW
have been fully processed, and that it is now okay to display its
new contents. SERIAL is the serial of the last configure event
processed. */
void
android_window_updated (android_window window, unsigned long serial)
{
jobject object;
jmethodID method;
object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
method = window_class.window_updated;
(*android_java_env)->CallVoidMethod (android_java_env, object,
method, (jlong) serial);
android_exception_check ();
}
@ -3811,7 +3898,7 @@ android_sync (void)
#undef faccessat
/* Replace the system faccessat with one which understands AT_EACCESS.
Android's faccessat simply fails upon using AT_EACCESS, so repalce
Android's faccessat simply fails upon using AT_EACCESS, so replace
it with zero here. This isn't caught during configuration.
This replacement is only done when building for Android 17 or
@ -3829,7 +3916,7 @@ faccessat (int dirfd, const char *pathname, int mode, int flags)
return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS);
}
#endif /* __ANDROID_API__ < 16 */
#endif /* __ANDROID_API__ >= 17 */

View File

@ -91,6 +91,8 @@ extern void android_exception_check (void);
extern void android_get_keysym_name (int, char *, size_t);
extern void android_wait_event (void);
extern void android_toggle_on_screen_keyboard (android_window, bool);
extern void android_window_updated (android_window, unsigned long);

View File

@ -2332,6 +2332,30 @@ there is no mouse. */)
#endif
}
DEFUN ("android-toggle-on-screen-keyboard",
Fandroid_toggle_on_screen_keyboard,
Sandroid_toggle_on_screen_keyboard, 2, 2, 0,
doc: /* Display or hide the on-screen keyboard.
If HIDE is non-nil, hide the on screen keyboard if it is currently
being displayed. Else, request that the system display it on behalf
of FRAME. This request may be rejected if FRAME does not have the
input focus. */)
(Lisp_Object frame, Lisp_Object hide)
{
#ifndef ANDROID_STUBIFY
struct frame *f;
f = decode_window_system_frame (frame);
block_input ();
android_toggle_on_screen_keyboard (FRAME_ANDROID_WINDOW (f),
NILP (hide));
unblock_input ();
#endif
return Qnil;
}
#ifndef ANDROID_STUBIFY
@ -2781,6 +2805,7 @@ syms_of_androidfns (void)
defsubr (&Sx_show_tip);
defsubr (&Sx_hide_tip);
defsubr (&Sandroid_detect_mouse);
defsubr (&Sandroid_toggle_on_screen_keyboard);
#ifndef ANDROID_STUBIFY
tip_timer = Qnil;

View File

@ -239,6 +239,7 @@ enum android_event_type
struct android_any_event
{
enum android_event_type type;
unsigned long serial;
android_window window;
};
@ -252,6 +253,7 @@ enum android_modifier_mask
struct android_key_event
{
enum android_event_type type;
unsigned long serial;
android_window window;
android_time time;
unsigned int state;
@ -271,6 +273,7 @@ struct android_key_event
struct android_configure_event
{
enum android_event_type type;
unsigned long serial;
android_window window;
android_time time;
int x, y;
@ -280,6 +283,7 @@ struct android_configure_event
struct android_focus_event
{
enum android_event_type type;
unsigned long serial;
android_window window;
android_time time;
};
@ -287,6 +291,7 @@ struct android_focus_event
struct android_window_action_event
{
enum android_event_type type;
unsigned long serial;
/* The window handle. This can be ANDROID_NONE. */
android_window window;
@ -301,6 +306,7 @@ struct android_window_action_event
struct android_crossing_event
{
enum android_event_type type;
unsigned long serial;
android_window window;
int x, y;
unsigned long time;
@ -309,6 +315,7 @@ struct android_crossing_event
struct android_motion_event
{
enum android_event_type type;
unsigned long serial;
android_window window;
int x, y;
unsigned long time;
@ -317,6 +324,7 @@ struct android_motion_event
struct android_button_event
{
enum android_event_type type;
unsigned long serial;
android_window window;
int x, y;
unsigned long time;
@ -329,6 +337,9 @@ struct android_touch_event
/* Type of the event. */
enum android_event_type type;
/* Serial identifying the event. */
unsigned long serial;
/* Window associated with the event. */
android_window window;
@ -347,6 +358,9 @@ struct android_wheel_event
/* Type of the event. */
enum android_event_type type;
/* Serial identifying the event. */
unsigned long serial;
/* Window associated with the event. */
android_window window;
@ -368,6 +382,9 @@ struct android_iconify_event
/* Type of the event. */
enum android_event_type type;
/* Serial identifying the event. */
unsigned long serial;
/* Window associated with the event. */
android_window window;
};
@ -377,6 +394,9 @@ struct android_menu_event
/* Type of the event. */
enum android_event_type type;
/* Serial identifying the event. */
unsigned long serial;
/* Window associated with the event. Always None. */
android_window window;

View File

@ -591,7 +591,17 @@ handle_one_android_event (struct android_display_info *dpyinfo,
android_clear_under_internal_border (f);
SET_FRAME_GARBAGED (f);
cancel_mouse_face (f);
/* Now stash the serial of this configure event somewhere,
and call android_window_updated with it once the redraw
completes. */
FRAME_OUTPUT_DATA (f)->last_configure_serial
= configureEvent.xconfigure.serial;
}
else
/* Reply to this ConfigureNotify event immediately. */
android_window_updated (FRAME_ANDROID_WINDOW (f),
configureEvent.xconfigure.serial);
goto OTHER;
@ -1194,6 +1204,9 @@ handle_one_android_event (struct android_display_info *dpyinfo,
/* Iconification. This is vastly simpler than on X. */
case ANDROID_ICONIFIED:
if (!any)
goto OTHER;
if (FRAME_ICONIFIED_P (any))
goto OTHER;
@ -1206,6 +1219,9 @@ handle_one_android_event (struct android_display_info *dpyinfo,
case ANDROID_DEICONIFIED:
if (!any)
goto OTHER;
if (!FRAME_ICONIFIED_P (any))
goto OTHER;
@ -1311,6 +1327,14 @@ android_frame_up_to_date (struct frame *f)
/* The frame is now complete, as its contents have been drawn. */
FRAME_ANDROID_COMPLETE_P (f) = true;
/* If there was an outstanding configure event, then tell system
that the update has finished and the new contents can now be
displayed. */
if (FRAME_OUTPUT_DATA (f)->last_configure_serial)
android_window_updated (FRAME_ANDROID_WINDOW (f),
FRAME_OUTPUT_DATA (f)->last_configure_serial);
FRAME_OUTPUT_DATA (f)->last_configure_serial = 0;
/* Shrink the scanline buffer used by the font backend. */
sfntfont_android_shrink_scanline_buffer ();
unblock_input ();

View File

@ -241,6 +241,11 @@ struct android_output
/* List of all tools (either styluses or fingers) pressed onto the
frame. */
struct android_touch_point *touch_points;
/* Event serial of the last ConfigureNotify event received that has
not yet been drawn. This is used to synchronize resize with the
window system. 0 if no such outstanding event exists. */
unsigned long last_configure_serial;
};
enum