1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-15 10:30:25 -08:00

Update Android port

* java/Makefile.in (clean): Fix distclean and bootstrap-clean rules.
* java/debug.sh (jdb_port):
(attach_existing):
(num_pids):
(line): Add new options to upload a gdbserver binary to the device.

* java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): Make
focusedActivities public.
* java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu):
New class.
* java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Fix
bounds computation.
* java/org/gnu/emacs/EmacsGC.java (markDirty): Set stroke width
explicitly.
* java/org/gnu/emacs/EmacsService.java (EmacsService)
(getLocationOnScreen, nameKeysym): New functions.
* java/org/gnu/emacs/EmacsView.java (EmacsView): Disable focus
highlight.
(onCreateContextMenu, popupMenu, cancelPopupMenu): New
functions.
* java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): Implement a
kind of ``override redirect'' window for tooltips.
* src/android.c (struct android_emacs_service): New method
`name_keysym'.
(android_run_select_thread, android_init_events):
(android_select): Release select thread on semaphores instead of
signals to avoid one nasty race on SIGUSR2 delivery.
(android_init_emacs_service): Initialize new method.
(android_create_window): Handle CW_OVERRIDE_REDIRECT.
(android_move_resize_window, android_map_raised)
(android_translate_coordinates, android_get_keysym_name)
(android_build_string, android_exception_check): New functions.
* src/android.h: Update prototypes.

* src/androidfns.c (android_set_parent_frame, Fx_create_frame)
(unwind_create_tip_frame, android_create_tip_frame)
(android_hide_tip, compute_tip_xy, Fx_show_tip, Fx_hide_tip)
(syms_of_androidfns): Implement tooltips and iconification
reporting.

* src/androidgui.h (enum android_window_value_mask): Add
CWOverrideRedirect.
(struct android_set_window_attributes): Add `override_redirect'.
(ANDROID_IS_MODIFIER_KEY): Recognize Caps Lock.

* src/androidmenu.c (struct android_emacs_context_menu): New
struct.
(android_init_emacs_context_menu, android_unwind_local_frame)
(android_push_local_frame, android_menu_show, init_androidmenu):
New functions.

* src/androidterm.c (handle_one_android_event): Fix NULL pointer
dereference.
(android_fullscreen_hook): Handle fullscreen correctly.
(android_draw_box_rect): Fix top line.
(get_keysym_name): Implement function.
(android_create_terminal): Remove scroll bar stubs and add menu
hook.

* src/androidterm.h: Update prototypes.
* src/emacs.c (android_emacs_init): Initialize androidmenu.c.
* xcompile/Makefile.in: Fix clean rules.
This commit is contained in:
Po Lu 2023-01-14 22:12:16 +08:00
parent 28a9baccd4
commit 2b87ab7b27
18 changed files with 1683 additions and 110 deletions

View file

@ -168,4 +168,4 @@ clean:
rm -rf install-temp
find . -name '*.class' -delete
maintainer-clean: clean
maintainer-clean distclean bootstrap-clean: clean

View file

@ -31,6 +31,7 @@ gdb_port=5039
jdb_port=64013
jdb=no
attach_existing=no
gdbserver=
while [ $# -gt 0 ]; do
case "$1" in
@ -41,6 +42,7 @@ while [ $# -gt 0 ]; do
echo "You must specify an argument to --device"
exit 1
fi
shift
;;
"--help" )
echo "Usage: $progname [options] -- [gdb options]"
@ -50,6 +52,7 @@ while [ $# -gt 0 ]; do
echo " --jdb-port PORT run the JDB server on a specific port"
echo " --jdb run JDB instead of GDB"
echo " --attach-existing attach to an existing process"
echo " --gdbserver BINARY upload and use the specified gdbserver binary"
echo " --help print this message"
echo ""
echo "Available devices:"
@ -62,9 +65,18 @@ while [ $# -gt 0 ]; do
"--jdb" )
jdb=yes
;;
"--gdbserver" )
shift
gdbserver=$1
;;
"--port" )
shift
gdb_port=$1
;;
"--jdb-port" )
shift
jdb_port=$1
;;
"--attach-existing" )
attach_existing=yes
;;
@ -170,46 +182,71 @@ elif [ -z $package_pids ]; then
exit 1
fi
# Start JDB to make the wait dialog disappear.
echo "Attaching JDB to unblock the application."
adb -s $device forward --remove-all
adb -s $device forward "tcp:$jdb_port" "jdwp:$pid"
# This isn't necessary when attaching gdb to an existing process.
if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then
# Start JDB to make the wait dialog disappear.
echo "Attaching JDB to unblock the application."
adb -s $device forward --remove-all
adb -s $device forward "tcp:$jdb_port" "jdwp:$pid"
if [ ! $? ]; then
if [ ! $? ]; then
echo "Failed to forward jdwp:$pid to $jdb_port!"
echo "Perhaps you need to specify a different port with --port?"
exit 1;
fi
fi
jdb_command="jdb -connect \
jdb_command="jdb -connect \
com.sun.jdi.SocketAttach:hostname=localhost,port=$jdb_port"
if [ $jdb = "yes" ]; then
if [ $jdb = "yes" ]; then
# Just start JDB and then exit
$jdb_command
exit 1
fi
fi
exec 4<> /tmp/file-descriptor-stamp
exec 4<> /tmp/file-descriptor-stamp
# Now run JDB with IO redirected to file descriptor 4 in a subprocess.
$jdb_command <&4 >&4 &
# Now run JDB with IO redirected to file descriptor 4 in a subprocess.
$jdb_command <&4 >&4 &
character=
# Next, wait until the prompt is found.
while read -n1 -u 4 character; do
character=
# Next, wait until the prompt is found.
while read -n1 -u 4 character; do
if [ "$character" = ">" ]; then
echo "JDB attached successfully"
break;
fi
done
done
fi
# See if gdbserver has to be uploaded
if [ -z "$gdbserver" ]; then
gdbserver_bin=/system/bin/gdbserver
else
gdbserver_bin=/data/local/tmp/gdbserver
# Upload the specified gdbserver binary to the device.
adb -s $device push "$gdbserver" "$gdbserver_bin"
adb -s $device shell chmod +x "$gdbserver_bin"
fi
# Now start gdbserver on the device asynchronously.
echo "Attaching gdbserver to $pid on $device..."
exec 5<> /tmp/file-descriptor-stamp
adb -s $device shell run-as $package /system/bin/gdbserver --once \
if [ -z "$gdbserver" ]; then
adb -s $device shell run-as $package $gdbserver_bin --once \
"+debug.$package_uid.socket" --attach $pid >&5 &
gdb_socket="localfilesystem:$app_data_dir/debug.$package_uid.socket"
else
# Normally the program cannot access $gdbserver_bin when it is
# placed in /data/local/tmp.
adb -s $device shell $gdbserver_bin --once \
"+/data/local/tmp/debug.$package_uid.socket" \
--attach $pid >&5 &
gdb_socket="localfilesystem:/data/local/tmp/debug.$package_uid.socket"
fi
# Wait until gdbserver successfully runs.
line=
@ -227,16 +264,17 @@ while read -u 5 line; do
esac
done
# Send EOF to JDB to make it go away. This will also cause Android to
# allow Emacs to continue executing.
echo "Making JDB go away..."
echo "exit" >&4
read -u 4 line
echo "JDB has gone away with $line"
if [ "$attach_existing" != "yes" ]; then
# Send EOF to JDB to make it go away. This will also cause
# Android to allow Emacs to continue executing.
echo "Making JDB go away..."
echo "exit" >&4
read -u 4 line
echo "JDB has gone away with $line"
fi
# Forward the gdb server port here.
adb -s $device forward "tcp:$gdb_port" \
"localfilesystem:$app_data_dir/debug.$package_uid.socket"
adb -s $device forward "tcp:$gdb_port" $gdb_socket
if [ ! $? ]; then
echo "Failed to forward $app_data_dir/debug.$package_uid.socket"
echo "to $gdb_port! Perhaps you need to specify a different port"

View file

@ -43,7 +43,7 @@ public class EmacsActivity extends Activity
private FrameLayout layout;
/* List of activities with focus. */
private static List<EmacsActivity> focusedActivities;
public static List<EmacsActivity> focusedActivities;
/* The currently focused window. */
public static EmacsWindow focusedWindow;

View file

@ -0,0 +1,213 @@
/* 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 java.util.List;
import java.util.ArrayList;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.PopupMenu;
/* Context menu implementation. This object is built from JNI and
describes a menu hiearchy. Then, `inflate' can turn it into an
Android menu, which can be turned into a popup (or other kind of)
menu. */
public class EmacsContextMenu
{
private class Item
{
public int itemID;
public String itemName;
public EmacsContextMenu subMenu;
public boolean isEnabled;
};
public List<Item> menuItems;
public String title;
private EmacsContextMenu parent;
/* Create a context menu with no items inside and the title TITLE,
which may be NULL. */
public static EmacsContextMenu
createContextMenu (String title)
{
EmacsContextMenu menu;
menu = new EmacsContextMenu ();
menu.menuItems = new ArrayList<Item> ();
menu.title = title;
return menu;
}
/* Add a normal menu item to the context menu with the id ITEMID and
the name ITEMNAME. Enable it if ISENABLED, else keep it
disabled. */
public void
addItem (int itemID, String itemName, boolean isEnabled)
{
Item item;
item = new Item ();
item.itemID = itemID;
item.itemName = itemName;
item.isEnabled = isEnabled;
menuItems.add (item);
}
/* Create a disabled menu item with the name ITEMNAME. */
public void
addPane (String itemName)
{
Item item;
item = new Item ();
item.itemName = itemName;
menuItems.add (item);
}
/* Add a submenu to the context menu with the specified title and
item name. */
public EmacsContextMenu
addSubmenu (String itemName, String title)
{
EmacsContextMenu submenu;
Item item;
item = new Item ();
item.itemID = 0;
item.itemName = itemName;
item.subMenu = createContextMenu (title);
item.subMenu.parent = this;
menuItems.add (item);
return item.subMenu;
}
/* Add the contents of this menu to MENU. */
private void
inflateMenuItems (Menu menu)
{
Intent intent;
MenuItem menuItem;
Menu submenu;
for (Item item : menuItems)
{
if (item.subMenu != null)
{
/* This is a submenu. Create the submenu and add the
contents of the menu to it. */
submenu = menu.addSubMenu (item.itemName);
inflateMenuItems (submenu);
}
else
{
menuItem = menu.add (item.itemName);
/* If the item ID is zero, then disable the item. */
if (item.itemID == 0 || !item.isEnabled)
menuItem.setEnabled (false);
}
}
}
/* Enter the items in this context menu to MENU. Create each menu
item with an Intent containing a Bundle, where the key
"emacs:menu_item_hi" maps to the high 16 bits of the
corresponding item ID, and the key "emacs:menu_item_low" maps to
the low 16 bits of the item ID. */
public void
expandTo (Menu menu)
{
inflateMenuItems (menu);
}
/* Return the parent or NULL. */
public EmacsContextMenu
parent ()
{
return parent;
}
/* Like display, but does the actual work and runs in the main
thread. */
private boolean
display1 (EmacsWindow window, int xPosition, int yPosition)
{
return window.view.popupMenu (this, xPosition, yPosition);
}
/* Display this context menu on WINDOW, at xPosition and
yPosition. */
public boolean
display (final EmacsWindow window, final int xPosition,
final int yPosition)
{
Runnable runnable;
final Holder<Boolean> rc;
rc = new Holder<Boolean> ();
runnable = new Runnable () {
@Override
public void
run ()
{
synchronized (this)
{
rc.thing = display1 (window, xPosition, yPosition);
notify ();
}
}
};
try
{
runnable.wait ();
}
catch (InterruptedException e)
{
EmacsNative.emacsAbort ();
}
return rc.thing;
}
};

View file

@ -59,7 +59,7 @@ public class EmacsDrawRectangle
}
paint = gc.gcPaint;
rect = new Rect (x, y, x + width, y + height);
rect = new Rect (x + 1, y + 1, x + width, y + height);
paint.setStyle (Paint.Style.STROKE);

View file

@ -93,6 +93,7 @@ public class EmacsGC extends EmacsHandleObject
else
real_clip_rects = clip_rects;
gcPaint.setStrokeWidth (1f);
gcPaint.setColor (foreground | 0xff000000);
gcPaint.setXfermode (function == GC_XOR
? xorAlu : srcInAlu);

View file

@ -29,6 +29,7 @@ import android.graphics.Point;
import android.view.View;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.annotation.TargetApi;
import android.app.Service;
@ -150,13 +151,13 @@ public class EmacsService extends Service
/* Functions from here on must only be called from the Emacs
thread. */
void
public void
runOnUiThread (Runnable runnable)
{
handler.post (runnable);
}
EmacsView
public EmacsView
getEmacsView (final EmacsWindow window, final int visibility,
final boolean isFocusedByDefault)
{
@ -196,6 +197,38 @@ public class EmacsService extends Service
return view.thing;
}
public void
getLocationOnScreen (final EmacsView view, final int[] coordinates)
{
Runnable runnable;
runnable = new Runnable () {
public void
run ()
{
synchronized (this)
{
view.getLocationOnScreen (coordinates);
notify ();
}
}
};
synchronized (runnable)
{
runOnUiThread (runnable);
try
{
runnable.wait ();
}
catch (InterruptedException e)
{
EmacsNative.emacsAbort ();
}
}
}
public void
fillRectangle (EmacsDrawable drawable, EmacsGC gc,
int x, int y, int width, int height)
@ -368,4 +401,10 @@ public class EmacsService extends Service
return false;
}
public String
nameKeysym (int keysym)
{
return KeyEvent.keyCodeToString (keysym);
}
};

View file

@ -21,6 +21,7 @@ package org.gnu.emacs;
import android.content.res.ColorStateList;
import android.view.ContextMenu;
import android.view.View;
import android.view.KeyEvent;
import android.view.MotionEvent;
@ -73,6 +74,12 @@ public class EmacsView extends ViewGroup
next call to getBitmap. */
private Rect bitmapDirty;
/* Whether or not a popup is active. */
private boolean popupActive;
/* The current context menu. */
private EmacsContextMenu contextMenu;
public
EmacsView (EmacsWindow window)
{
@ -98,6 +105,10 @@ public class EmacsView extends ViewGroup
/* Get rid of the foreground and background tint. */
setBackgroundTintList (null);
setForegroundTintList (null);
/* Get rid of the default focus highlight. */
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
setDefaultFocusHighlightEnabled (false);
}
private void
@ -423,4 +434,40 @@ public class EmacsView extends ViewGroup
removeView (surfaceView);
addView (surfaceView, 0);
}
@Override
protected void
onCreateContextMenu (ContextMenu menu)
{
if (contextMenu == null)
return;
contextMenu.expandTo (menu);
}
public boolean
popupMenu (EmacsContextMenu menu, int xPosition,
int yPosition)
{
if (popupActive)
return false;
contextMenu = menu;
/* On API 21 or later, use showContextMenu (float, float). */
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP)
return showContextMenu ((float) xPosition, (float) yPosition);
else
return showContextMenu ();
}
public void
cancelPopupMenu ()
{
if (!popupActive)
throw new IllegalStateException ("cancelPopupMenu called without"
+ " popupActive set");
contextMenu = null;
}
};

View file

@ -24,16 +24,22 @@ import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Canvas;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PixelFormat;
import android.view.View;
import android.view.ViewManager;
import android.view.ViewGroup;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.InputDevice;
import android.view.WindowManager;
import android.content.Intent;
import android.util.Log;
@ -110,9 +116,17 @@ public class EmacsWindow extends EmacsHandleObject
not the window should be focusable. */
private boolean dontFocusOnMap, dontAcceptFocus;
/* Whether or not the window is override-redirect. An
override-redirect window always has its own system window. */
private boolean overrideRedirect;
/* The window manager that is the parent of this window. NULL if
there is no such window manager. */
private WindowManager windowManager;
public
EmacsWindow (short handle, final EmacsWindow parent, int x, int y,
int width, int height)
int width, int height, boolean overrideRedirect)
{
super (handle);
@ -124,6 +138,7 @@ public class EmacsWindow extends EmacsHandleObject
view = EmacsService.SERVICE.getEmacsView (this, View.GONE,
parent == null);
this.parent = parent;
this.overrideRedirect = overrideRedirect;
/* Create the list of children. */
children = new ArrayList<EmacsWindow> ();
@ -180,7 +195,7 @@ public class EmacsWindow extends EmacsHandleObject
public void
run ()
{
View parent;
ViewManager parent;
EmacsWindowAttachmentManager manager;
if (EmacsActivity.focusedWindow == EmacsWindow.this)
@ -189,10 +204,15 @@ public class EmacsWindow extends EmacsHandleObject
manager = EmacsWindowAttachmentManager.MANAGER;
view.setVisibility (View.GONE);
parent = (View) view.getParent ();
/* If the window manager is set, use that instead. */
if (windowManager != null)
parent = windowManager;
else
parent = (ViewManager) view.getParent ();
windowManager = null;
if (parent != null)
((ViewGroup) parent).removeView (view);
parent.removeView (view);
manager.detachWindow (EmacsWindow.this);
}
@ -247,6 +267,10 @@ public class EmacsWindow extends EmacsHandleObject
public void
run ()
{
if (overrideRedirect)
/* Set the layout parameters again. */
view.setLayoutParams (getWindowLayoutParams ());
view.mustReportLayout = true;
view.requestLayout ();
}
@ -284,6 +308,39 @@ public class EmacsWindow extends EmacsHandleObject
}
}
private WindowManager.LayoutParams
getWindowLayoutParams ()
{
WindowManager.LayoutParams params;
int flags, type;
Rect rect;
flags = 0;
rect = getGeometry ();
flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
params
= new WindowManager.LayoutParams (rect.width (), rect.height (),
rect.left, rect.top,
type, flags,
PixelFormat.RGBA_8888);
params.gravity = Gravity.TOP | Gravity.LEFT;
return params;
}
private Context
findSuitableActivityContext ()
{
/* Find a recently focused activity. */
if (!EmacsActivity.focusedActivities.isEmpty ())
return EmacsActivity.focusedActivities.get (0);
/* Return the service context, which probably won't work. */
return EmacsService.SERVICE;
}
public void
mapWindow ()
{
@ -300,10 +357,16 @@ public class EmacsWindow extends EmacsHandleObject
run ()
{
EmacsWindowAttachmentManager manager;
WindowManager windowManager;
Context ctx;
Object tem;
WindowManager.LayoutParams params;
/* Make the view visible, first of all. */
view.setVisibility (View.VISIBLE);
if (!overrideRedirect)
{
manager = EmacsWindowAttachmentManager.MANAGER;
/* If parent is the root window, notice that there are new
@ -315,6 +378,40 @@ public class EmacsWindow extends EmacsHandleObject
/* Eventually this should check no-focus-on-map. */
view.requestFocus ();
}
else
{
/* But if the window is an override-redirect window,
then:
- Find an activity that is currently active.
- Map the window as a panel on top of that
activity using the system window manager. */
ctx = findSuitableActivityContext ();
tem = ctx.getSystemService (Context.WINDOW_SERVICE);
windowManager = (WindowManager) tem;
/* Calculate layout parameters. */
params = getWindowLayoutParams ();
view.setLayoutParams (params);
/* Attach the view. */
try
{
windowManager.addView (view, params);
/* Record the window manager being used in the
EmacsWindow object. */
EmacsWindow.this.windowManager = windowManager;
}
catch (Exception e)
{
Log.w (TAG,
"failed to attach override-redirect window, " + e);
}
}
}
});
}
else
@ -355,6 +452,11 @@ public class EmacsWindow extends EmacsHandleObject
view.setVisibility (View.GONE);
/* Detach the view from the window manager if possible. */
if (windowManager != null)
windowManager.removeView (view);
windowManager = null;
/* Now that the window is unmapped, unregister it as
well. */
manager.detachWindow (EmacsWindow.this);
@ -756,17 +858,23 @@ public class EmacsWindow extends EmacsHandleObject
run ()
{
EmacsWindowAttachmentManager manager;
View parent;
ViewManager parent;
/* First, detach this window if necessary. */
manager = EmacsWindowAttachmentManager.MANAGER;
manager.detachWindow (EmacsWindow.this);
/* Also unparent this view. */
parent = (View) view.getParent ();
/* If the window manager is set, use that instead. */
if (windowManager != null)
parent = windowManager;
else
parent = (ViewManager) view.getParent ();
windowManager = null;
if (parent != null)
((ViewGroup) parent).removeView (view);
parent.removeView (view);
/* Next, either add this window as a child of the new
parent's view, or make it available again. */
@ -899,4 +1007,23 @@ public class EmacsWindow extends EmacsHandleObject
{
return dontFocusOnMap;
}
public int[]
translateCoordinates (int x, int y)
{
int[] array;
/* This is supposed to translate coordinates to the root
window. */
array = new int[2];
EmacsService.SERVICE.getLocationOnScreen (view, array);
/* Now, the coordinates of the view should be in array. Offset X
and Y by them. */
array[0] += x;
array[1] += y;
/* Return the resulting coordinates. */
return array;
}
};

View file

@ -87,6 +87,7 @@ struct android_emacs_service
jmethodID get_screen_width;
jmethodID get_screen_height;
jmethodID detect_mouse;
jmethodID name_keysym;
};
struct android_emacs_pixmap
@ -229,14 +230,14 @@ static volatile bool android_pselect_completed;
/* The global event queue. */
static struct android_event_queue event_queue;
/* Semaphore used to signal select completion. */
static sem_t android_pselect_sem;
/* Semaphores used to signal select completion and start. */
static sem_t android_pselect_sem, android_pselect_start_sem;
static void *
android_run_select_thread (void *data)
{
sigset_t signals;
int sig, rc;
int rc;
sigfillset (&signals);
@ -245,23 +246,10 @@ android_run_select_thread (void *data)
"pthread_sigmask: %s",
strerror (errno));
sigemptyset (&signals);
sigaddset (&signals, SIGUSR1);
if (pthread_sigmask (SIG_UNBLOCK, &signals, NULL))
__android_log_print (ANDROID_LOG_FATAL, __func__,
"pthread_sigmask: %s",
strerror (errno));
sigemptyset (&signals);
sigaddset (&signals, SIGUSR2);
while (true)
{
/* Keep waiting for SIGUSR2, ignoring EINTR in the meantime. */
while (sigwait (&signals, &sig))
/* Spin. */;
/* Wait for the thread to be released. */
sem_wait (&android_pselect_start_sem);
/* Get the select lock and call pselect. */
pthread_mutex_lock (&event_queue.select_mutex);
@ -322,6 +310,7 @@ android_init_events (void)
strerror (errno));
sem_init (&android_pselect_sem, 0, 0);
sem_init (&android_pselect_start_sem, 0, 0);
event_queue.events.next = &event_queue.events;
event_queue.events.last = &event_queue.events;
@ -444,7 +433,9 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds,
android_pselect_sigset = sigset;
pthread_mutex_unlock (&event_queue.select_mutex);
pthread_kill (event_queue.select_thread, SIGUSR2);
/* Release the select thread. */
sem_post (&android_pselect_start_sem);
pthread_cond_wait (&event_queue.read_var, &event_queue.mutex);
/* Interrupt the select thread now, in case it's still in
@ -1058,6 +1049,7 @@ android_init_emacs_service (void)
FIND_METHOD (get_screen_width, "getScreenWidth", "(Z)I");
FIND_METHOD (get_screen_height, "getScreenHeight", "(Z)I");
FIND_METHOD (detect_mouse, "detectMouse", "()Z");
FIND_METHOD (name_keysym, "nameKeysym", "(I)Ljava/lang/String;");
#undef FIND_METHOD
}
@ -1678,6 +1670,7 @@ android_create_window (android_window parent, int x, int y,
jobject object, parent_object, old;
android_window window;
android_handle prev_max_handle;
bool override_redirect;
parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW);
@ -1695,7 +1688,8 @@ android_create_window (android_window parent, int x, int y,
constructor
= (*android_java_env)->GetMethodID (android_java_env, class, "<init>",
"(SLorg/gnu/emacs/EmacsWindow;IIII)V");
"(SLorg/gnu/emacs/EmacsWindow;"
"IIIIZ)V");
assert (constructor != NULL);
old = class;
@ -1707,10 +1701,17 @@ android_create_window (android_window parent, int x, int y,
memory_full (0);
}
/* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window
creation time. */
override_redirect = ((value_mask
& ANDROID_CW_OVERRIDE_REDIRECT)
&& attrs->override_redirect);
object = (*android_java_env)->NewObject (android_java_env, class,
constructor, (jshort) window,
parent_object, (jint) x, (jint) y,
(jint) width, (jint) height);
(jint) width, (jint) height,
(jboolean) override_redirect);
if (!object)
{
(*android_java_env)->ExceptionClear (android_java_env);
@ -3212,6 +3213,66 @@ android_get_geometry (android_window handle,
ANDROID_DELETE_LOCAL_REF (window_geometry);
}
void
android_move_resize_window (android_window window, int x, int y,
unsigned int width, unsigned int height)
{
android_move_window (window, x, y);
android_resize_window (window, width, height);
}
void
android_map_raised (android_window window)
{
android_raise_window (window);
android_map_window (window);
}
void
android_translate_coordinates (android_window src, int x,
int y, int *root_x, int *root_y)
{
jobject window;
jarray coordinates;
jmethodID method;
jint *ints;
window = android_resolve_handle (src, ANDROID_HANDLE_WINDOW);
method = android_lookup_method ("org/gnu/emacs/EmacsWindow",
"translateCoordinates",
"(II)[I");
coordinates
= (*android_java_env)->CallObjectMethod (android_java_env,
window, method,
(jint) x, (jint) y);
if (!coordinates)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
/* The array must contain two elements: X, Y translated to the root
window. */
eassert ((*android_java_env)->GetArrayLength (android_java_env,
coordinates)
== 2);
/* Obtain the coordinates from the array. */
ints = (*android_java_env)->GetIntArrayElements (android_java_env,
coordinates, NULL);
*root_x = ints[0];
*root_y = ints[1];
/* Release the coordinates. */
(*android_java_env)->ReleaseIntArrayElements (android_java_env,
coordinates, ints,
JNI_ABORT);
/* And free the local reference. */
ANDROID_DELETE_LOCAL_REF (coordinates);
}
/* Low level drawing primitives. */
@ -3384,6 +3445,30 @@ android_set_dont_accept_focus (android_window handle,
(jboolean) no_accept_focus);
}
void
android_get_keysym_name (int keysym, char *name_return, size_t size)
{
jobject string;
const char *buffer;
string = (*android_java_env)->CallObjectMethod (android_java_env,
emacs_service,
service_class.name_keysym,
(jint) keysym);
android_exception_check ();
buffer = (*android_java_env)->GetStringUTFChars (android_java_env,
(jstring) string,
NULL);
android_exception_check ();
strncpy (name_return, buffer, size - 1);
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
(jstring) string,
buffer);
ANDROID_DELETE_LOCAL_REF (string);
}
#undef faccessat
@ -3525,6 +3610,48 @@ emacs_abort (void)
abort ();
}
/* Given a Lisp string TEXT, return a local reference to an equivalent
Java string. */
jstring
android_build_string (Lisp_Object text)
{
Lisp_Object encoded;
jstring string;
encoded = ENCODE_UTF_8 (text);
/* Note that Java expects this string to be in ``modified UTF
encoding'', which is actually UTF-8, except with NUL encoded as a
two-byte sequence. The only consequence of passing an actual
UTF-8 string is that NUL bytes cannot be represented, which is
not really of consequence. */
string = (*android_java_env)->NewStringUTF (android_java_env,
SSDATA (encoded));
if (!string)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
return string;
}
/* Check for JNI exceptions and call memory_full in that
situation. */
void
android_exception_check (void)
{
if ((*android_java_env)->ExceptionCheck (android_java_env))
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
}
#else /* ANDROID_STUBIFY */
/* X emulation functions for Android. */

View file

@ -35,6 +35,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <android/bitmap.h>
#include "androidgui.h"
#include "lisp.h"
#endif
/* This must be used in every symbol declaration to export it to the
@ -84,6 +85,11 @@ extern bool android_detect_mouse (void);
extern void android_set_dont_focus_on_map (android_window, bool);
extern void android_set_dont_accept_focus (android_window, bool);
extern jstring android_build_string (Lisp_Object);
extern void android_exception_check (void);
extern void android_get_keysym_name (int, char *, size_t);
/* Directory listing emulation. */

View file

@ -25,12 +25,36 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "androidterm.h"
#include "blockinput.h"
#include "keyboard.h"
#include "buffer.h"
#ifndef ANDROID_STUBIFY
/* Some kind of reference count for the image cache. */
static ptrdiff_t image_cache_refcount;
/* The frame of the currently visible tooltip, or nil if none. */
static Lisp_Object tip_frame;
/* The window-system window corresponding to the frame of the
currently visible tooltip. */
static android_window tip_window;
/* The X and Y deltas of the last call to `x-show-tip'. */
static Lisp_Object tip_dx, tip_dy;
/* A timer that hides or deletes the currently visible tooltip when it
fires. */
static Lisp_Object tip_timer;
/* STRING argument of last `x-show-tip' call. */
static Lisp_Object tip_last_string;
/* Normalized FRAME argument of last `x-show-tip' call. */
static Lisp_Object tip_last_frame;
/* PARMS argument of last `x-show-tip' call. */
static Lisp_Object tip_last_parms;
#endif
static struct android_display_info *
@ -180,6 +204,9 @@ android_set_parent_frame (struct frame *f, Lisp_Object new_value,
fset_parent_frame (f, new_value);
}
/* Update the fullscreen frame parameter as well. */
FRAME_TERMINAL (f)->fullscreen_hook (f);
}
void
@ -858,13 +885,13 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
gui_default_parameter (f, parms, Qbottom_divider_width, make_fixnum (0),
NULL, NULL, RES_TYPE_NUMBER);
/* gui_default_parameter (f, parms, Qvertical_scroll_bars, */
/* Qleft, */
/* "verticalScrollBars", "ScrollBars", */
/* RES_TYPE_SYMBOL); */
/* gui_default_parameter (f, parms, Qhorizontal_scroll_bars, Qnil, */
/* "horizontalScrollBars", "ScrollBars", */
/* RES_TYPE_SYMBOL); TODO */
gui_default_parameter (f, parms, Qvertical_scroll_bars,
Qleft,
"verticalScrollBars", "ScrollBars",
RES_TYPE_SYMBOL);
gui_default_parameter (f, parms, Qhorizontal_scroll_bars, Qnil,
"horizontalScrollBars", "ScrollBars",
RES_TYPE_SYMBOL);
/* Also do the stuff which must be set before the window exists. */
gui_default_parameter (f, parms, Qforeground_color, build_string ("black"),
@ -893,7 +920,7 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
android_default_scroll_bar_color_parameter (f, parms, Qscroll_bar_background,
"scrollBarBackground",
"ScrollBarBackground", false);
#endif /* TODO */
#endif
/* Init faces before gui_default_parameter is called for the
scroll-bar-width parameter because otherwise we end up in
@ -974,12 +1001,16 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
"autoLower", "AutoRaiseLower", RES_TYPE_BOOLEAN);
gui_default_parameter (f, parms, Qcursor_type, Qbox,
"cursorType", "CursorType", RES_TYPE_SYMBOL);
/* Scroll bars are not supported on Android, as they are near
useless. */
#if 0
gui_default_parameter (f, parms, Qscroll_bar_width, Qnil,
"scrollBarWidth", "ScrollBarWidth",
RES_TYPE_NUMBER);
gui_default_parameter (f, parms, Qscroll_bar_height, Qnil,
"scrollBarHeight", "ScrollBarHeight",
RES_TYPE_NUMBER);
#endif
gui_default_parameter (f, parms, Qalpha, Qnil,
"alpha", "Alpha", RES_TYPE_NUMBER);
gui_default_parameter (f, parms, Qalpha_background, Qnil,
@ -1009,8 +1040,9 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
/* Process fullscreen parameter here in the hope that normalizing a
fullheight/fullwidth frame will produce the size set by the last
adjust_frame_size call. */
gui_default_parameter (f, parms, Qfullscreen, Qnil,
adjust_frame_size call. Note that Android only supports the
`maximized' state. */
gui_default_parameter (f, parms, Qfullscreen, Qmaximized,
"fullscreen", "Fullscreen", RES_TYPE_SYMBOL);
/* When called from `x-create-frame-with-faces' visibility is
@ -1661,6 +1693,391 @@ DEFUN ("x-display-list", Fx_display_list, Sx_display_list, 0, 0, 0,
return result;
}
#ifndef ANDROID_STUBIFY
static void
unwind_create_tip_frame (Lisp_Object frame)
{
Lisp_Object deleted;
deleted = unwind_create_frame (frame);
if (EQ (deleted, Qt))
{
tip_window = ANDROID_NONE;
tip_frame = Qnil;
}
}
static Lisp_Object
android_create_tip_frame (struct android_display_info *dpyinfo,
Lisp_Object parms)
{
struct frame *f;
Lisp_Object frame;
Lisp_Object name;
specpdl_ref count = SPECPDL_INDEX ();
bool face_change_before = face_change;
if (!dpyinfo->terminal->name)
error ("Terminal is not live, can't create new frames on it");
parms = Fcopy_alist (parms);
/* Get the name of the frame to use for resource lookup. */
name = gui_display_get_arg (dpyinfo, parms, Qname, "name", "Name",
RES_TYPE_STRING);
if (!STRINGP (name)
&& !BASE_EQ (name, Qunbound)
&& !NILP (name))
error ("Invalid frame name--not a string or nil");
frame = Qnil;
f = make_frame (false);
f->wants_modeline = false;
XSETFRAME (frame, f);
record_unwind_protect (unwind_create_tip_frame, frame);
f->terminal = dpyinfo->terminal;
/* By setting the output method, we're essentially saying that
the frame is live, as per FRAME_LIVE_P. If we get a signal
from this point on, x_destroy_window might screw up reference
counts etc. */
f->output_method = output_android;
f->output_data.android = xzalloc (sizeof *f->output_data.android);
FRAME_FONTSET (f) = -1;
f->output_data.android->white_relief.pixel = -1;
f->output_data.android->black_relief.pixel = -1;
f->tooltip = true;
fset_icon_name (f, Qnil);
FRAME_DISPLAY_INFO (f) = dpyinfo;
f->output_data.android->parent_desc = FRAME_DISPLAY_INFO (f)->root_window;
/* These colors will be set anyway later, but it's important
to get the color reference counts right, so initialize them! */
{
Lisp_Object black;
/* Function android_decode_color can signal an error. Make sure
to initialize color slots so that we won't try to free colors
we haven't allocated. */
FRAME_FOREGROUND_PIXEL (f) = -1;
FRAME_BACKGROUND_PIXEL (f) = -1;
f->output_data.android->cursor_pixel = -1;
f->output_data.android->cursor_foreground_pixel = -1;
black = build_string ("black");
FRAME_FOREGROUND_PIXEL (f)
= android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
FRAME_BACKGROUND_PIXEL (f)
= android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
f->output_data.android->cursor_pixel
= android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
f->output_data.android->cursor_foreground_pixel
= android_decode_color (f, black, BLACK_PIX_DEFAULT (f));
}
/* Set the name; the functions to which we pass f expect the name to
be set. */
if (BASE_EQ (name, Qunbound) || NILP (name))
f->explicit_name = false;
else
{
fset_name (f, name);
f->explicit_name = true;
/* use the frame's title when getting resources for this frame. */
specbind (Qx_resource_name, name);
}
register_font_driver (&androidfont_driver, f);
register_font_driver (&android_sfntfont_driver, f);
image_cache_refcount
= FRAME_IMAGE_CACHE (f) ? FRAME_IMAGE_CACHE (f)->refcount : 0;
#ifdef GLYPH_DEBUG
dpyinfo_refcount = dpyinfo->reference_count;
#endif /* GLYPH_DEBUG */
gui_default_parameter (f, parms, Qfont_backend, Qnil,
"fontBackend", "FontBackend", RES_TYPE_STRING);
/* Extract the window parameters from the supplied values that are
needed to determine window geometry. */
android_default_font_parameter (f, parms);
gui_default_parameter (f, parms, Qborder_width, make_fixnum (0),
"borderWidth", "BorderWidth", RES_TYPE_NUMBER);
/* This defaults to 1 in order to match xterm. We recognize either
internalBorderWidth or internalBorder (which is what xterm calls
it). */
if (NILP (Fassq (Qinternal_border_width, parms)))
{
Lisp_Object value;
value = gui_display_get_arg (dpyinfo, parms, Qinternal_border_width,
"internalBorder", "internalBorder",
RES_TYPE_NUMBER);
if (! BASE_EQ (value, Qunbound))
parms = Fcons (Fcons (Qinternal_border_width, value),
parms);
}
gui_default_parameter (f, parms, Qinternal_border_width, make_fixnum (1),
"internalBorderWidth", "internalBorderWidth",
RES_TYPE_NUMBER);
gui_default_parameter (f, parms, Qright_divider_width, make_fixnum (0),
NULL, NULL, RES_TYPE_NUMBER);
gui_default_parameter (f, parms, Qbottom_divider_width, make_fixnum (0),
NULL, NULL, RES_TYPE_NUMBER);
/* Also do the stuff which must be set before the window exists. */
gui_default_parameter (f, parms, Qforeground_color, build_string ("black"),
"foreground", "Foreground", RES_TYPE_STRING);
gui_default_parameter (f, parms, Qbackground_color, build_string ("white"),
"background", "Background", RES_TYPE_STRING);
gui_default_parameter (f, parms, Qmouse_color, build_string ("black"),
"pointerColor", "Foreground", RES_TYPE_STRING);
gui_default_parameter (f, parms, Qcursor_color, build_string ("black"),
"cursorColor", "Foreground", RES_TYPE_STRING);
gui_default_parameter (f, parms, Qborder_color, build_string ("black"),
"borderColor", "BorderColor", RES_TYPE_STRING);
gui_default_parameter (f, parms, Qno_special_glyphs, Qnil,
NULL, NULL, RES_TYPE_BOOLEAN);
{
struct android_set_window_attributes attrs;
unsigned long mask;
block_input ();
mask = ANDROID_CW_OVERRIDE_REDIRECT;
attrs.override_redirect = true;
tip_window
= FRAME_ANDROID_WINDOW (f)
= android_create_window (FRAME_DISPLAY_INFO (f)->root_window,
/* x, y, width, height, value-mask,
attrs. */
0, 0, 1, 1, mask, &attrs);
unblock_input ();
}
/* Init faces before gui_default_parameter is called for the
scroll-bar-width parameter because otherwise we end up in
init_iterator with a null face cache, which should not happen. */
init_frame_faces (f);
gui_default_parameter (f, parms, Qinhibit_double_buffering, Qnil,
"inhibitDoubleBuffering", "InhibitDoubleBuffering",
RES_TYPE_BOOLEAN);
gui_figure_window_size (f, parms, false, false);
f->output_data.android->parent_desc = FRAME_DISPLAY_INFO (f)->root_window;
android_make_gc (f);
gui_default_parameter (f, parms, Qauto_raise, Qnil,
"autoRaise", "AutoRaiseLower", RES_TYPE_BOOLEAN);
gui_default_parameter (f, parms, Qauto_lower, Qnil,
"autoLower", "AutoRaiseLower", RES_TYPE_BOOLEAN);
gui_default_parameter (f, parms, Qcursor_type, Qbox,
"cursorType", "CursorType", RES_TYPE_SYMBOL);
gui_default_parameter (f, parms, Qalpha, Qnil,
"alpha", "Alpha", RES_TYPE_NUMBER);
gui_default_parameter (f, parms, Qalpha_background, Qnil,
"alphaBackground", "AlphaBackground", RES_TYPE_NUMBER);
/* Add `tooltip' frame parameter's default value. */
if (NILP (Fframe_parameter (frame, Qtooltip)))
{
AUTO_FRAME_ARG (arg, Qtooltip, Qt);
Fmodify_frame_parameters (frame, arg);
}
/* FIXME - can this be done in a similar way to normal frames?
https://lists.gnu.org/r/emacs-devel/2007-10/msg00641.html */
/* Set the `display-type' frame parameter before setting up faces. */
{
Lisp_Object disptype;
disptype = Qcolor;
if (NILP (Fframe_parameter (frame, Qdisplay_type)))
{
AUTO_FRAME_ARG (arg, Qdisplay_type, disptype);
Fmodify_frame_parameters (frame, arg);
}
}
/* Set up faces after all frame parameters are known. This call
also merges in face attributes specified for new frames. */
{
Lisp_Object bg = Fframe_parameter (frame, Qbackground_color);
call2 (Qface_set_after_frame_default, frame, Qnil);
if (!EQ (bg, Fframe_parameter (frame, Qbackground_color)))
{
AUTO_FRAME_ARG (arg, Qbackground_color, bg);
Fmodify_frame_parameters (frame, arg);
}
}
f->no_split = true;
/* Now that the frame will be official, it counts as a reference to
its display and terminal. */
f->terminal->reference_count++;
/* It is now ok to make the frame official even if we get an error
below. And the frame needs to be on Vframe_list or making it
visible won't work. */
Vframe_list = Fcons (frame, Vframe_list);
f->can_set_window_size = true;
adjust_frame_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f),
0, true, Qtip_frame);
/* Setting attributes of faces of the tooltip frame from resources
and similar will set face_change, which leads to the clearing of
all current matrices. Since this isn't necessary here, avoid it
by resetting face_change to the value it had before we created
the tip frame. */
face_change = face_change_before;
/* Discard the unwind_protect. */
return unbind_to (count, frame);
}
static Lisp_Object
android_hide_tip (bool delete)
{
if (!NILP (tip_timer))
{
call1 (Qcancel_timer, tip_timer);
tip_timer = Qnil;
}
if (NILP (tip_frame)
|| (!delete
&& !NILP (tip_frame)
&& FRAME_LIVE_P (XFRAME (tip_frame))
&& !FRAME_VISIBLE_P (XFRAME (tip_frame))))
return Qnil;
else
{
Lisp_Object was_open = Qnil;
specpdl_ref count = SPECPDL_INDEX ();
specbind (Qinhibit_redisplay, Qt);
specbind (Qinhibit_quit, Qt);
if (!NILP (tip_frame))
{
struct frame *f = XFRAME (tip_frame);
if (FRAME_LIVE_P (f))
{
if (delete)
{
delete_frame (tip_frame, Qnil);
tip_frame = Qnil;
}
else
android_make_frame_invisible (XFRAME (tip_frame));
was_open = Qt;
}
else
tip_frame = Qnil;
}
else
tip_frame = Qnil;
return unbind_to (count, was_open);
}
}
static void
compute_tip_xy (struct frame *f, Lisp_Object parms, Lisp_Object dx,
Lisp_Object dy, int width, int height, int *root_x,
int *root_y)
{
Lisp_Object left, top, right, bottom;
int min_x, min_y, max_x, max_y = -1;
android_window window;
struct frame *mouse_frame;
/* Initialize these values in case there is no mouse frame. */
*root_x = 0;
*root_y = 0;
/* User-specified position? */
left = CDR (Fassq (Qleft, parms));
top = CDR (Fassq (Qtop, parms));
right = CDR (Fassq (Qright, parms));
bottom = CDR (Fassq (Qbottom, parms));
/* Move the tooltip window where the mouse pointer was last seen.
Resize and show it. */
if ((!FIXNUMP (left) && !FIXNUMP (right))
|| (!FIXNUMP (top) && !FIXNUMP (bottom)))
{
if (x_display_list->last_mouse_motion_frame)
{
*root_x = x_display_list->last_mouse_motion_x;
*root_y = x_display_list->last_mouse_motion_y;
mouse_frame = x_display_list->last_mouse_motion_frame;
window = FRAME_ANDROID_WINDOW (mouse_frame);
/* Translate the coordinates to the screen. */
android_translate_coordinates (window, *root_x, *root_y,
root_x, root_y);
}
}
min_x = 0;
min_y = 0;
max_x = android_get_screen_width ();
max_y = android_get_screen_height ();
if (FIXNUMP (top))
*root_y = XFIXNUM (top);
else if (FIXNUMP (bottom))
*root_y = XFIXNUM (bottom) - height;
else if (*root_y + XFIXNUM (dy) <= min_y)
*root_y = min_y; /* Can happen for negative dy */
else if (*root_y + XFIXNUM (dy) + height <= max_y)
/* It fits below the pointer */
*root_y += XFIXNUM (dy);
else if (height + XFIXNUM (dy) + min_y <= *root_y)
/* It fits above the pointer. */
*root_y -= height + XFIXNUM (dy);
else
/* Put it on the top. */
*root_y = min_y;
if (FIXNUMP (left))
*root_x = XFIXNUM (left);
else if (FIXNUMP (right))
*root_x = XFIXNUM (right) - width;
else if (*root_x + XFIXNUM (dx) <= min_x)
*root_x = 0; /* Can happen for negative dx */
else if (*root_x + XFIXNUM (dx) + width <= max_x)
/* It fits to the right of the pointer. */
*root_x += XFIXNUM (dx);
else if (width + XFIXNUM (dx) + min_x <= *root_x)
/* It fits to the left of the pointer. */
*root_x -= width + XFIXNUM (dx);
else
/* Put it left justified on the screen -- it ought to fit that way. */
*root_x = min_x;
}
#endif
DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0,
doc: /* SKIP: real doc in xfns.c. */)
(Lisp_Object string, Lisp_Object frame, Lisp_Object parms,
@ -1670,8 +2087,214 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0,
error ("Android cross-compilation stub called!");
return Qnil;
#else
/* TODO tooltips */
return Qnil;
struct frame *f, *tip_f;
struct window *w;
int root_x, root_y;
struct buffer *old_buffer;
struct text_pos pos;
int width, height;
int old_windows_or_buffers_changed = windows_or_buffers_changed;
specpdl_ref count = SPECPDL_INDEX ();
Lisp_Object window, size, tip_buf;
bool displayed;
#ifdef ENABLE_CHECKING
struct glyph_row *row, *end;
#endif
AUTO_STRING (tip, " *tip*");
specbind (Qinhibit_redisplay, Qt);
CHECK_STRING (string);
if (SCHARS (string) == 0)
string = make_unibyte_string (" ", 1);
if (NILP (frame))
frame = selected_frame;
f = decode_window_system_frame (frame);
if (NILP (timeout))
timeout = Vx_show_tooltip_timeout;
CHECK_FIXNAT (timeout);
if (NILP (dx))
dx = make_fixnum (5);
else
CHECK_FIXNUM (dx);
if (NILP (dy))
dy = make_fixnum (-10);
else
CHECK_FIXNUM (dy);
tip_dx = dx;
tip_dy = dy;
if (!NILP (tip_frame) && FRAME_LIVE_P (XFRAME (tip_frame)))
{
if (FRAME_VISIBLE_P (XFRAME (tip_frame))
&& !NILP (Fequal_including_properties (tip_last_string,
string))
&& !NILP (Fequal (tip_last_parms, parms)))
{
/* Only DX and DY have changed. */
tip_f = XFRAME (tip_frame);
if (!NILP (tip_timer))
{
call1 (Qcancel_timer, tip_timer);
tip_timer = Qnil;
}
block_input ();
compute_tip_xy (tip_f, parms, dx, dy, FRAME_PIXEL_WIDTH (tip_f),
FRAME_PIXEL_HEIGHT (tip_f), &root_x, &root_y);
android_move_window (FRAME_ANDROID_WINDOW (tip_f),
root_x, root_y);
unblock_input ();
goto start_timer;
}
else
android_hide_tip (true);
}
else
android_hide_tip (true);
tip_last_frame = frame;
tip_last_string = string;
tip_last_parms = parms;
if (NILP (tip_frame) || !FRAME_LIVE_P (XFRAME (tip_frame)))
{
/* Add default values to frame parameters. */
if (NILP (Fassq (Qname, parms)))
parms = Fcons (Fcons (Qname, build_string ("tooltip")), parms);
if (NILP (Fassq (Qinternal_border_width, parms)))
parms = Fcons (Fcons (Qinternal_border_width, make_fixnum (3)),
parms);
if (NILP (Fassq (Qborder_width, parms)))
parms = Fcons (Fcons (Qborder_width, make_fixnum (1)), parms);
if (NILP (Fassq (Qborder_color, parms)))
parms = Fcons (Fcons (Qborder_color, build_string ("lightyellow")),
parms);
if (NILP (Fassq (Qbackground_color, parms)))
parms = Fcons (Fcons (Qbackground_color,
build_string ("lightyellow")),
parms);
/* Create a frame for the tooltip, and record it in the global
variable tip_frame. */
if (NILP (tip_frame = android_create_tip_frame (FRAME_DISPLAY_INFO (f),
parms)))
/* Creating the tip frame failed. */
return unbind_to (count, Qnil);
}
tip_f = XFRAME (tip_frame);
window = FRAME_ROOT_WINDOW (tip_f);
tip_buf = Fget_buffer_create (tip, Qnil);
/* We will mark the tip window a "pseudo-window" below, and such
windows cannot have display margins. */
bset_left_margin_cols (XBUFFER (tip_buf), make_fixnum (0));
bset_right_margin_cols (XBUFFER (tip_buf), make_fixnum (0));
set_window_buffer (window, tip_buf, false, false);
w = XWINDOW (window);
w->pseudo_window_p = true;
/* Try to avoid that `other-window' select us (Bug#47207). */
Fset_window_parameter (window, Qno_other_window, Qt);
/* Set up the frame's root window. Note: The following code does not
try to size the window or its frame correctly. Its only purpose is
to make the subsequent text size calculations work. The right
sizes should get installed when the toolkit gets back to us. */
w->left_col = 0;
w->top_line = 0;
w->pixel_left = 0;
w->pixel_top = 0;
if (CONSP (Vx_max_tooltip_size)
&& RANGED_FIXNUMP (1, XCAR (Vx_max_tooltip_size), INT_MAX)
&& RANGED_FIXNUMP (1, XCDR (Vx_max_tooltip_size), INT_MAX))
{
w->total_cols = XFIXNAT (XCAR (Vx_max_tooltip_size));
w->total_lines = XFIXNAT (XCDR (Vx_max_tooltip_size));
}
else
{
w->total_cols = 80;
w->total_lines = 40;
}
w->pixel_width = w->total_cols * FRAME_COLUMN_WIDTH (tip_f);
w->pixel_height = w->total_lines * FRAME_LINE_HEIGHT (tip_f);
FRAME_TOTAL_COLS (tip_f) = w->total_cols;
adjust_frame_glyphs (tip_f);
/* Insert STRING into root window's buffer and fit the frame to the
buffer. */
specpdl_ref count_1 = SPECPDL_INDEX ();
old_buffer = current_buffer;
set_buffer_internal_1 (XBUFFER (w->contents));
bset_truncate_lines (current_buffer, Qnil);
specbind (Qinhibit_read_only, Qt);
specbind (Qinhibit_modification_hooks, Qt);
specbind (Qinhibit_point_motion_hooks, Qt);
Ferase_buffer ();
Finsert (1, &string);
clear_glyph_matrix (w->desired_matrix);
clear_glyph_matrix (w->current_matrix);
SET_TEXT_POS (pos, BEGV, BEGV_BYTE);
displayed = try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE);
if (!displayed && NILP (Vx_max_tooltip_size))
{
#ifdef ENABLE_CHECKING
row = w->desired_matrix->rows;
end = w->desired_matrix->rows + w->desired_matrix->nrows;
while (row < end)
{
if (!row->displays_text_p
|| row->ends_at_zv_p)
break;
++row;
}
eassert (row < end && row->ends_at_zv_p);
#endif
}
/* Calculate size of tooltip window. */
size = Fwindow_text_pixel_size (window, Qnil, Qnil, Qnil,
make_fixnum (w->pixel_height), Qnil,
Qnil);
/* Add the frame's internal border to calculated size. */
width = XFIXNUM (CAR (size)) + 2 * FRAME_INTERNAL_BORDER_WIDTH (tip_f);
height = XFIXNUM (CDR (size)) + 2 * FRAME_INTERNAL_BORDER_WIDTH (tip_f);
/* Calculate position of tooltip frame. */
compute_tip_xy (tip_f, parms, dx, dy, width, height, &root_x, &root_y);
/* Show tooltip frame. */
block_input ();
android_move_resize_window (FRAME_ANDROID_WINDOW (tip_f),
root_x, root_y, width,
height);
android_map_raised (FRAME_ANDROID_WINDOW (tip_f));
unblock_input ();
w->must_be_updated_p = true;
update_single_window (w);
flush_frame (tip_f);
set_buffer_internal_1 (old_buffer);
unbind_to (count_1, Qnil);
windows_or_buffers_changed = old_windows_or_buffers_changed;
start_timer:
/* Let the tip disappear after timeout seconds. */
tip_timer = call3 (Qrun_at_time, timeout, Qnil,
Qx_hide_tip);
return unbind_to (count, Qnil);
#endif
}
@ -1683,7 +2306,7 @@ DEFUN ("x-hide-tip", Fx_hide_tip, Sx_hide_tip, 0, 0, 0,
error ("Android cross-compilation stub called!");
return Qnil;
#else
return Qnil;
return android_hide_tip (true);
#endif
}
@ -2112,6 +2735,17 @@ syms_of_androidfns (void)
doc: /* SKIP: real doc in xfns.c. */);
Vx_cursor_fore_pixel = Qnil;
/* Used by Fx_show_tip. */
DEFSYM (Qrun_at_time, "run-at-time");
DEFSYM (Qx_hide_tip, "x-hide-tip");
DEFSYM (Qcancel_timer, "cancel-timer");
DEFSYM (Qassq_delete_all, "assq-delete-all");
DEFSYM (Qcolor, "color");
DEFVAR_LISP ("x-max-tooltip-size", Vx_max_tooltip_size,
doc: /* SKIP: real doc in xfns.c. */);
Vx_max_tooltip_size = Qnil;
/* Functions defined. */
defsubr (&Sx_create_frame);
defsubr (&Sxw_color_defined_p);
@ -2139,4 +2773,21 @@ syms_of_androidfns (void)
defsubr (&Sx_show_tip);
defsubr (&Sx_hide_tip);
defsubr (&Sandroid_detect_mouse);
#ifndef ANDROID_STUBIFY
tip_timer = Qnil;
staticpro (&tip_timer);
tip_frame = Qnil;
staticpro (&tip_frame);
tip_last_frame = Qnil;
staticpro (&tip_last_frame);
tip_last_string = Qnil;
staticpro (&tip_last_string);
tip_last_parms = Qnil;
staticpro (&tip_last_parms);
tip_dx = Qnil;
staticpro (&tip_dx);
tip_dy = Qnil;
staticpro (&tip_dy);
#endif
}

View file

@ -81,12 +81,17 @@ enum android_fill_style
enum android_window_value_mask
{
ANDROID_CW_BACK_PIXEL = (1 << 1),
ANDROID_CW_OVERRIDE_REDIRECT = (1 << 2),
};
struct android_set_window_attributes
{
/* The background pixel. */
unsigned long background_pixel;
/* Whether or not the window is override redirect. This cannot be
set after creation on Android. */
bool override_redirect;
};
struct android_gc_values
@ -260,7 +265,7 @@ struct android_key_event
((key) == 57 || (key) == 58 || (key) == 113 || (key) == 114 \
|| (key) == 119 || (key) == 117 || (key) == 118 || (key) == 78 \
|| (key) == 94 || (key) == 59 || (key) == 60 || (key) == 95 \
|| (key) == 63)
|| (key) == 63 || (key) == 115)
struct android_configure_event
{
@ -478,6 +483,11 @@ extern int android_query_tree (android_window, android_window *,
extern void android_get_geometry (android_window, android_window *,
int *, int *, unsigned int *,
unsigned int *, unsigned int *);
extern void android_move_resize_window (android_window, int, int,
unsigned int, unsigned int);
extern void android_map_raised (android_window);
extern void android_translate_coordinates (android_window, int,
int, int *, int *);
#endif

View file

@ -21,6 +21,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "lisp.h"
#include "androidterm.h"
#include "android.h"
#include "blockinput.h"
#include "keyboard.h"
#include "menu.h"
#ifndef ANDROID_STUBIFY
@ -35,4 +39,293 @@ popup_activated (void)
return popup_activated_flag;
}
/* Toolkit menu implementation. */
/* Structure describing the EmacsContextMenu class. */
struct android_emacs_context_menu
{
jclass class;
jmethodID create_context_menu;
jmethodID add_item;
jmethodID add_submenu;
jmethodID add_pane;
jmethodID parent;
jmethodID display;
};
/* Identifiers associated with the EmacsContextMenu class. */
static struct android_emacs_context_menu menu_class;
static void
android_init_emacs_context_menu (void)
{
jclass old;
menu_class.class
= (*android_java_env)->FindClass (android_java_env,
"org/gnu/emacs/EmacsContextMenu");
eassert (menu_class.class);
old = menu_class.class;
menu_class.class
= (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
(jobject) old);
ANDROID_DELETE_LOCAL_REF (old);
if (!menu_class.class)
emacs_abort ();
#define FIND_METHOD(c_name, name, signature) \
menu_class.c_name \
= (*android_java_env)->GetMethodID (android_java_env, \
menu_class.class, \
name, signature); \
eassert (menu_class.c_name);
#define FIND_METHOD_STATIC(c_name, name, signature) \
menu_class.c_name \
= (*android_java_env)->GetStaticMethodID (android_java_env, \
menu_class.class, \
name, signature); \
eassert (menu_class.c_name);
FIND_METHOD_STATIC (create_context_menu, "createContextMenu",
"(Ljava/lang/String;)Lorg/gnu/emacs/EmacsContextMenu;");
FIND_METHOD (add_item, "addItem", "(ILjava/lang/String;Z)V");
FIND_METHOD (add_submenu, "addSubmenu", "(Ljava/lang/String;"
"Ljava/lang/String;)Lorg/gnu/emacs/EmacsContextMenu;");
FIND_METHOD (add_pane, "addPane", "(Ljava/lang/String;)V");
FIND_METHOD (parent, "parent", "()Lorg/gnu/emacs/EmacsContextMenu;");
FIND_METHOD (display, "display", "(Lorg/gnu/emacs/EmacsWindow;II)Z");
#undef FIND_METHOD
#undef FIND_METHOD_STATIC
}
static void
android_unwind_local_frame (void)
{
(*android_java_env)->PopLocalFrame (android_java_env, NULL);
}
/* Push a local reference frame to the JVM stack and record it on the
specpdl. Release local references created within that frame when
the specpdl is unwound past where it is after returning. */
static void
android_push_local_frame (void)
{
int rc;
rc = (*android_java_env)->PushLocalFrame (android_java_env, 30);
/* This means the JVM ran out of memory. */
if (rc < 1)
android_exception_check ();
record_unwind_protect_void (android_unwind_local_frame);
}
Lisp_Object
android_menu_show (struct frame *f, int x, int y, int menuflags,
Lisp_Object title, const char **error_name)
{
jobject context_menu, current_context_menu;
jobject title_string, temp;
size_t i;
Lisp_Object pane_name, prefix;
const char *pane_string;
specpdl_ref count, count1;
Lisp_Object item_name, enable, def;
jmethodID method;
jobject store;
bool rc;
jobject window;
count = SPECPDL_INDEX ();
block_input ();
/* Push the first local frame. */
android_push_local_frame ();
/* Push the first local frame for the context menu. */
title_string = (!NILP (title)
? (jobject) android_build_string (title)
: NULL);
method = menu_class.create_context_menu;
current_context_menu = context_menu
= (*android_java_env)->CallStaticObjectMethod (android_java_env,
menu_class.class,
method,
title_string);
if (title_string)
ANDROID_DELETE_LOCAL_REF (title_string);
/* Push the second local frame for temporaries. */
count1 = SPECPDL_INDEX ();
android_push_local_frame ();
/* Iterate over the menu. */
i = 0;
while (i < menu_items_used)
{
if (NILP (AREF (menu_items, i)))
{
/* This is the start of a new submenu. However, it can be
ignored here. */
i += 1;
}
else if (EQ (AREF (menu_items, i), Qlambda))
{
/* This is the end of a submenu. Go back to the previous
context menu. */
store = current_context_menu;
current_context_menu
= (*android_java_env)->CallObjectMethod (android_java_env,
current_context_menu,
menu_class.parent);
android_exception_check ();
if (store != context_menu)
ANDROID_DELETE_LOCAL_REF (store);
i += 1;
eassert (current_context_menu);
}
else if (EQ (AREF (menu_items, i), Qquote))
i += 1;
else if (EQ (AREF (menu_items, i), Qt))
{
/* This is a new pane. Switch back to the topmost context
menu. */
if (current_context_menu != context_menu)
ANDROID_DELETE_LOCAL_REF (current_context_menu);
current_context_menu = context_menu;
/* Now figure out the title of this pane. */
pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
pane_string = (NILP (pane_name)
? "" : SSDATA (pane_name));
if ((menuflags & MENU_KEYMAPS) && !NILP (prefix))
pane_string++;
/* Add the pane. */
temp = (*android_java_env)->NewStringUTF (android_java_env,
pane_string);
android_exception_check ();
(*android_java_env)->CallVoidMethod (android_java_env,
current_context_menu,
menu_class.add_pane,
temp);
android_exception_check ();
ANDROID_DELETE_LOCAL_REF (temp);
i += MENU_ITEMS_PANE_LENGTH;
}
else
{
item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
/* This is an actual menu item (or submenu). Add it to the
menu. */
if (i + MENU_ITEMS_ITEM_LENGTH < menu_items_used &&
NILP (AREF (menu_items, i + MENU_ITEMS_ITEM_LENGTH)))
{
/* This is a submenu. Add it. */
title_string = (!NILP (item_name)
? android_build_string (item_name)
: NULL);
store = current_context_menu;
current_context_menu
= (*android_java_env)->CallObjectMethod (android_java_env,
current_context_menu,
menu_class.add_submenu,
title_string);
android_exception_check ();
if (store != context_menu)
ANDROID_DELETE_LOCAL_REF (store);
if (title_string)
ANDROID_DELETE_LOCAL_REF (title_string);
}
else if (NILP (def) && menu_separator_name_p (SSDATA (item_name)))
/* Ignore this separator item. */
;
else
{
/* Add this menu item with the appropriate state. */
title_string = (!NILP (item_name)
? android_build_string (item_name)
: NULL);
(*android_java_env)->CallVoidMethod (android_java_env,
current_context_menu,
menu_class.add_item,
(jint) 1,
title_string,
(jboolean) !NILP (enable));
android_exception_check ();
if (title_string)
ANDROID_DELETE_LOCAL_REF (title_string);
}
i += MENU_ITEMS_ITEM_LENGTH;
}
}
/* The menu has now been built. Pop the second local frame. */
unbind_to (count1, Qnil);
/* Now, display the context menu. */
window = android_resolve_handle (FRAME_ANDROID_WINDOW (f),
ANDROID_HANDLE_WINDOW);
rc = (*android_java_env)->CallBooleanMethod (android_java_env,
context_menu,
window, (jint) x,
(jint) y);
android_exception_check ();
if (!rc)
/* This means displaying the menu failed. */
goto finish;
#if 0
record_unwind_protect_ptr (android_dismiss_menu, &context_menu);
/* Otherwise, loop waiting for the menu event to arrive. */
android_process_events_for_menu (&id);
if (!id)
/* This means no menu item was selected. */
goto finish;
#endif
finish:
unblock_input ();
return unbind_to (count, Qnil);
}
#endif
void
init_androidmenu (void)
{
#ifndef ANDROID_STUBIFY
android_init_emacs_context_menu ();
#endif
}

View file

@ -559,6 +559,9 @@ handle_one_android_event (struct android_display_info *dpyinfo,
f = android_window_to_frame (dpyinfo,
configureEvent.xconfigure.window);
if (!f)
goto OTHER;
int width = configureEvent.xconfigure.width;
int height = configureEvent.xconfigure.height;
@ -884,10 +887,6 @@ handle_one_android_event (struct android_display_info *dpyinfo,
inev.ie.arg = tab_bar_arg;
}
}
else
{
/* TODO: scroll bars */
}
if (event->type == ANDROID_BUTTON_PRESS)
{
@ -1451,7 +1450,12 @@ android_make_frame_visible_invisible (struct frame *f, bool visible)
static void
android_fullscreen_hook (struct frame *f)
{
/* TODO */
/* Explicitly setting fullscreen is not supported on Android. */
if (!FRAME_PARENT_FRAME (f))
store_frame_param (f, Qfullscreen, Qmaximized);
else
store_frame_param (f, Qfullscreen, Qnil);
}
void
@ -2360,7 +2364,7 @@ android_draw_box_rect (struct glyph_string *s,
/* Top. */
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x,
left_x, right_x - left_x + 1, hwidth);
top_y, right_x - left_x + 1, hwidth);
/* Left. */
if (left_p)
@ -3958,7 +3962,14 @@ frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y)
char *
get_keysym_name (int keysym)
{
return (char *) "UNKNOWN KEYSYM";
static char buffer[64];
#ifndef ANDROID_STUBIFY
android_get_keysym_name (keysym, buffer, 64);
#else
emacs_abort ();
#endif
return buffer;
}
@ -4009,20 +4020,13 @@ android_create_terminal (struct android_display_info *dpyinfo)
terminal->set_new_font_hook = android_new_font;
terminal->set_bitmap_icon_hook = android_bitmap_icon;
terminal->implicit_set_name_hook = android_implicitly_set_name;
/* terminal->menu_show_hook = android_menu_show; XXX */
terminal->menu_show_hook = android_menu_show;
terminal->change_tab_bar_height_hook = android_change_tab_bar_height;
terminal->change_tool_bar_height_hook = android_change_tool_bar_height;
/* terminal->set_vertical_scroll_bar_hook */
/* = android_set_vertical_scroll_bar; */
/* terminal->set_horizontal_scroll_bar_hook */
/* = android_set_horizontal_scroll_bar; */
terminal->set_scroll_bar_default_width_hook
= android_set_scroll_bar_default_width;
terminal->set_scroll_bar_default_height_hook
= android_set_scroll_bar_default_height;
/* terminal->condemn_scroll_bars_hook = android_condemn_scroll_bars; */
/* terminal->redeem_scroll_bars_hook = android_redeem_scroll_bars; */
/* terminal->judge_scroll_bars_hook = android_judge_scroll_bars; */
terminal->free_pixmap = android_free_pixmap_hook;
terminal->delete_frame_hook = android_delete_frame;
terminal->delete_terminal_hook = android_delete_terminal;

View file

@ -402,6 +402,12 @@ extern void syms_of_androidfont (void);
extern void android_finalize_font_entity (struct font_entity *);
/* Defined in androidmenu.c. */
extern Lisp_Object android_menu_show (struct frame *, int, int, int,
Lisp_Object, const char **);
extern void init_androidmenu (void);
/* Defined in sfntfont-android.c. */
extern const struct font_driver android_sfntfont_driver;

View file

@ -2499,6 +2499,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
init_window ();
init_font ();
#ifdef HAVE_ANDROID
init_androidmenu ();
#endif
#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
init_androidfont ();
init_sfntfont ();

View file

@ -110,7 +110,7 @@ lib/libgnu.a: src/verbose.mk config.status $(LIB_DEPS) $(PRE_BUILD_DEPS)
+make -C lib libgnu.a
src/Makefile src/config.h &: $(top_builddir)/src/config.h.android \
$(top_builddir)/src/Makefile.android $(PRE_BUILD_DEPS)
$(top_builddir)/src/Makefile.android
mkdir -p src src/deps
# Copy config.h to src/
cp -f -p $(top_builddir)/src/config.h.android src/config.h
@ -155,13 +155,20 @@ $(LIBSRC_BINARIES) &: src/verbose.mk $(top_builddir)/$@ lib/libgnu.a \
# Finally, go into lib-src and make everything being built
+make -C lib-src $(foreach bin,$(LIBSRC_BINARIES),$(notdir $(bin)))
.PHONY: clean maintainer-clean
.PHONY: clean maintainer-clean distclean
clean:
rm -rf $(CLEAN_SUBDIRS) *.bak sys
if [ -e lib/Makefile ]; then \
make -C lib clean; \
fi
rm -rf lib/gnulib.mk lib/Makefile lib/config.h
rm -rf lib/config.h
distclean bootstrap-clean: clean
if [ -e lib/Makefile ]; then \
make -C lib distclean; \
fi
# Just in case.
rm -rf lib/Makefile lib/gnulib.mk
maintainer-clean: clean
if [ -e lib/Makefile ]; then \