Update Android port

* Makefile.in (java): Depend on info.
(MAKEFILE_NAME):
(config.status): Remove unneeded changes.
* configure.ac (BUILD_DETAILS, ANDROID_STUBIFY): Don't require a
C++ compiler on Android.
* java/AndroidManifest.xml: <EmacsActivity>: Set launchMode
appropriately.  <EmacsMultitaskActivity>: New activity.
* java/Makefile.in (CROSS_BINS): Add EmacsClient.
* java/org/gnu/emacs/EmacsActivity.java (EmacsActivity)
(onCreate): Use the window attachment manager.
* java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea)
(paintTo): Implement clip masks correctly.
* java/org/gnu/emacs/EmacsDrawRectangle.java (getRect, paintTo):
Fix damage tracking rectangles.
* java/org/gnu/emacs/EmacsFontDriver.java (FontSpec, toString):
New function.
(FontMetrics, EmacsFontDriver): Fix signature of textExtents.
* java/org/gnu/emacs/EmacsMultitaskActivity.java
(EmacsMultitaskActivity): New file.
* java/org/gnu/emacs/EmacsNative.java (EmacsNative): New
functions sendFocusIn, sendFocusOut, sendWindowAction.
* java/org/gnu/emacs/EmacsPaintQueue.java (run): Fix clipping
handling.
* java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): Add
constructor for mutable pixmaps.
* java/org/gnu/emacs/EmacsSdk23FontDriver.java
(EmacsSdk23FontDriver): New file.
* java/org/gnu/emacs/EmacsSdk7FontDriver.java
(EmacsSdk7FontDriver, Sdk7Typeface, Sdk7FontEntity, Sdk7FontObject)
(checkMatch, hasChar, encodeChar): Implement text display and
fix font metrics semantics.

* java/org/gnu/emacs/EmacsService.java (EmacsService): Remove
availableChildren.
(getLibraryDirectory, onCreate): Pass pixel density to Emacs.
(clearArea): Fix arguments.  Switch to using the window
attachment manager.
* java/org/gnu/emacs/EmacsSurfaceView.java (surfaceChanged)
(surfaceCreated): Flip buffers on surface attachment.
* java/org/gnu/emacs/EmacsView.java (EmacsView, swapBuffers):
New argument FORCE.  Always swap if it is true.
(onKeyMultiple, onFocusChanged): New functions.

* java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, destroyHandle)
(run): Switch to using the window attachment manager.
* java/org/gnu/emacs/EmacsWindowAttachmentManager.java
(EmacsWindowAttachmentManager): New file.

* lisp/cus-edit.el (custom-button, custom-button-mouse)
(custom-button-pressed):
* lisp/faces.el (tool-bar): Define faces correctly on Android.
* src/android.c (struct android_emacs_pixmap): Add mutable
constructor.
(struct android_emacs_drawable): New structure.
(android_write_event): Check if event queue hasn't yet been
initialized.
(android_select): Set errno to EINTR if pselect fails.
(android_close): Remove unused debugging code.
(android_get_home_directory): New function.
(Java_org_gnu_emacs_EmacsNative_setEmacsParams): Set pixel
density and compute game path.
(android_init_emacs_drawable): New function.
(Java_org_gnu_emacs_EmacsNative_sendKeyPress): New argument
`unicode_char'.  Pass it in events.
(Java_org_gnu_emacs_EmacsNative_sendKeyRelease): Likewise.
(Java_org_gnu_emacs_EmacsNative_sendFocusIn)
(Java_org_gnu_emacs_EmacsNative_sendFocusOut)
(Java_org_gnu_emacs_EmacsNative_sendWindowAction): New
functions.
(android_resolve_handle): Export function.
(android_change_gc): Clear clip rects under the right
circumstances.  Set right clip mask field.
(android_create_pixmap_from_bitmap_data): Use correct alpha
channels.
(android_create_pixmap): Create mutable pixmap and avoid
redundant color array allocation.
(android_create_bitmap_from_data, android_create_image)
(android_destroy_image, android_put_pixel, android_get_pixel)
(android_get_image, android_put_image, faccessat): New
functions.

* src/android.h: Update prototypes.

* src/androidfns.c (android_default_font_parameter): Prefer
monospace to Droid Sans Mono.
* src/androidfont.c (struct android_emacs_font_driver): New
method `draw'.
(struct android_emacs_font_spec): New field `dpi'.
(struct androidfont_info): Add font metrics cache.
(android_init_font_driver, android_init_font_spec): Adjust
accordingly.
(androidfont_from_lisp, androidfont_from_java): Handle new
fields.
(androidfont_draw): Implement function.
(androidfont_open_font): Set pixel size correctly.
(androidfont_close_font): Free metrics cache.
(androidfont_cache_text_extents)
(androidfont_check_cached_extents): New functions.
(androidfont_text_extents): Cache glyph metrics somewhere for
future use.
(androidfont_list_family): Implement function.

* src/androidgui.h (enum android_event_type): New focus and
window action events.
(enum android_modifier_mask): New masks.
(struct android_key_event): New field `unicode_char'.
(ANDROID_IS_MODIFIER_KEY): Newmacro.
(struct android_focus_event, struct
android_window_action_event): New structs.
(union android_event): Add new fields.
(enum android_image_format, struct android_image): New enums and
structs.

* src/androidterm.c (android_android_to_emacs_modifiers)
(android_emacs_to_android_modifiers, android_lower_frame)
(android_raise_frame, android_new_focus_frame)
(android_focus_changed, android_detect_focus_change): New
functions.
(handle_one_android_event): Implement focus and key event
handling.
(android_frame_rehighlight): New function.
(android_frame_raise_lower): Implement accordingly.
(android_make_frame_invisible): Clear highlight_frame if
required.
(android_free_frame_resources): Clear x_focus_event_frame if
required.
(android_draw_fringe_bitmap, android_draw_image_foreground)
(android_draw_image_foreground_1)
(android_draw_image_glyph_string): Remove unnecessary code.
(android_create_terminal, android_term_init): Set the baud rate
to something sensible.
* src/androidterm.h (struct android_bitmap_record): Make
structure the same as on X.
(struct android_display_info): New focus tracking fields.
(struct android_output): Likewise.
* src/dispextern.h (struct image): Add ximg and mask_img on
Android.

* src/emacs.c (android_emacs_init): Fix argc sorting iteration.

* src/fileio.c (user_homedir):
(get_homedir): Implement correctly on Android.

* src/font.h (PT_PER_INCH): Define correctly on Android.

* src/fringe.c (X, swap_nibble, init_fringe_bitmap): Swap fringe
bitmaps correctly on Android.

* src/image.c (GET_PIXEL, image_create_bitmap_from_data)
(image_create_bitmap_from_file, free_bitmap_record)
(image_unget_x_image_or_dc, struct image_type)
(prepare_image_for_display, image_clear_image_1)
(image_size_in_bytes, x_check_image_size)
(x_create_x_image_and_pixmap, x_destroy_x_image)
(image_check_image_size, image_create_x_image_and_pixmap_1)
(image_destroy_x_image, gui_put_x_image, image_put_x_image)
(image_get_x_image, image_unget_x_image)
(Create_Pixmap_From_Bitmap_Data, image_pixmap_draw_cross)
(MaskForeground, image_types, syms_of_image): Implement all of
the above on Android in terms of an API very similar to X.

* src/keyboard.c (FUNCTION_KEY_OFFSET, lispy_function_keys):
Define on Android to something sensible.

* src/lread.c (build_load_history): Fix problem.
This commit is contained in:
Po Lu 2023-01-02 21:38:19 +08:00
parent fd074f3133
commit a32963e11f
36 changed files with 2435 additions and 693 deletions

View file

@ -536,7 +536,7 @@ lisp: src
lib lib-src lisp nt: Makefile
$(MAKE) -C $@ all
java: lisp
java: lisp info
$(MAKE) -C $@ all
xcompile: src
@ -571,20 +571,10 @@ blessmail: Makefile src
# then attempts to build that file. This forces 'Makefile', 'lib/Makefile',
# etc. to be built without running into similar recursion problems.
MAKEFILE_NAME = Makefile
ifeq ($(ANDROID),)
$(MAKEFILE_NAME): config.status $(srcdir)/configure \
$(srcdir)/lib/gnulib.mk.in \
$(srcdir)/Makefile.in $(SUBDIR_MAKEFILES_IN) $(CONFIG_STATUS_FILES_IN)
MAKE='$(MAKE)' ./config.status
else
# Note that calling config.status is insufficient on Android due to
# the recursive calls to configure.
$(MAKEFILE_NAME): $(srcdir)/configure \
$(srcdir)/lib/gnulib.mk.in \
$(srcdir)/Makefile.in $(SUBDIR_MAKEFILES_IN) $(CONFIG_STATUS_FILES_IN)
$(CFG) $(srcdir)/configure $(CONFIGURE_FLAGS);
endif
# Don't erase these files if make is interrupted while refreshing them.
.PRECIOUS: Makefile config.status
@ -592,17 +582,12 @@ endif
# Note that calling config.status --recheck is insufficient on Android
# due to the recursive calls to configure.
ifneq ($(ANDROID),)
config.status: ${srcdir}/configure
$(CFG) $(srcdir)/configure $(CONFIGURE_FLAGS)
else
config.status: ${srcdir}/configure
if [ -x ./config.status ]; then \
$(CFG) ./config.status --recheck; \
else \
$(CFG) $(srcdir)/configure $(CONFIGURE_FLAGS); \
fi
endif
$(srcdir)/configure: $(srcdir)/configure.ac $(srcdir)/m4/*.m4
cd $(srcdir) && ./autogen.sh autoconf

View file

@ -31,9 +31,8 @@ if test "$XCONFIGURE" = "android"; then
# Android!
AC_MSG_NOTICE([called to recursively configure Emacs \
for Android.])
# Set CC to ANDROID_CC, and CXX to ANDROID_CXX.
# Set CC to ANDROID_CC.
CC=$ANDROID_CC
CXX=$ANDROID_CXX
fi
dnl Set emacs_config_options to the options of 'configure', quoted for the shell,
@ -147,7 +146,7 @@ if test "$XCONFIGURE" = "android"; then
;;
*) AC_MSG_ERROR([The cross compiler does not compile for Android.
Please verify that you specified the correct compiler in the ANDROID_CC
and ANDROID_CXX variables when you ran configure.])
variable when you ran configure.])
;;
esac
AC_MSG_RESULT([$host_alias])
@ -759,8 +758,7 @@ tools such as aapt, dx, and aidl):
The cross-compiler should then be specified:
ANDROID_CC=/path/to/armv7a-linux-androideabi19-clang
ANDROID_CXX=/path/to/armv7a-linux-androideabi19-clang++])
ANDROID_CC=/path/to/armv7a-linux-androideabi19-clang])
elif test "$with_android" = "no" || test "$with_android" = ""; then
ANDROID=no
else
@ -833,12 +831,11 @@ Please verify that the path to the SDK build tools you specified is correct]);
dnl Now configure Emacs to generate binaries for Android. After the
dnl configuration completes, move the generated Makefiles.
if test "$ANDROID_CC" = "" || test "$ANDROID_CXX" = ""; then
if test "$ANDROID_CC" = ""; then
AC_MSG_ERROR([Please specify the path to the Android cross-compiler
for your machine. For example:
ANDROID_CC=/path/to/armv7a-linux-androideabi19-clang \\
ANDROID_CXX=/path/to/armv7a-linux-androideabi19-clang++ \\
./configure --with-android])
fi
@ -863,7 +860,7 @@ for your machine. For example:
*) AC_MSG_ERROR([configure could not determine the type of Android \
binary Emacs is being configured for. Please port this configure script \
to your Android system, or verify that you specified the correct compiler \
in the ANDROID_CC and ANDROID_CXX variables when you ran configure.])
in the ANDROID_CC variable when you ran configure.])
;;
esac
AC_MSG_RESULT([$android_abi])
@ -874,9 +871,8 @@ in the ANDROID_CC and ANDROID_CXX variables when you ran configure.])
mv -f confdefs.h _confdefs.h
mv -f config.log _config.log
AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" \
ANDROID_CXX="$ANDROID_CXX" $0], [], [AC_MSG_ERROR([Failed to cross-\
configure Emacs for android.])])
AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" $0], [],
[AC_MSG_ERROR([Failed to cross-configure Emacs for android.])])
# Now set ANDROID to yes.
ANDROID=yes
@ -2254,7 +2250,7 @@ for Android, but all API calls need to be stubbed out])
ANDROID_CFLAGS="-fPIC -fvisibility=hidden"
# Link with libraries required for Android support.
ANDROID_LIBS="-landroid -llog"
ANDROID_LIBS="-landroid -llog -ljnigraphics"
fi
fi

View file

@ -31,13 +31,15 @@
android:targetSdkVersion="28"/>
<application android:name="org.gnu.emacs.EmacsApplication"
android:label="GNU Emacs"
android:label="Emacs"
android:hardwareAccelerated="true"
android:supportsRtl="true"
android:theme="@android:style/Theme"
android:debuggable="true"
android:extractNativeLibs="true">
<activity android:name="org.gnu.emacs.EmacsActivity">
<activity android:name="org.gnu.emacs.EmacsActivity"
android:launchMode="singleTop"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
@ -45,6 +47,9 @@
</intent-filter>
</activity>
<activity android:name="org.gnu.emacs.EmacsMultitaskActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"/>
<service android:name="org.gnu.emacs.EmacsService"
android:directBootAware="false"
android:enabled="true"

View file

@ -60,7 +60,7 @@ all: emacs.apk
# Binaries to cross-compile.
CROSS_BINS = ../xcompile/src/android-emacs ../xcompile/lib-src/ctags \
../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \
../xcompile/lib-src/ebrowse
../xcompile/lib-src/ebrowse ../xcompile/lib-src/emacsclient
# Libraries to cross-compile.
CROSS_LIBS = ../xcompile/src/libemacs.so
@ -84,6 +84,7 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml
# Install architecture independents to assets/etc and assets/lisp
cp -r $(top_builddir)/lisp install_temp/assets
cp -r $(top_builddir)/etc install_temp/assets
cp -r $(top_builddir)/info install_temp/assets
# Remove undesirable files from those directories.
for subdir in `find install_temp -type d -print`; do \
chmod a+rx $${subdir} ; \

View file

@ -32,65 +32,114 @@
import android.widget.FrameLayout.LayoutParams;
public class EmacsActivity extends Activity
implements EmacsWindowAttachmentManager.WindowConsumer
{
public static final String TAG = "EmacsActivity";
/* List of all activities that do not have an associated
EmacsWindow. */
public static List<EmacsActivity> availableActivities;
/* The currently attached EmacsWindow, or null if none. */
private EmacsWindow window;
/* The frame layout associated with the activity. */
private FrameLayout layout;
/* List of activities with focus. */
private static List<EmacsActivity> focusedActivities;
/* The currently focused window. */
public static EmacsWindow focusedWindow;
static
{
/* Set up the list of available activities. */
availableActivities = new ArrayList<EmacsActivity> ();
focusedActivities = new ArrayList<EmacsActivity> ();
};
public static void
invalidateFocus1 (EmacsWindow window)
{
if (window.view.isFocused ())
focusedWindow = window;
for (EmacsWindow child : window.children)
invalidateFocus1 (window);
}
public static void
invalidateFocus ()
{
EmacsWindow oldFocus;
/* Walk through each focused activity and assign the window focus
to the bottom-most focused window within. Record the old focus
as well. */
oldFocus = focusedWindow;
focusedWindow = null;
for (EmacsActivity activity : focusedActivities)
{
if (activity.window != null)
invalidateFocus1 (activity.window);
}
/* Send focus in- and out- events to the previous and current
focus. */
if (oldFocus != null)
EmacsNative.sendFocusOut (oldFocus.handle,
System.currentTimeMillis ());
if (focusedWindow != null)
EmacsNative.sendFocusIn (focusedWindow.handle,
System.currentTimeMillis ());
}
@Override
public void
attachChild (EmacsWindow child)
detachWindow ()
{
if (window == null)
Log.w (TAG, "detachWindow called, but there is no window");
else
{
/* Clear the window's pointer to this activity and remove the
window's view. */
window.setConsumer (null);
layout.removeView (window.view);
window = null;
invalidateFocus ();
}
}
@Override
public void
attachWindow (EmacsWindow child)
{
if (window != null)
throw new IllegalStateException ("trying to attach window when one"
+ " already exists");
/* Record and attach the view. */
window = child;
layout.addView (window.view);
child.setConsumer (this);
/* Remove the objects from the lists of what is available. */
EmacsService.availableChildren.remove (child);
availableActivities.remove (this);
/* Now set child->activity. */
child.setActivity (this);
/* Invalidate the focus. */
invalidateFocus ();
}
/* Make this activity available for future windows to attach
again. */
@Override
public void
makeAvailable ()
destroy ()
{
window = null;
finish ();
}
for (EmacsWindow iterWindow
: EmacsService.availableChildren)
{
synchronized (iterWindow)
{
if (!iterWindow.isDestroyed ())
attachChild (iterWindow);
return;
}
}
availableActivities.add (this);
@Override
public EmacsWindow
getAttachedWindow ()
{
return window;
}
@Override
@ -109,38 +158,38 @@ public class EmacsActivity extends Activity
/* Set it as the content view. */
setContentView (layout);
/* Make the activity available before starting the
service. */
makeAvailable ();
if (EmacsService.SERVICE == null)
/* Start the Emacs service now. */
startService (new Intent (this, EmacsService.class));
/* Add this activity to the list of available activities. */
EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
super.onCreate (savedInstanceState);
}
@Override
public void
onStop ()
onDestroy ()
{
/* The activity is no longer visible. If there is a window
attached, detach it. */
/* The activity will die shortly hereafter. If there is a window
attached, close it now. */
Log.d (TAG, "onDestroy " + this);
EmacsWindowAttachmentManager.MANAGER.removeWindowConsumer (this);
focusedActivities.remove (this);
invalidateFocus ();
super.onDestroy ();
}
if (window != null)
{
layout.removeView (window.view);
@Override
public void
onWindowFocusChanged (boolean isFocused)
{
if (isFocused && !focusedActivities.contains (this))
focusedActivities.add (this);
else
focusedActivities.remove (this);
/* Notice that the window is already available too. But do
not call noticeAvailableChild; that might assign it to some
other activity, which behaves badly. */
EmacsService.availableChildren.add (window);
window = null;
}
/* Finally, remove this activity from the list of available
activities. */
availableActivities.remove (this);
super.onStop ();
invalidateFocus ();
}
};

View file

@ -32,19 +32,22 @@ public class EmacsCopyArea implements EmacsPaintReq
private int src_x, src_y, dest_x, dest_y, width, height;
private EmacsDrawable destination, source;
private EmacsGC immutableGC;
private static Xfermode xorAlu, srcInAlu;
private static Xfermode xorAlu, srcInAlu, overAlu;
static
{
overAlu = new PorterDuffXfermode (Mode.SRC_OVER);
xorAlu = new PorterDuffXfermode (Mode.XOR);
srcInAlu = new PorterDuffXfermode (Mode.SRC_IN);
};
public
EmacsCopyArea (EmacsDrawable destination, EmacsDrawable source,
EmacsCopyArea (EmacsDrawable source, EmacsDrawable destination,
int src_x, int src_y, int width, int height,
int dest_x, int dest_y, EmacsGC immutableGC)
{
Bitmap bitmap;
this.destination = destination;
this.source = source;
this.src_x = src_x;
@ -71,6 +74,16 @@ public class EmacsCopyArea implements EmacsPaintReq
return destination;
}
private void
insetRectBy (Rect rect, int left, int top, int right,
int bottom)
{
rect.left += left;
rect.top += top;
rect.right -= right;
rect.bottom -= bottom;
}
@Override
public EmacsGC
getGC ()
@ -86,16 +99,45 @@ public class EmacsCopyArea implements EmacsPaintReq
Bitmap bitmap;
Paint maskPaint;
Canvas maskCanvas;
Bitmap maskBitmap;
Rect rect, srcRect;
Bitmap srcBitmap, maskBitmap, clipBitmap;
Rect rect, maskRect, srcRect, dstRect, maskDestRect;
boolean needFill;
/* TODO implement stippling. */
if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED)
return;
alu = immutableGC.function;
/* A copy must be created or drawBitmap could end up overwriting
itself. */
srcBitmap = source.getBitmap ();
/* If srcBitmap is out of bounds, then adjust the source rectangle
to be within bounds. Note that tiling on windows with
backgrounds is unimplemented. */
if (src_x < 0)
{
width += src_x;
dest_x -= src_x;
src_x = 0;
}
if (src_y < 0)
{
height += src_y;
dest_y -= src_y;
src_y = 0;
}
if (src_x + width > srcBitmap.getWidth ())
width = srcBitmap.getWidth () - src_x;
if (src_y + height > srcBitmap.getHeight ())
height = srcBitmap.getHeight () - src_y;
rect = getRect ();
bitmap = source.getBitmap ();
if (alu == EmacsGC.GC_COPY)
paint.setXfermode (null);
@ -103,29 +145,76 @@ public class EmacsCopyArea implements EmacsPaintReq
paint.setXfermode (xorAlu);
if (immutableGC.clip_mask == null)
canvas.drawBitmap (bitmap, new Rect (src_x, src_y,
src_x + width,
src_y + height),
rect, paint);
{
bitmap = Bitmap.createBitmap (srcBitmap,
src_x, src_y, width,
height);
canvas.drawBitmap (bitmap, null, rect, paint);
}
else
{
maskPaint = new Paint ();
srcRect = new Rect (0, 0, rect.width (),
rect.height ());
maskBitmap
= immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888,
true);
/* Drawing with a clip mask involves calculating the
intersection of the clip mask with the dst rect, and
extrapolating the corresponding part of the src rect. */
clipBitmap = immutableGC.clip_mask.bitmap;
dstRect = new Rect (dest_x, dest_y,
dest_x + width,
dest_y + height);
maskRect = new Rect (immutableGC.clip_x_origin,
immutableGC.clip_y_origin,
(immutableGC.clip_x_origin
+ clipBitmap.getWidth ()),
(immutableGC.clip_y_origin
+ clipBitmap.getHeight ()));
clipBitmap = immutableGC.clip_mask.bitmap;
if (maskBitmap == null)
if (!maskRect.setIntersect (dstRect, maskRect))
/* There is no intersection between the clip mask and the
dest rect. */
return;
maskPaint.setXfermode (srcInAlu);
/* Now figure out which part of the source corresponds to
maskRect and return it relative to srcBitmap. */
srcRect = new Rect (src_x, src_y, src_x + width,
src_y + height);
insetRectBy (srcRect, maskRect.left - dstRect.left,
maskRect.top - dstRect.top,
maskRect.right - dstRect.right,
maskRect.bottom - dstRect.bottom);
/* Finally, create a temporary bitmap that is the size of
maskRect. */
maskBitmap
= Bitmap.createBitmap (maskRect.width (), maskRect.height (),
Bitmap.Config.ARGB_8888);
/* Draw the mask onto the maskBitmap. */
maskCanvas = new Canvas (maskBitmap);
maskCanvas.drawBitmap (bitmap, new Rect (src_x, src_y,
src_x + width,
src_y + height),
srcRect, maskPaint);
canvas.drawBitmap (maskBitmap, srcRect, rect, paint);
maskRect.offset (-immutableGC.clip_x_origin,
-immutableGC.clip_y_origin);
maskCanvas.drawBitmap (immutableGC.clip_mask.bitmap,
maskRect, new Rect (0, 0,
maskRect.width (),
maskRect.height ()),
paint);
maskRect.offset (immutableGC.clip_x_origin,
immutableGC.clip_y_origin);
/* Set the transfer mode to SRC_IN to preserve only the parts
of the source that overlap with the mask. */
maskPaint = new Paint ();
maskPaint.setXfermode (srcInAlu);
/* Draw the source. */
maskDestRect = new Rect (0, 0, srcRect.width (),
srcRect.height ());
maskCanvas.drawBitmap (srcBitmap, srcRect, maskDestRect,
maskPaint);
/* Finally, draw the mask bitmap to the destination. */
paint.setXfermode (overAlu);
canvas.drawBitmap (maskBitmap, null, maskRect, paint);
}
}
}

View file

@ -57,7 +57,10 @@ public class EmacsDrawRectangle implements EmacsPaintReq
public Rect
getRect ()
{
return new Rect (x, y, x + width, y + height);
/* Canvas.drawRect actually behaves exactly like PolyRectangle wrt
to where the lines are placed, so extend the width and height
by 1 in the damage rectangle. */
return new Rect (x, y, x + width + 1, y + height + 1);
}
@Override
@ -89,9 +92,10 @@ public class EmacsDrawRectangle implements EmacsPaintReq
return;
alu = immutableGC.function;
rect = getRect ();
rect = new Rect (x, y, x + width, y + height);
paint.setStyle (Paint.Style.STROKE);
paint.setStrokeWidth (1);
if (alu == EmacsGC.GC_COPY)
paint.setXfermode (null);

View file

@ -21,6 +21,8 @@
import java.util.List;
import android.os.Build;
public abstract class EmacsFontDriver
{
/* Font weights. */
@ -75,6 +77,7 @@ public class FontSpec
public Integer size;
public Integer spacing;
public Integer avgwidth;
public Integer dpi;
@Override
public String
@ -88,7 +91,8 @@ public class FontSpec
+ " weight: " + weight
+ " slant: " + slant
+ " spacing: " + spacing
+ " avgwidth: " + avgwidth);
+ " avgwidth: " + avgwidth
+ " dpi: " + dpi);
}
};
@ -99,6 +103,17 @@ public class FontMetrics
public short width;
public short ascent;
public short descent;
@Override
public String
toString ()
{
return ("lbearing " + lbearing
+ " rbearing " + rbearing
+ " width " + width
+ " ascent " + ascent
+ " descent " + descent);
}
}
public class FontEntity extends FontSpec
@ -139,12 +154,19 @@ public abstract class FontObject extends FontSpec
public abstract FontObject openFont (FontEntity fontEntity, int pixelSize);
public abstract int hasChar (FontSpec font, char charCode);
public abstract void textExtents (FontObject font, int code[],
FontMetrics fontMetrics[]);
FontMetrics fontMetrics);
public abstract int encodeChar (FontObject fontObject, char charCode);
public abstract int draw (FontObject fontObject, EmacsGC gc,
EmacsDrawable drawable, int[] chars,
int x, int y, int backgroundWidth,
boolean withBackground);
public static EmacsFontDriver
createFontDriver ()
{
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M)
return new EmacsSdk23FontDriver ();
return new EmacsSdk7FontDriver ();
}
};

View file

@ -0,0 +1,25 @@
/* 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;
public class EmacsMultitaskActivity extends EmacsActivity
{
}

View file

@ -38,10 +38,15 @@ public class EmacsNative
libDir must be the package's data storage location for native
libraries. It is used as PATH.
pixelDensityX and pixelDensityY are the DPI values that will be
used by Emacs.
emacsService must be the emacsService singleton. */
public static native void setEmacsParams (AssetManager assetManager,
String filesDir,
String libDir,
float pixelDensityX,
float pixelDensityY,
EmacsService emacsService);
/* Initialize Emacs with the argument array ARGV. Each argument
@ -59,11 +64,20 @@ public static native void sendConfigureNotify (short window, long time,
/* Send an ANDROID_KEY_PRESS event. */
public static native void sendKeyPress (short window, long time, int state,
int keyCode);
int keyCode, int unicodeChar);
/* Send an ANDROID_KEY_RELEASE event. */
public static native void sendKeyRelease (short window, long time, int state,
int keyRelease);
int keyCode, int unicodeChar);
/* Send an ANDROID_FOCUS_IN event. */
public static native void sendFocusIn (short window, long time);
/* Send an ANDROID_FOCUS_OUT event. */
public static native void sendFocusOut (short window, long time);
/* Send an ANDROID_WINDOW_ACTION event. */
public static native void sendWindowAction (short window, int action);
static
{

View file

@ -47,7 +47,7 @@ public class EmacsPaintQueue
{
EmacsDrawable drawable, last;
Canvas canvas;
EmacsGC gc, lastGC;
EmacsGC gc;
int i;
Paint paint;
Rect rect, offsetRect, copyRect;
@ -60,45 +60,34 @@ public class EmacsPaintQueue
for (EmacsPaintReq req : paintOperations)
{
drawable = req.getDrawable ();
synchronized (req)
{
/* Ignore graphics requests for drawables that have been
destroyed. */
if (drawable.isDestroyed ())
continue;
}
canvas = drawable.lockCanvas ();
if (canvas == null)
/* No canvas is currently available. */
continue;
lastGC = gc;
gc = req.getGC ();
rect = req.getRect ();
drawable.damageRect (rect);
if (gc.clip_rects == null)
{
/* No clipping is applied. Just draw and continue. */
canvas.save ();
req.paintTo (canvas, paint, gc);
canvas.restore ();
drawable.damageRect (rect);
continue;
}
if (gc.clip_rects != null && gc.clip_rects.length > 0)
{
canvas.save ();
if (gc.clip_rects.length == 1)
{
/* There is only a single clip rect, which is simple
enough. */
canvas.save ();
canvas.clipRect (gc.clip_rects[0]);
req.paintTo (canvas, paint, gc);
canvas.restore ();
}
else
{
@ -122,9 +111,6 @@ public class EmacsPaintQueue
}
}
}
drawable.damageRect (rect);
canvas.restore ();
}
}
}

View file

@ -69,6 +69,35 @@ public class EmacsPixmap extends EmacsHandleObject
this.depth = depth;
}
public
EmacsPixmap (short handle, int width, int height, int depth)
{
super (handle);
if (depth != 1 && depth != 24)
throw new IllegalArgumentException ("Invalid depth specified"
+ " for pixmap: " + depth);
switch (depth)
{
case 1:
bitmap = Bitmap.createBitmap (width, height,
Bitmap.Config.ALPHA_8,
false);
break;
case 24:
bitmap = Bitmap.createBitmap (width, height,
Bitmap.Config.ARGB_8888,
false);
break;
}
this.width = width;
this.height = height;
this.depth = depth;
}
@Override
public Canvas
lockCanvas ()

View file

@ -0,0 +1,43 @@
/* Font backend 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.graphics.Paint;
public class EmacsSdk23FontDriver extends EmacsSdk7FontDriver
{
@Override
public int
hasChar (FontSpec font, char charCode)
{
Sdk7FontObject fontObject;
Paint paint;
if (font instanceof Sdk7FontObject)
{
fontObject = (Sdk7FontObject) font;
paint = fontObject.typeface.typefacePaint;
}
else
paint = ((Sdk7FontEntity) font).typeface.typefacePaint;
return paint.hasGlyph (String.valueOf (charCode)) ? 1 : 0;
}
};

View file

@ -28,16 +28,19 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Canvas;
import android.util.Log;
import android.os.Build;
public class EmacsSdk7FontDriver extends EmacsFontDriver
{
private static final String TOFU_STRING = "\uDB3F\uDFFD";
private static final String EM_STRING = "m";
private static final String TAG = "EmacsSdk7FontDriver";
private class Sdk7Typeface
protected class Sdk7Typeface
{
/* The typeface and paint. */
public Typeface typeface;
@ -57,7 +60,10 @@ private class Sdk7Typeface
width = UNSPECIFIED;
spacing = PROPORTIONAL;
this.typeface = typeface;
typefacePaint = new Paint ();
typefacePaint.setAntiAlias (true);
typefacePaint.setTypeface (typeface);
/* For the calls to measureText below. */
@ -160,7 +166,7 @@ else if (style.contains ("Expanded"))
}
};
private class Sdk7FontEntity extends FontEntity
protected class Sdk7FontEntity extends FontEntity
{
/* The typeface. */
public Sdk7Typeface typeface;
@ -177,19 +183,17 @@ private class Sdk7FontEntity extends FontEntity
slant = typeface.slant;
spacing = typeface.spacing;
width = typeface.width;
dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
this.typeface = typeface;
}
};
private class Sdk7FontObject extends FontObject
protected class Sdk7FontObject extends FontObject
{
/* The typeface. */
public Sdk7Typeface typeface;
/* The text size. */
public int pixelSize;
public
Sdk7FontObject (Sdk7Typeface typeface, int pixelSize)
{
@ -205,6 +209,7 @@ private class Sdk7FontObject extends FontObject
slant = typeface.slant;
spacing = typeface.spacing;
width = typeface.width;
dpi = Math.round (EmacsService.SERVICE.metrics.scaledDensity * 160f);
/* Compute the ascent and descent. */
typeface.typefacePaint.setTextSize (pixelSize);
@ -238,6 +243,93 @@ private class Sdk7FontObject extends FontObject
}
};
private class Sdk7DrawString implements EmacsPaintReq
{
private boolean drawBackground;
private Sdk7FontObject fontObject;
private char[] chars;
private EmacsGC immutableGC;
private EmacsDrawable drawable;
private Rect rect;
private int originX, originY;
public
Sdk7DrawString (Sdk7FontObject fontObject, char[] chars,
EmacsGC immutableGC, EmacsDrawable drawable,
boolean drawBackground, Rect rect,
int originX, int originY)
{
this.fontObject = fontObject;
this.chars = chars;
this.immutableGC = immutableGC;
this.drawable = drawable;
this.drawBackground = drawBackground;
this.rect = rect;
this.originX = originX;
this.originY = originY;
}
@Override
public EmacsDrawable
getDrawable ()
{
return drawable;
}
@Override
public EmacsGC
getGC ()
{
return immutableGC;
}
@Override
public void
paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC)
{
int scratch;
paint.setStyle (Paint.Style.FILL);
if (drawBackground)
{
paint.setColor (immutableGC.background | 0xff000000);
canvas.drawRect (rect, paint);
}
paint.setTextSize (fontObject.pixelSize);
paint.setColor (immutableGC.foreground | 0xff000000);
paint.setTypeface (fontObject.typeface.typeface);
paint.setAntiAlias (true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
/* Disable hinting as that leads to displayed text not
matching the computed metrics. */
paint.setHinting (Paint.HINTING_OFF);
canvas.drawText (chars, 0, chars.length, originX, originY, paint);
paint.setAntiAlias (false);
}
@Override
public Rect
getRect ()
{
Rect rect;
rect = new Rect ();
fontObject.typeface.typefacePaint.setTextSize (fontObject.pixelSize);
fontObject.typeface.typefacePaint.getTextBounds (chars, 0, chars.length,
rect);
/* Add the background rect to the damage as well. */
rect.union (this.rect);
return rect;
}
};
private String[] fontFamilyList;
private Sdk7Typeface[] typefaceList;
private Sdk7Typeface fallbackTypeface;
@ -252,7 +344,7 @@ private class Sdk7FontObject extends FontObject
systemFontsDirectory = new File ("/system/fonts");
fontFamilyList = systemFontsDirectory.list ();
typefaceList = new Sdk7Typeface[fontFamilyList.length];
typefaceList = new Sdk7Typeface[fontFamilyList.length + 3];
/* It would be nice to avoid opening each and every font upon
startup. But that doesn't seem to be possible on
@ -267,8 +359,18 @@ private class Sdk7FontObject extends FontObject
typeface);
}
/* Initialize the default monospace and serif typefaces. */
fallbackTypeface = new Sdk7Typeface ("monospace",
Typeface.MONOSPACE);
typefaceList[fontFamilyList.length] = fallbackTypeface;
fallbackTypeface = new Sdk7Typeface ("Monospace",
Typeface.MONOSPACE);
typefaceList[fontFamilyList.length + 1] = fallbackTypeface;
fallbackTypeface = new Sdk7Typeface ("Sans Serif",
Typeface.DEFAULT);
typefaceList[fontFamilyList.length + 2] = fallbackTypeface;
}
private boolean
@ -278,11 +380,6 @@ private class Sdk7FontObject extends FontObject
&& !fontSpec.family.equals (typeface.familyName))
return false;
if (fontSpec.adstyle != null
&& !fontSpec.adstyle.isEmpty ())
/* return false; */;
if (fontSpec.slant != null
&& !fontSpec.weight.equals (typeface.weight))
return false;
@ -393,7 +490,7 @@ private class Sdk7FontObject extends FontObject
paint.getTextBounds (TOFU_STRING, 0, TOFU_STRING.length (),
rect1);
paint.getTextBounds ("" + charCode, 0, 1, rect2);
return rect1.equals (rect2) ? 1 : 0;
return rect1.equals (rect2) ? 0 : 1;
}
private void
@ -434,21 +531,47 @@ raster. descent is the distance (once again counting
@Override
public void
textExtents (FontObject font, int code[], FontMetrics fontMetrics[])
textExtents (FontObject font, int code[], FontMetrics fontMetrics)
{
int i;
Paint paintCache;
Rect boundsCache;
Sdk7FontObject fontObject;
char[] text;
float width;
fontObject = (Sdk7FontObject) font;
paintCache = fontObject.typeface.typefacePaint;
paintCache.setTextSize (fontObject.pixelSize);
boundsCache = new Rect ();
for (i = 0; i < code.length; ++i)
textExtents1 ((Sdk7FontObject) font, code[i], fontMetrics[i],
if (code.length == 0)
{
fontMetrics.lbearing = 0;
fontMetrics.rbearing = 0;
fontMetrics.ascent = 0;
fontMetrics.descent = 0;
fontMetrics.width = 0;
}
else if (code.length == 1)
textExtents1 ((Sdk7FontObject) font, code[0], fontMetrics,
paintCache, boundsCache);
else
{
text = new char[code.length];
for (i = 0; i < code.length; ++i)
text[i] = (char) code[i];
paintCache.getTextBounds (text, 0, 1, boundsCache);
width = paintCache.measureText (text, 0, code.length);
fontMetrics.lbearing = (short) boundsCache.left;
fontMetrics.rbearing = (short) boundsCache.right;
fontMetrics.ascent = (short) -boundsCache.top;
fontMetrics.descent = (short) boundsCache.bottom;
fontMetrics.width = (short) Math.round (width);
}
}
@Override
@ -457,4 +580,37 @@ raster. descent is the distance (once again counting
{
return charCode;
}
@Override
public int
draw (FontObject fontObject, EmacsGC gc, EmacsDrawable drawable,
int[] chars, int x, int y, int backgroundWidth,
boolean withBackground)
{
Rect backgroundRect;
Sdk7FontObject sdk7FontObject;
Sdk7DrawString op;
char[] charsArray;
int i;
sdk7FontObject = (Sdk7FontObject) fontObject;
charsArray = new char[chars.length];
for (i = 0; i < chars.length; ++i)
charsArray[i] = (char) chars[i];
backgroundRect = new Rect ();
backgroundRect.top = y - sdk7FontObject.ascent;
backgroundRect.left = x;
backgroundRect.right = x + backgroundWidth;
backgroundRect.bottom = y + sdk7FontObject.descent;
op = new Sdk7DrawString (sdk7FontObject, charsArray,
gc.immutableGC (), drawable,
withBackground,
backgroundRect, x, y);
EmacsService.SERVICE.appendPaintOperation (op);
return 1;
}
};

View file

@ -28,6 +28,8 @@
import android.graphics.Bitmap;
import android.graphics.Point;
import android.view.View;
import android.annotation.TargetApi;
import android.app.Service;
import android.content.Context;
@ -37,7 +39,9 @@
import android.os.Looper;
import android.os.IBinder;
import android.os.Handler;
import android.util.Log;
import android.util.DisplayMetrics;
class Holder<T>
{
@ -57,14 +61,8 @@ public class EmacsService extends Service
private Handler handler;
private EmacsPaintQueue paintQueue;
/* List of all EmacsWindows that are available to attach to an
activity. */
public static List<EmacsWindow> availableChildren;
static
{
availableChildren = new ArrayList<EmacsWindow> ();
};
/* Display metrics used by font backends. */
public DisplayMetrics metrics;
@Override
public int
@ -88,7 +86,7 @@ public class EmacsService extends Service
Context context;
context = getApplicationContext ();
apiLevel = android.os.Build.VERSION.SDK_INT;
apiLevel = Build.VERSION.SDK_INT;
if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
return context.getApplicationInfo().nativeLibraryDir;
@ -105,11 +103,16 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
AssetManager manager;
Context app_context;
String filesDir, libDir;
double pixelDensityX;
double pixelDensityY;
SERVICE = this;
handler = new Handler (Looper.getMainLooper ());
manager = getAssets ();
app_context = getApplicationContext ();
metrics = getResources ().getDisplayMetrics ();
pixelDensityX = metrics.xdpi;
pixelDensityY = metrics.ydpi;
try
{
@ -122,6 +125,8 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
+ " and libDir = " + libDir);
EmacsNative.setEmacsParams (manager, filesDir, libDir,
(float) pixelDensityX,
(float) pixelDensityY,
this);
/* Start the thread that runs Emacs. */
@ -147,7 +152,8 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
}
EmacsView
getEmacsView (final EmacsWindow window)
getEmacsView (final EmacsWindow window, final int visibility,
final boolean isFocusedByDefault)
{
Runnable runnable;
final Holder<EmacsView> view;
@ -161,6 +167,8 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
synchronized (this)
{
view.thing = new EmacsView (window);
view.thing.setVisibility (visibility);
view.thing.setFocusedByDefault (isFocusedByDefault);
notify ();
}
}
@ -183,48 +191,6 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
return view.thing;
}
/* Notice that a child of the root window named WINDOW is now
available for attachment to a specific activity. */
public void
noticeAvailableChild (final EmacsWindow window)
{
Log.d (TAG, "A new child is available: " + window);
handler.post (new Runnable () {
public void
run ()
{
for (EmacsActivity activity
: EmacsActivity.availableActivities)
{
/* TODO: check if the activity matches. */
activity.attachChild (window);
break;
}
/* Nope, wait for an activity to become available. */
availableChildren.add (window);
}
});
}
/* Notice that a child of the root window named WINDOW has been
destroyed. */
public void
noticeChildDestroyed (final EmacsWindow child)
{
handler.post (new Runnable () {
@Override
public void
run ()
{
availableChildren.remove (child);
}
});
}
/* X drawing operations. These are quite primitive operations. The
drawing queue is kept on the Emacs thread, but is periodically
flushed to the application thread, upon buffers swaps and once it
@ -311,11 +277,6 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
ensurePaintQueue ();
if (gc.clip_rects != null && gc.clip_rects.length >= 1)
android.util.Log.d ("drawRectangle",
gc.clip_rects[0].toString ()
+ " " + gc.toString ());
req = new EmacsDrawRectangle (drawable, x, y,
width, height,
gc.immutableGC ());
@ -381,4 +342,12 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
{
window.clearArea (x, y, width, height);
}
public void
appendPaintOperation (EmacsPaintReq op)
{
ensurePaintQueue ();
paintQueue.appendPaintOperation (op);
checkFlush ();
}
};

View file

@ -22,6 +22,8 @@
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.os.Build;
import android.graphics.Canvas;
import android.graphics.Rect;
@ -40,7 +42,9 @@ public class EmacsSurfaceView extends SurfaceView
surfaceChanged (SurfaceHolder holder, int format,
int width, int height)
{
/* Force a buffer swap now to get the contents of the Emacs
view on screen. */
view.swapBuffers (true);
}
@Override
@ -51,7 +55,7 @@ public class EmacsSurfaceView extends SurfaceView
/* Force a buffer swap now to get the contents of the Emacs
view on screen. */
view.swapBuffers ();
view.swapBuffers (true);
}
@Override
@ -72,12 +76,37 @@ public class EmacsSurfaceView extends SurfaceView
public Canvas
lockCanvas (Rect damage)
{
return getHolder ().lockCanvas (damage);
SurfaceHolder holder;
holder = getHolder ();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
damage.setEmpty ();
return holder.lockHardwareCanvas ();
}
return holder.lockCanvas (damage);
}
/* This method is only used during debugging when it seems damage
isn't working correctly. */
public Canvas
lockCanvas ()
{
SurfaceHolder holder;
holder = getHolder ();
return holder.lockCanvas ();
}
public void
unlockCanvasAndPost (Canvas canvas)
{
getHolder ().unlockCanvasAndPost (canvas);
SurfaceHolder holder;
holder = getHolder ();
holder.unlockCanvasAndPost (canvas);
}
};

View file

@ -19,17 +19,20 @@
package org.gnu.emacs;
import android.content.res.ColorStateList;
import android.view.View;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Paint;
import android.util.Log;
import android.os.Build;
import android.util.Log;
/* This is an Android view which has a back and front buffer. When
swapBuffers is called, the back buffer is swapped to the front
@ -70,10 +73,11 @@ public class EmacsView extends ViewGroup
this.damageRegion = new Region ();
this.paint = new Paint ();
setFocusable (true);
setFocusableInTouchMode (true);
/* Create the surface view. */
this.surfaceView = new EmacsSurfaceView (this);
setFocusable (FOCUSABLE);
addView (this.surfaceView);
}
@ -162,7 +166,7 @@ else if (child.getVisibility () != GONE)
}
public void
swapBuffers ()
swapBuffers (boolean force)
{
Bitmap back;
Canvas canvas;
@ -185,14 +189,25 @@ else if (child.getVisibility () != GONE)
if (canvas == null)
return;
/* Copy from the back buffer to the canvas. */
canvas.drawBitmap (bitmap, damageRect, damageRect, paint);
/* Copy from the back buffer to the canvas. If damageRect was
made empty, then draw the entire back buffer. */
if (damageRect.isEmpty ())
canvas.drawBitmap (bitmap, 0f, 0f, paint);
else
canvas.drawBitmap (bitmap, damageRect, damageRect, paint);
/* Unlock the canvas and clear the damage. */
surfaceView.unlockCanvasAndPost (canvas);
damageRegion.setEmpty ();
}
public void
swapBuffers ()
{
swapBuffers (false);
}
@Override
public boolean
onKeyDown (int keyCode, KeyEvent event)
@ -201,6 +216,14 @@ else if (child.getVisibility () != GONE)
return true;
}
@Override
public boolean
onKeyMultiple (int keyCode, int repeatCount, KeyEvent event)
{
window.onKeyDown (keyCode, event);
return true;
}
@Override
public boolean
onKeyUp (int keyCode, KeyEvent event)
@ -208,4 +231,14 @@ else if (child.getVisibility () != GONE)
window.onKeyUp (keyCode, event);
return true;
}
@Override
public void
onFocusChanged (boolean gainFocus, int direction,
Rect previouslyFocusedRect)
{
window.onFocusChanged (gainFocus);
super.onFocusChanged (gainFocus, direction,
previouslyFocusedRect);
}
};

View file

@ -32,6 +32,8 @@
import android.view.ViewGroup;
import android.view.KeyEvent;
import android.content.Intent;
/* This defines a window, which is a handle. Windows represent a
rectangular subset of the screen with their own contents.
@ -57,10 +59,10 @@ public class EmacsWindow extends EmacsHandleObject
/* List of all children in stacking order. This must be kept
consistent! */
private ArrayList<EmacsWindow> children;
public ArrayList<EmacsWindow> children;
/* The EmacsActivity currently attached, if it exists. */
private EmacsActivity attached;
/* The window consumer currently attached, if it exists. */
private EmacsWindowAttachmentManager.WindowConsumer attached;
/* The window background scratch GC. foreground is always the
window background. */
@ -74,35 +76,44 @@ public class EmacsWindow extends EmacsHandleObject
rect = new Rect (x, y, x + width, y + height);
/* Create the view from the context's UI thread. */
view = EmacsService.SERVICE.getEmacsView (this);
/* Create the view from the context's UI thread. The window is
unmapped, so the view is GONE. */
view = EmacsService.SERVICE.getEmacsView (this, View.GONE,
parent == null);
this.parent = parent;
/* Create the list of children. */
children = new ArrayList<EmacsWindow> ();
/* The window is unmapped by default. */
view.setVisibility (View.GONE);
/* If parent is the root window, notice that there are new
children available for interested activites to pick up. */
if (parent == null)
EmacsService.SERVICE.noticeAvailableChild (this);
else
if (parent != null)
{
/* Otherwise, directly add this window as a child of that
window's view. */
synchronized (parent)
{
parent.children.add (this);
parent.view.post (new Runnable () {
@Override
public void
run ()
{
parent.view.addView (view);
}
});
}
parent.children.add (this);
parent.view.post (new Runnable () {
@Override
public void
run ()
{
parent.view.addView (view);
}
});
}
else
EmacsService.SERVICE.runOnUiThread (new Runnable () {
@Override
public void
run ()
{
EmacsWindowAttachmentManager manager;
manager = EmacsWindowAttachmentManager.MANAGER;
/* If parent is the root window, notice that there are new
children available for interested activites to pick
up. */
manager.registerWindow (EmacsWindow.this);
}
});
scratchGC = new EmacsGC ((short) 0);
}
@ -129,28 +140,35 @@ public class EmacsWindow extends EmacsHandleObject
public void
destroyHandle () throws IllegalStateException
{
synchronized (this)
{
if (!children.isEmpty ())
throw new IllegalStateException ("Trying to destroy window with "
+ "children!");
}
if (parent != null)
parent.children.remove (this);
/* Notice that the child has been destroyed. */
EmacsService.SERVICE.noticeChildDestroyed (this);
EmacsActivity.invalidateFocus ();
if (!children.isEmpty ())
throw new IllegalStateException ("Trying to destroy window with "
+ "children!");
/* Remove the view from its parent and make it invisible. */
view.post (new Runnable () {
public void
run ()
{
View parent;
EmacsWindowAttachmentManager manager;
if (EmacsActivity.focusedWindow == EmacsWindow.this)
EmacsActivity.focusedWindow = null;
manager = EmacsWindowAttachmentManager.MANAGER;
view.setVisibility (View.GONE);
if (view.getParent () != null)
((ViewGroup) view.getParent ()).removeView (view);
parent = (View) view.getParent ();
if (attached != null)
attached.makeAvailable ();
if (parent != null && attached == null)
((ViewGroup) parent).removeView (view);
manager.detachWindow (EmacsWindow.this);
}
});
@ -158,12 +176,15 @@ public class EmacsWindow extends EmacsHandleObject
}
public void
setActivity (EmacsActivity activity)
setConsumer (EmacsWindowAttachmentManager.WindowConsumer consumer)
{
synchronized (this)
{
activity = activity;
}
attached = consumer;
}
public EmacsWindowAttachmentManager.WindowConsumer
getAttachedConsumer ()
{
return attached;
}
public void
@ -233,7 +254,10 @@ public class EmacsWindow extends EmacsHandleObject
public void
run ()
{
view.setVisibility (View.VISIBLE);
/* Eventually this should check no-focus-on-map. */
view.requestFocus ();
}
});
}
@ -319,18 +343,47 @@ public class EmacsWindow extends EmacsHandleObject
public void
onKeyDown (int keyCode, KeyEvent event)
{
int state;
state = event.getModifiers ();
state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK);
EmacsNative.sendKeyPress (this.handle,
event.getEventTime (),
event.getModifiers (),
keyCode);
keyCode,
/* Ignore meta-state understood by Emacs
for now, or Ctrl+C will not be
recognized as an ASCII key press
event. */
event.getUnicodeChar (state));
}
public void
onKeyUp (int keyCode, KeyEvent event)
{
int state;
state = event.getModifiers ();
state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK);
EmacsNative.sendKeyRelease (this.handle,
event.getEventTime (),
event.getModifiers (),
keyCode);
keyCode,
event.getUnicodeChar (state));
}
public void
onFocusChanged (boolean gainFocus)
{
EmacsActivity.invalidateFocus ();
}
public void
onActivityDetached ()
{
/* Destroy the associated frame when the activity is detached. */
EmacsNative.sendWindowAction (this.handle, 0);
}
};

View file

@ -0,0 +1,166 @@
/* 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.LinkedList;
import java.util.List;
import android.content.Intent;
import android.util.Log;
/* Code to paper over the differences in lifecycles between
"activities" and windows. There are four interfaces to an instance
of this class:
registerWindowConsumer (WindowConsumer)
registerWindow (EmacsWindow)
removeWindowConsumer (WindowConsumer)
removeWindow (EmacsWindow)
A WindowConsumer is expected to allow an EmacsWindow to be attached
to it, and be created or destroyed.
Every time a window is created, registerWindow checks the list of
window consumers. If a consumer exists and does not currently have
a window of its own attached, it gets the new window. Otherwise,
the window attachment manager starts a new consumer.
Every time a consumer is registered, registerWindowConsumer checks
the list of available windows. If a window exists and is not
currently attached to a consumer, then the consumer gets it.
Finally, every time a window is removed, the consumer is
destroyed. */
public class EmacsWindowAttachmentManager
{
public static EmacsWindowAttachmentManager MANAGER;
private final static String TAG = "EmacsWindowAttachmentManager";
static
{
MANAGER = new EmacsWindowAttachmentManager ();
};
public interface WindowConsumer
{
public void attachWindow (EmacsWindow window);
public EmacsWindow getAttachedWindow ();
public void detachWindow ();
public void destroy ();
};
private List<WindowConsumer> consumers;
private List<EmacsWindow> windows;
public
EmacsWindowAttachmentManager ()
{
consumers = new LinkedList<WindowConsumer> ();
windows = new LinkedList<EmacsWindow> ();
}
public void
registerWindowConsumer (WindowConsumer consumer)
{
Log.d (TAG, "registerWindowConsumer " + consumer);
consumers.add (consumer);
for (EmacsWindow window : windows)
{
if (window.getAttachedConsumer () == null)
{
Log.d (TAG, "registerWindowConsumer: attaching " + window);
consumer.attachWindow (window);
return;
}
}
Log.d (TAG, "registerWindowConsumer: sendWindowAction 0, 0");
EmacsNative.sendWindowAction ((short) 0, 0);
}
public void
registerWindow (EmacsWindow window)
{
Intent intent;
Log.d (TAG, "registerWindow " + window);
windows.add (window);
for (WindowConsumer consumer : consumers)
{
if (consumer.getAttachedWindow () == null)
{
Log.d (TAG, "registerWindow: attaching " + consumer);
consumer.attachWindow (window);
return;
}
}
intent = new Intent (EmacsService.SERVICE,
EmacsMultitaskActivity.class);
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
| Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
EmacsService.SERVICE.startActivity (intent);
Log.d (TAG, "registerWindow: startActivity");
}
public void
removeWindowConsumer (WindowConsumer consumer)
{
EmacsWindow window;
Log.d (TAG, "removeWindowConsumer " + consumer);
window = consumer.getAttachedWindow ();
if (window != null)
{
Log.d (TAG, "removeWindowConsumer: detaching " + window);
consumer.detachWindow ();
window.onActivityDetached ();
}
Log.d (TAG, "removeWindowConsumer: removing " + consumer);
consumers.remove (consumer);
}
public void
detachWindow (EmacsWindow window)
{
WindowConsumer consumer;
Log.d (TAG, "detachWindow " + window);
if (window.getAttachedConsumer () != null)
{
consumer = window.getAttachedConsumer ();
Log.d (TAG, "detachWindow: removing" + consumer);
consumers.remove (consumer);
consumer.destroy ();
}
}
};

View file

@ -2209,7 +2209,7 @@ and `face'."
;;; The `custom' Widget.
(defface custom-button
'((((type x w32 ns haiku pgtk) (class color)) ; Like default mode line
'((((type x w32 ns haiku pgtk android) (class color)) ; Like default mode line
:box (:line-width 2 :style released-button)
:background "lightgrey" :foreground "black"))
"Face for custom buffer buttons if `custom-raised-buttons' is non-nil."
@ -2217,7 +2217,7 @@ and `face'."
:group 'custom-faces)
(defface custom-button-mouse
'((((type x w32 ns haiku pgtk) (class color))
'((((type x w32 ns haiku pgtk android) (class color))
:box (:line-width 2 :style released-button)
:background "grey90" :foreground "black")
(t
@ -2242,7 +2242,7 @@ and `face'."
(if custom-raised-buttons 'custom-button-mouse 'highlight))
(defface custom-button-pressed
'((((type x w32 ns haiku pgtk) (class color))
'((((type x w32 ns haiku pgtk android) (class color))
:box (:line-width 2 :style pressed-button)
:background "lightgrey" :foreground "black")
(t :inverse-video t))

View file

@ -2911,7 +2911,7 @@ Note: Other faces cannot inherit from the cursor face."
(((type haiku))
:foreground "B_MENU_ITEM_TEXT_COLOR"
:background "B_MENU_BACKGROUND_COLOR")
(((type x w32 ns pgtk) (class color))
(((type x w32 ns pgtk android) (class color))
:background "grey75")
(((type x) (class mono))
:background "grey"))

View file

@ -24,6 +24,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <limits.h>
#include <signal.h>
#include <semaphore.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <sys/mman.h>
@ -46,6 +47,7 @@ bool android_init_gui;
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <linux/ashmem.h>
@ -86,6 +88,7 @@ struct android_emacs_pixmap
{
jclass class;
jmethodID constructor;
jmethodID constructor_mutable;
};
struct android_graphics_point
@ -94,6 +97,12 @@ struct android_graphics_point
jmethodID constructor;
};
struct android_emacs_drawable
{
jclass class;
jmethodID get_bitmap;
};
/* The asset manager being used. */
static AAssetManager *asset_manager;
@ -106,6 +115,12 @@ char *android_site_load_path;
/* The path used to store native libraries. */
char *android_lib_dir;
/* The path used to store game files. */
char *android_game_path;
/* The display's pixel densities. */
double android_pixel_density_x, android_pixel_density_y;
/* The Android application data directory. */
static char *android_files_dir;
@ -149,6 +164,9 @@ static struct android_emacs_pixmap pixmap_class;
/* Various methods associated with the Point class. */
static struct android_graphics_point point_class;
/* Various methods associated with the EmacsDrawable class. */
static struct android_emacs_drawable drawable_class;
/* Event handling functions. Events are stored on a (circular) queue
@ -383,6 +401,10 @@ android_write_event (union android_event *event)
if (!container)
return;
/* If the event queue hasn't been initialized yet, return false. */
if (!event_queue.events.next)
return;
pthread_mutex_lock (&event_queue.mutex);
/* The event queue is full, wait for events to be read. */
@ -451,6 +473,10 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds,
/* Unlock the event queue mutex. */
pthread_mutex_unlock (&event_queue.mutex);
/* This is to shut up process.c when pselect gets EINTR. */
if (nfds_return < 0)
errno = EINTR;
return nfds_return;
}
@ -793,16 +819,20 @@ android_close (int fd)
{
if (fd < ANDROID_MAX_ASSET_FD
&& (android_table[fd].flags & ANDROID_FD_TABLE_ENTRY_IS_VALID))
{
__android_log_print (ANDROID_LOG_INFO, __func__,
"closing android file descriptor %d",
fd);
android_table[fd].flags = 0;
}
android_table[fd].flags = 0;
return close (fd);
}
/* Return the current user's ``home'' directory, which is actually the
app data directory on Android. */
const char *
android_get_home_directory (void)
{
return android_files_dir;
}
/* JNI functions called by Java. */
@ -814,6 +844,8 @@ JNIEXPORT void JNICALL
NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
jobject local_asset_manager,
jobject files_dir, jobject libs_dir,
jfloat pixel_density_x,
jfloat pixel_density_y,
jobject emacs_service_object)
{
int pipefd[2];
@ -829,6 +861,9 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
return;
}
android_pixel_density_x = pixel_density_x;
android_pixel_density_y = pixel_density_y;
__android_log_print (ANDROID_LOG_INFO, __func__,
"Initializing "PACKAGE_STRING"...\nPlease report bugs to "
PACKAGE_BUGREPORT". Thanks.\n");
@ -891,15 +926,23 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
if (!android_site_load_path)
emacs_abort ();
android_game_path = malloc (PATH_MAX + 1);
if (!android_game_path)
emacs_abort ();
snprintf (android_site_load_path, PATH_MAX, "%s/site-lisp",
android_files_dir);
snprintf (android_game_path, PATH_MAX, "%s/scores", android_files_dir);
__android_log_print (ANDROID_LOG_INFO, __func__,
"Site-lisp directory: %s\n"
"Files directory: %s\n"
"Native code directory: %s",
"Native code directory: %s\n"
"Game score path: %s\n",
android_site_load_path,
android_files_dir,
android_lib_dir);
android_lib_dir, android_game_path);
/* Make a reference to the Emacs service. */
emacs_service = (*env)->NewGlobalRef (env, emacs_service_object);
@ -997,6 +1040,7 @@ android_init_emacs_pixmap (void)
assert (pixmap_class.c_name);
FIND_METHOD (constructor, "<init>", "(S[IIII)V");
FIND_METHOD (constructor_mutable, "<init>", "(SIII)V");
#undef FIND_METHOD
}
@ -1031,6 +1075,36 @@ android_init_graphics_point (void)
#undef FIND_METHOD
}
static void
android_init_emacs_drawable (void)
{
jclass old;
drawable_class.class
= (*android_java_env)->FindClass (android_java_env,
"org/gnu/emacs/EmacsDrawable");
eassert (drawable_class.class);
old = drawable_class.class;
drawable_class.class
= (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
(jobject) old);
ANDROID_DELETE_LOCAL_REF (old);
if (!drawable_class.class)
emacs_abort ();
#define FIND_METHOD(c_name, name, signature) \
drawable_class.c_name \
= (*android_java_env)->GetMethodID (android_java_env, \
drawable_class.class, \
name, signature); \
assert (drawable_class.c_name);
FIND_METHOD (get_bitmap, "getBitmap", "()Landroid/graphics/Bitmap;");
#undef FIND_METHOD
}
extern JNIEXPORT void JNICALL
NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv)
{
@ -1063,6 +1137,15 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv)
android_init_emacs_service ();
android_init_emacs_pixmap ();
android_init_graphics_point ();
android_init_emacs_drawable ();
/* Set HOME to the app data directory. */
setenv ("HOME", android_files_dir, 1);
/* Set the cwd to that directory as well. */
if (chdir (android_files_dir))
__android_log_print (ANDROID_LOG_WARN, __func__,
"chdir: %s", strerror (errno));
/* Initialize the Android GUI. */
android_init_gui = true;
@ -1099,7 +1182,8 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
extern JNIEXPORT void JNICALL
NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode)
jint state, jint keycode,
jint unicode_char)
{
union android_event event;
@ -1108,6 +1192,7 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
event.xkey.time = time;
event.xkey.state = state;
event.xkey.keycode = keycode;
event.xkey.unicode_char = unicode_char;
android_write_event (&event);
}
@ -1115,7 +1200,8 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
extern JNIEXPORT void JNICALL
NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode)
jint state, jint keycode,
jint unicode_char)
{
union android_event event;
@ -1124,6 +1210,46 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
event.xkey.time = time;
event.xkey.state = state;
event.xkey.keycode = keycode;
event.xkey.unicode_char = unicode_char;
android_write_event (&event);
}
extern JNIEXPORT void 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;
android_write_event (&event);
}
extern JNIEXPORT void 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;
android_write_event (&event);
}
extern JNIEXPORT void JNICALL
NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
jshort window, jint action)
{
union android_event event;
event.xaction.type = ANDROID_WINDOW_ACTION;
event.xaction.window = window;
event.xaction.action = action;
android_write_event (&event);
}
@ -1142,13 +1268,6 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
#define MAX_HANDLE 65535
enum android_handle_type
{
ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_GCONTEXT,
ANDROID_HANDLE_PIXMAP,
};
struct android_handle_entry
{
/* The type. */
@ -1245,7 +1364,7 @@ android_destroy_handle (android_handle handle)
android_handles[handle].handle = NULL;
}
static jobject
jobject
android_resolve_handle (android_handle handle,
enum android_handle_type type)
{
@ -1625,14 +1744,15 @@ android_change_gc (struct android_gc *gc,
ANDROID_HANDLE_PIXMAP);
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_stipple,
emacs_gc_clip_mask,
what);
/* Clearing GCClipMask also clears the clip rectangles. */
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_clip_rects,
NULL);
if (!what)
(*android_java_env)->SetObjectField (android_java_env,
gcontext,
emacs_gc_clip_rects,
NULL);
}
if (mask & ANDROID_GC_STIPPLE)
@ -2008,10 +2128,23 @@ android_create_pixmap_from_bitmap_data (char *data, unsigned int width,
{
for (x = 0; x < width; ++x)
{
if (data[y / 8] & (1 << (x % 8)))
region[x] = foreground;
if (depth == 24)
{
/* The alpha channels must be set, or otherwise, the
pixmap will be created entirely transparent. */
if (data[y * (width + 7) / 8 + x / 8] & (1 << (x % 8)))
region[x] = foreground | 0xff000000;
else
region[x] = background | 0xff000000;
}
else
region[x] = background;
{
if (data[y * (width + 7) / 8 + x / 8] & (1 << (x % 8)))
region[x] = foreground;
else
region[x] = background;
}
}
(*android_java_env)->SetIntArrayRegion (android_java_env,
@ -2236,36 +2369,21 @@ android_create_pixmap (unsigned int width, unsigned int height,
{
android_handle prev_max_handle;
jobject object;
jintArray colors;
android_pixmap pixmap;
/* Create the color array holding the data. */
colors = (*android_java_env)->NewIntArray (android_java_env,
width * height);
if (!colors)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
/* First, allocate the pixmap handle. */
prev_max_handle = max_handle;
pixmap = android_alloc_id ();
if (!pixmap)
{
ANDROID_DELETE_LOCAL_REF ((jobject) colors);
error ("Out of pixmap handles!");
}
error ("Out of pixmap handles!");
object = (*android_java_env)->NewObject (android_java_env,
pixmap_class.class,
pixmap_class.constructor,
(jshort) pixmap, colors,
pixmap_class.constructor_mutable,
(jshort) pixmap,
(jint) width, (jint) height,
(jint) depth);
ANDROID_DELETE_LOCAL_REF ((jobject) colors);
if (!object)
{
@ -2313,6 +2431,387 @@ android_clear_area (android_window handle, int x, int y,
(jint) width, (jint) height);
}
android_pixmap
android_create_bitmap_from_data (char *bits, unsigned int width,
unsigned int height)
{
return android_create_pixmap_from_bitmap_data (bits, 1, 0,
width, height, 1);
}
struct android_image *
android_create_image (unsigned int depth, enum android_image_format format,
char *data, unsigned int width, unsigned int height)
{
struct android_image *image;
image = xmalloc (sizeof *image);
/* Fill in the fields required by image.c. N.B. that
android_destroy_image ostensibly will free data, but image.c
mostly sets and frees data itself. */
image->width = width;
image->height = height;
image->data = data;
image->depth = depth;
image->format = format;
/* Now fill in the image dimensions. There are only two depths
supported by this function. */
if (depth == 1)
{
image->bytes_per_line = (width + 7) / 8;
image->bits_per_pixel = 1;
}
else if (depth == 24)
{
image->bytes_per_line = width * 4;
image->bits_per_pixel = 32;
}
else
emacs_abort ();
return image;
}
void
android_destroy_image (struct android_image *ximg)
{
/* If XIMG->data is NULL, then it has already been freed by
image.c. */
if (ximg->data)
xfree (ximg->data);
xfree (ximg);
}
void
android_put_pixel (struct android_image *ximg, int x, int y,
unsigned long pixel)
{
char *byte, *word;
unsigned int r, g, b;
/* Ignore out-of-bounds accesses. */
if (x >= ximg->width || y >= ximg->height || x < 0 || y < 0)
return;
switch (ximg->depth)
{
case 1:
byte = ximg->data + y * ximg->bytes_per_line + x / 8;
if (pixel)
*byte |= (1 << x % 8);
else
*byte &= ~(1 << x % 8);
break;
case 24:
/* Unaligned accesses are problematic on Android devices. */
word = ximg->data + y * ximg->bytes_per_line + x * 4;
/* Swizzle the pixel into ABGR format. Android uses Skia's
``native color type'', which is ABGR. This is despite the
format being named ``ARGB'', and more confusingly
`ANDROID_BITMAP_FORMAT_RGBA_8888' in bitmap.h. */
r = pixel & 0x00ff0000;
g = pixel & 0x0000ff00;
b = pixel & 0x000000ff;
pixel = (r >> 16) | g | (b << 16) | 0xff000000;
memcpy (word, &pixel, sizeof pixel);
break;
}
}
unsigned long
android_get_pixel (struct android_image *ximg, int x, int y)
{
char *byte, *word;
unsigned int pixel, r, g, b;
if (x >= ximg->width || y >= ximg->height
|| x < 0 || y < 0)
return 0;
switch (ximg->depth)
{
case 1:
byte = ximg->data + y * ximg->bytes_per_line + x / 8;
return (*byte & (1 << x % 8)) ? 1 : 0;
case 24:
word = ximg->data + y * ximg->bytes_per_line + x * 4;
memcpy (&pixel, word, sizeof pixel);
/* Convert the pixel back to RGB. */
b = pixel & 0x00ff0000;
g = pixel & 0x0000ff00;
r = pixel & 0x000000ff;
pixel = ((r << 16) | g | (b >> 16)) & ~0xff000000;
return pixel;
}
emacs_abort ();
}
struct android_image *
android_get_image (android_drawable handle,
enum android_image_format format)
{
jobject drawable, bitmap;
AndroidBitmapInfo bitmap_info;
size_t byte_size;
void *data;
struct android_image *image;
unsigned char *data1, *data2;
int i, x;
/* N.B. that supporting windows requires some more work to make
EmacsDrawable.getBitmap thread safe. */
drawable = android_resolve_handle2 (handle, ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_PIXMAP);
/* Look up the drawable and get the bitmap corresponding to it.
Then, lock the bitmap's bits. */
bitmap = (*android_java_env)->CallObjectMethod (android_java_env,
drawable,
drawable_class.get_bitmap);
if (!bitmap)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
memset (&bitmap_info, 0, sizeof bitmap_info);
/* The NDK doc seems to imply this function can fail but doesn't say
what value it gives when it does! */
AndroidBitmap_getInfo (android_java_env, bitmap, &bitmap_info);
if (!bitmap_info.stride)
{
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
/* Compute how big the image data will be. Fail if it would be too
big. */
if (bitmap_info.format != ANDROID_BITMAP_FORMAT_A_8)
{
if (INT_MULTIPLY_WRAPV ((size_t) bitmap_info.stride,
(size_t) bitmap_info.height,
&byte_size))
{
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
}
else
/* This A8 image will be packed into A1 later on. */
byte_size = (bitmap_info.width + 7) / 8;
/* Lock the image data. Once again, the NDK documentation says the
call can fail, but does not say how to determine whether or not
it has failed, nor how the address is aligned. */
data = NULL;
AndroidBitmap_lockPixels (android_java_env, bitmap, &data);
if (!data)
{
/* Take a NULL pointer to mean that AndroidBitmap_lockPixels
failed. */
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
/* Copy the data into a new struct android_image. */
image = xmalloc (sizeof *image);
image->width = bitmap_info.width;
image->height = bitmap_info.height;
image->data = malloc (byte_size);
if (!image->data)
{
ANDROID_DELETE_LOCAL_REF (bitmap);
xfree (image);
memory_full (byte_size);
}
/* Use the format of the bitmap to determine the image depth. */
switch (bitmap_info.format)
{
case ANDROID_BITMAP_FORMAT_RGBA_8888:
image->depth = 24;
image->bits_per_pixel = 32;
break;
/* A8 images are used by Emacs to represent bitmaps. They have
to be packed manually. */
case ANDROID_BITMAP_FORMAT_A_8:
image->depth = 1;
image->bits_per_pixel = 1;
break;
/* Other formats are currently not supported. */
default:
emacs_abort ();
}
image->format = format;
if (image->depth == 24)
{
image->bytes_per_line = bitmap_info.stride;
/* Copy the bitmap data over. */
memcpy (image->data, data, byte_size);
}
else
{
/* Pack the A8 image data into bits manually. */
image->bytes_per_line = (image->width + 7) / 8;
data1 = (unsigned char *) image->data;
data2 = data;
for (i = 0; i < image->height; ++i)
{
for (x = 0; x < image->width; ++x)
/* Some bits in data1 might be initialized at this point,
but they will all be set properly later. */
data1[x / 8] = (data2[x]
? (data1[x / 8] | (1 << (x % 8)))
: (data1[x / 8] & ~(1 << (x % 8))));
data1 += image->bytes_per_line;
data2 += bitmap_info.stride;
}
}
/* Unlock the bitmap pixels. */
AndroidBitmap_unlockPixels (android_java_env, bitmap);
/* Delete the bitmap reference. */
ANDROID_DELETE_LOCAL_REF (bitmap);
return image;
}
void
android_put_image (android_pixmap handle, struct android_image *image)
{
jobject drawable, bitmap;
AndroidBitmapInfo bitmap_info;
void *data;
unsigned char *data_1, *data_2;
int i, x;
drawable = android_resolve_handle (handle, ANDROID_HANDLE_PIXMAP);
/* Look up the drawable and get the bitmap corresponding to it.
Then, lock the bitmap's bits. */
bitmap = (*android_java_env)->CallObjectMethod (android_java_env,
drawable,
drawable_class.get_bitmap);
if (!bitmap)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
memset (&bitmap_info, 0, sizeof bitmap_info);
/* The NDK doc seems to imply this function can fail but doesn't say
what value it gives when it does! */
AndroidBitmap_getInfo (android_java_env, bitmap, &bitmap_info);
if (!bitmap_info.stride)
{
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
if (bitmap_info.width != image->width
|| bitmap_info.height != image->height)
/* This is not yet supported. */
emacs_abort ();
/* Make sure the bitmap formats are compatible with each other. */
if ((image->depth == 24
&& bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
|| (image->depth == 1
&& bitmap_info.format != ANDROID_BITMAP_FORMAT_A_8))
emacs_abort ();
/* Lock the image data. Once again, the NDK documentation says the
call can fail, but does not say how to determine whether or not
it has failed, nor how the address is aligned. */
data = NULL;
AndroidBitmap_lockPixels (android_java_env, bitmap, &data);
if (!data)
{
/* Take a NULL pointer to mean that AndroidBitmap_lockPixels
failed. */
ANDROID_DELETE_LOCAL_REF (bitmap);
memory_full (0);
}
data_1 = data;
data_2 = (unsigned char *) image->data;
/* Copy the bitmap data over scanline-by-scanline. */
for (i = 0; i < image->height; ++i)
{
if (image->depth != 1)
memcpy (data_1, data_2,
image->width * (image->bits_per_pixel / 8));
else
{
/* Android internally uses a 1 byte-per-pixel format for
ALPHA_8 images. Expand the image from the 1
bit-per-pixel X format correctly. */
for (x = 0; x < image->width; ++x)
data_1[x] = (data_2[x / 8] & (1 << x % 8)) ? 0xff : 0;
}
data_1 += bitmap_info.stride;
data_2 += image->bytes_per_line;
}
/* Unlock the bitmap pixels. */
AndroidBitmap_unlockPixels (android_java_env, bitmap);
/* Delete the bitmap reference. */
ANDROID_DELETE_LOCAL_REF (bitmap);
}
#undef faccessat
/* Replace the system faccessat with one which understands AT_EACCESS.
Android's faccessat simply fails upon using AT_EACCESS, so repalce
it with zero here. This isn't caught during configuration. */
int
faccessat (int dirfd, const char *pathname, int mode, int flags)
{
static int (*real_faccessat) (int, const char *, int, int);
if (!real_faccessat)
real_faccessat = dlsym (RTLD_NEXT, "faccessat");
return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS);
}
#else /* ANDROID_STUBIFY */
/* X emulation functions for Android. */
@ -2332,4 +2831,44 @@ android_free_gc (struct android_gc *gc)
emacs_abort ();
}
struct android_image *
android_create_image (unsigned int depth, enum android_image_format format,
char *data, unsigned int width, unsigned int height)
{
emacs_abort ();
}
void
android_destroy_image (struct android_image *ximg)
{
emacs_abort ();
}
void
android_put_pixel (struct android_image *ximg, int x, int y,
unsigned long pixel)
{
emacs_abort ();
}
unsigned long
android_get_pixel (struct android_image *ximg, int x, int y)
{
emacs_abort ();
}
struct android_image *
android_get_image (android_drawable drawable,
enum android_image_format format)
{
emacs_abort ();
}
void
android_put_image (android_pixmap pixmap,
struct android_image *image)
{
emacs_abort ();
}
#endif

View file

@ -28,6 +28,8 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <jni.h>
#include <pwd.h>
#include <sys/stat.h>
#include "androidgui.h"
#endif
/* This must be used in every symbol declaration to export it to the
@ -49,6 +51,19 @@ extern int android_fstat (int, struct stat *);
extern int android_fstatat (int, const char *restrict,
struct stat *restrict, int);
extern int android_close (int);
extern const char *android_get_home_directory (void);
extern double android_pixel_density_x, android_pixel_density_y;
enum android_handle_type
{
ANDROID_HANDLE_WINDOW,
ANDROID_HANDLE_GCONTEXT,
ANDROID_HANDLE_PIXMAP,
};
extern jobject android_resolve_handle (android_handle,
enum android_handle_type);
#endif

View file

@ -531,9 +531,10 @@ android_default_font_parameter (struct frame *f, Lisp_Object parms)
if (! FONTP (font) && ! STRINGP (font))
{
const char *names[] = {
/* This will find the normal font. */
"DroidSansMono",
/* This will find the normal font. The default font size on
Android is 8. */
"monospace",
"DroidSansMono",
NULL
};
int i;

View file

@ -49,6 +49,7 @@ struct android_emacs_font_driver
jmethodID has_char;
jmethodID text_extents;
jmethodID encode_char;
jmethodID draw;
/* Static methods. */
jmethodID create_font_driver;
@ -67,6 +68,7 @@ struct android_emacs_font_spec
jfieldID size;
jfieldID spacing;
jfieldID avgwidth;
jfieldID dpi;
};
struct android_emacs_font_metrics
@ -113,6 +115,9 @@ struct androidfont_info
/* The Java-side font. */
jobject object;
/* Cached glyph metrics arranged in a two dimensional array. */
struct font_metrics **metrics;
};
struct androidfont_entity
@ -197,9 +202,11 @@ android_init_font_driver (void)
FIND_METHOD (has_char, "hasChar", "(Lorg/gnu/emacs/EmacsFontDriver$Font"
"Spec;C)I");
FIND_METHOD (text_extents, "textExtents", "(Lorg/gnu/emacs/EmacsFontDriver"
"$FontObject;[I[Lorg/gnu/emacs/EmacsFontDriver$FontMetrics;)V");
"$FontObject;[ILorg/gnu/emacs/EmacsFontDriver$FontMetrics;)V");
FIND_METHOD (encode_char, "encodeChar", "(Lorg/gnu/emacs/EmacsFontDriver"
"$FontObject;C)I");
FIND_METHOD (draw, "draw", "(Lorg/gnu/emacs/EmacsFontDriver$FontObject;"
"Lorg/gnu/emacs/EmacsGC;Lorg/gnu/emacs/EmacsDrawable;[IIIIZ)I");
font_driver_class.create_font_driver
= (*android_java_env)->GetStaticMethodID (android_java_env,
@ -252,6 +259,7 @@ android_init_font_spec (void)
FIND_FIELD (size, "size", "Ljava/lang/Integer;");
FIND_FIELD (spacing, "spacing", "Ljava/lang/Integer;");
FIND_FIELD (avgwidth, "avgwidth", "Ljava/lang/Integer;");
FIND_FIELD (dpi, "dpi", "Ljava/lang/Integer;");
#undef FIND_FIELD
}
@ -449,6 +457,9 @@ androidfont_from_lisp (Lisp_Object font)
DO_CARDINAL_FIELD (avgwidth, (FIXNUMP (AREF (font, FONT_AVGWIDTH_INDEX))
? XFIXNUM (AREF (font, FONT_AVGWIDTH_INDEX))
: -1));
DO_CARDINAL_FIELD (dpi, (FIXNUMP (AREF (font, FONT_DPI_INDEX))
? XFIXNUM (AREF (font, FONT_DPI_INDEX))
: -1));
#undef DO_CARDINAL_FIELD
@ -507,6 +518,8 @@ androidfont_from_java (jobject spec, Lisp_Object entity)
DO_CARDINAL_FIELD (size, FONT_SIZE_INDEX, false);
DO_CARDINAL_FIELD (spacing, FONT_SPACING_INDEX, false);
DO_CARDINAL_FIELD (avgwidth, FONT_AVGWIDTH_INDEX, false);
DO_CARDINAL_FIELD (dpi, FONT_DPI_INDEX, false);
#undef DO_CARDINAL_FIELD
}
@ -613,49 +626,98 @@ static int
androidfont_draw (struct glyph_string *s, int from, int to,
int x, int y, bool with_background)
{
return 0;
struct androidfont_info *info;
jarray chars;
int rc;
jobject gcontext, drawable;
verify (sizeof (unsigned int) == sizeof (jint));
info = (struct androidfont_info *) s->font;
gcontext = android_resolve_handle (s->gc->gcontext,
ANDROID_HANDLE_GCONTEXT);
drawable = android_resolve_handle (FRAME_ANDROID_WINDOW (s->f),
ANDROID_HANDLE_WINDOW);
chars = (*android_java_env)->NewIntArray (android_java_env,
to - from);
if (!chars)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
(*android_java_env)->SetIntArrayRegion (android_java_env, chars,
0, to - from,
(jint *) s->char2b + from);
info = (struct androidfont_info *) s->font;
prepare_face_for_display (s->f, s->face);
rc = (*android_java_env)->CallIntMethod (android_java_env,
font_driver,
font_driver_class.draw,
info->object,
gcontext, drawable,
chars, (jint) x, (jint) y,
(jint) s->width,
(jboolean) with_background);
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (chars);
return rc;
}
static Lisp_Object
androidfont_open_font (struct frame *f, Lisp_Object font_entity, int x)
androidfont_open_font (struct frame *f, Lisp_Object font_entity,
int pixel_size)
{
struct androidfont_info *font_info;
struct androidfont_entity *entity;
struct font *font;
Lisp_Object font_object, tem;
Lisp_Object font_object;
jobject old;
jint value;
if (x <= 0)
if (XFIXNUM (AREF (font_entity, FONT_SIZE_INDEX)) != 0)
pixel_size = XFIXNUM (AREF (font_entity, FONT_SIZE_INDEX));
else if (pixel_size == 0)
{
/* Get pixel size from frame instead. */
tem = get_frame_param (f, Qfontsize);
x = NILP (tem) ? 0 : XFIXNAT (tem);
/* This bit was copied from xfont.c. The values might need
adjustment. */
if (FRAME_FONT (f))
pixel_size = FRAME_FONT (f)->pixel_size;
else
pixel_size = 12;
}
__android_log_print (ANDROID_LOG_DEBUG, __func__,
"opening font entity %"pI"x:%d",
(EMACS_INT) font_entity, x);
(EMACS_INT) font_entity, pixel_size);
entity = (struct androidfont_entity *) XFONT_ENTITY (font_entity);
block_input ();
font_object = font_make_object (VECSIZE (struct androidfont_info),
font_entity, x);
font_entity, pixel_size);
ASET (font_object, FONT_TYPE_INDEX, Qandroid);
font_info = (struct androidfont_info *) XFONT_OBJECT (font_object);
font = &font_info->font;
font->driver = &androidfont_driver;
/* Clear font_info->object early in case GC happens later on! */
/* Clear font_info->object and font_info->metrics early in case GC
happens later on! */
font_info->object = NULL;
font_info->metrics = NULL;
unblock_input ();
font_info->object
= (*android_java_env)->CallObjectMethod (android_java_env,
font_driver,
font_driver_class.open_font,
entity->object, (jint) x);
entity->object,
(jint) pixel_size);
if (!font_info->object)
{
(*android_java_env)->ExceptionClear (android_java_env);
@ -710,9 +772,19 @@ static void
androidfont_close_font (struct font *font)
{
struct androidfont_info *info;
int i;
info = (struct androidfont_info *) font;
/* Free the font metrics cache if it exists. */
if (info->metrics)
{
for (i = 0; i < 256; ++i)
xfree (info->metrics[i]);
xfree (info->metrics);
}
/* If info->object is NULL, then FONT was unsuccessfully created,
and there is no global reference that has to be deleted. */
@ -762,18 +834,65 @@ androidfont_encode_char (struct font *font, int c)
info->object, (jchar) c);
}
static void
androidfont_cache_text_extents (struct androidfont_info *info,
unsigned int glyph,
struct font_metrics *metrics)
{
int i;
/* Glyphs larger than 65535 can't be cached. */
if (glyph >= 256 * 256)
return;
if (!info->metrics)
info->metrics = xzalloc (256 * sizeof *info->metrics);
if (!info->metrics[glyph / 256])
{
info->metrics[glyph / 256]
= xnmalloc (256, sizeof **info->metrics);
/* Now, all the metrics in that array as invalid by setting
lbearing to SHRT_MAX. */
for (i = 0; i < 256; ++i)
info->metrics[glyph / 256][i].lbearing = SHRT_MAX;
}
/* Finally, cache the glyph. */
info->metrics[glyph / 256][glyph % 256] = *metrics;
}
static bool
androidfont_check_cached_extents (struct androidfont_info *info,
unsigned int glyph,
struct font_metrics *metrics)
{
if (info->metrics && info->metrics[glyph / 256]
&& info->metrics[glyph / 256][glyph % 256].lbearing != SHRT_MAX)
{
*metrics = info->metrics[glyph / 256][glyph % 256];
return true;
}
return false;
}
static void
androidfont_text_extents (struct font *font, const unsigned int *code,
int nglyphs, struct font_metrics *metrics)
{
struct androidfont_info *info;
jarray codepoint_array, metrics_array;
jarray codepoint_array;
jobject metrics_object;
int i;
short value;
info = (struct androidfont_info *) font;
if (nglyphs == 1
&& androidfont_check_cached_extents (info, *code, metrics))
return;
/* Allocate the arrays of code points and font metrics. */
codepoint_array
= (*android_java_env)->NewIntArray (android_java_env,
@ -784,92 +903,103 @@ androidfont_text_extents (struct font *font, const unsigned int *code,
memory_full (0);
}
metrics_array
= (*android_java_env)->NewObjectArray (android_java_env,
nglyphs,
font_metrics_class.class,
NULL);
if (!metrics_array)
{
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (metrics_array);
memory_full (0);
}
verify (sizeof (unsigned int) == sizeof (jint));
if (sizeof (unsigned int) == sizeof (jint))
/* Always true on every Android device. */
(*android_java_env)->SetIntArrayRegion (android_java_env,
codepoint_array,
0, nglyphs,
(jint *) code);
else
emacs_abort ();
/* Always true on every Android device. */
(*android_java_env)->SetIntArrayRegion (android_java_env,
codepoint_array,
0, nglyphs,
(jint *) code);
for (i = 0; i < nglyphs; ++i)
{
metrics_object
= (*android_java_env)->AllocObject (android_java_env,
font_metrics_class.class);
if (!metrics_object)
{
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (metrics_array);
ANDROID_DELETE_LOCAL_REF (codepoint_array);
memory_full (0);
}
(*android_java_env)->SetObjectArrayElement (android_java_env,
metrics_array, i,
metrics_object);
ANDROID_DELETE_LOCAL_REF (metrics_object);
}
metrics_object
= (*android_java_env)->AllocObject (android_java_env,
font_metrics_class.class);
(*android_java_env)->CallVoidMethod (android_java_env,
font_driver,
font_driver_class.text_extents,
info->object, codepoint_array,
metrics_array);
metrics_object);
if ((*android_java_env)->ExceptionCheck (android_java_env))
{
(*android_java_env)->ExceptionClear (android_java_env);
ANDROID_DELETE_LOCAL_REF (metrics_array);
ANDROID_DELETE_LOCAL_REF (metrics_object);
ANDROID_DELETE_LOCAL_REF (codepoint_array);
memory_full (0);
}
for (i = 0; i < nglyphs; ++i)
{
metrics_object
= (*android_java_env)->GetObjectArrayElement (android_java_env,
metrics_array, i);
#define DO_CARDINAL_FIELD(field) \
value \
= (*android_java_env)->GetShortField (android_java_env, \
metrics_object, \
font_metrics_class.field); \
metrics[i].field = value;
metrics->field = value;
DO_CARDINAL_FIELD (lbearing);
DO_CARDINAL_FIELD (rbearing);
DO_CARDINAL_FIELD (width);
DO_CARDINAL_FIELD (ascent);
DO_CARDINAL_FIELD (descent);
DO_CARDINAL_FIELD (lbearing);
DO_CARDINAL_FIELD (rbearing);
DO_CARDINAL_FIELD (width);
DO_CARDINAL_FIELD (ascent);
DO_CARDINAL_FIELD (descent);
#undef DO_CARDINAL_FIELD
ANDROID_DELETE_LOCAL_REF (metrics_object);
}
ANDROID_DELETE_LOCAL_REF (metrics_array);
ANDROID_DELETE_LOCAL_REF (metrics_object);
ANDROID_DELETE_LOCAL_REF (codepoint_array);
/* Emacs spends a lot of time in androidfont_text_extents, which
makes calling JNI too slow. Cache the metrics for this single
glyph. */
if (nglyphs == 1)
androidfont_cache_text_extents (info, *code, metrics);
}
static Lisp_Object
androidfont_list_family (struct frame *f)
{
return Qnil;
Lisp_Object families;
jarray family_array;
jobject string;
jsize i, length;
const char *family;
family_array
= (*android_java_env)->CallObjectMethod (android_java_env,
font_driver,
font_driver_class.list_families);
if (!family_array)
{
(*android_java_env)->ExceptionClear (android_java_env);
memory_full (0);
}
length = (*android_java_env)->GetArrayLength (android_java_env,
family_array);
families = Qnil;
for (i = 0; i < length; ++i)
{
string = (*android_java_env)->GetObjectArrayElement (android_java_env,
family_array, i);
family = (*android_java_env)->GetStringUTFChars (android_java_env,
(jstring) string, NULL);
if (!family)
{
ANDROID_DELETE_LOCAL_REF (string);
ANDROID_DELETE_LOCAL_REF (family_array);
}
families = Fcons (build_string_from_utf8 (string), families);
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
(jstring) string,
family);
ANDROID_DELETE_LOCAL_REF (string);
}
ANDROID_DELETE_LOCAL_REF (family_array);
return Fnreverse (families);
}
struct font_driver androidfont_driver =

View file

@ -201,6 +201,9 @@ enum android_event_type
ANDROID_KEY_PRESS,
ANDROID_KEY_RELEASE,
ANDROID_CONFIGURE_NOTIFY,
ANDROID_FOCUS_IN,
ANDROID_FOCUS_OUT,
ANDROID_WINDOW_ACTION,
};
struct android_any_event
@ -209,6 +212,13 @@ struct android_any_event
android_window window;
};
enum android_modifier_mask
{
ANDROID_SHIFT_MASK = 193,
ANDROID_CONTROL_MASK = 4096,
ANDROID_ALT_MASK = 2,
};
struct android_key_event
{
enum android_event_type type;
@ -216,8 +226,18 @@ struct android_key_event
android_time time;
unsigned int state;
unsigned int keycode;
unsigned int unicode_char;
};
/* These hard coded values are Android modifier keycodes derived
through experimentation. */
#define ANDROID_IS_MODIFIER_KEY(key) \
((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)
struct android_configure_event
{
enum android_event_type type;
@ -227,12 +247,35 @@ struct android_configure_event
int width, height;
};
struct android_focus_event
{
enum android_event_type type;
android_window window;
android_time time;
};
struct android_window_action_event
{
enum android_event_type type;
/* The window handle. This can be ANDROID_NONE. */
android_window window;
/* Numerical identifier for this action. If 0 and WINDOW is set,
then it means the frame associated with that window has been
destroyed. Otherwise, it means Emacs should create a new
frame. */
unsigned int action;
};
union android_event
{
enum android_event_type type;
struct android_any_event xany;
struct android_key_event xkey;
struct android_configure_event xconfigure;
struct android_focus_event xfocus;
struct android_window_action_event xaction;
};
extern int android_pending (void);
@ -303,9 +346,47 @@ extern android_pixmap android_create_pixmap (unsigned int, unsigned int,
extern void android_set_ts_origin (struct android_gc *, int, int);
extern void android_clear_area (android_window, int, int, unsigned int,
unsigned int);
extern android_pixmap android_create_bitmap_from_data (char *, unsigned int,
unsigned int);
#endif
/* Image support. Keep the API as similar to XImage as possible. To
avoid leaving a huge mess of "#ifndef ANDROID_STUBIFY" in image.c,
stubs should be defined for all functions. */
enum android_image_format
{
ANDROID_Z_PIXMAP,
};
struct android_image
{
int width, height;
enum android_image_format format;
char *data;
int depth;
int bytes_per_line;
int bits_per_pixel;
};
extern struct android_image *android_create_image (unsigned int,
enum android_image_format,
char *, unsigned int,
unsigned int);
extern void android_destroy_image (struct android_image *);
extern void android_put_pixel (struct android_image *, int, int,
unsigned long);
extern unsigned long android_get_pixel (struct android_image *, int, int);
extern struct android_image *android_get_image (android_drawable,
enum android_image_format);
extern void android_put_image (android_pixmap, struct android_image *);
/* X emulation stuff also needed while building stubs. */
extern struct android_gc *android_create_gc (enum android_gc_value_mask,

View file

@ -39,6 +39,8 @@ struct android_display_info *x_display_list;
#ifndef ANDROID_STUBIFY
#include <android/log.h>
enum
{
ANDROID_EVENT_NORMAL,
@ -141,6 +143,124 @@ android_flush_dirty_back_buffer_on (struct frame *f)
show_back_buffer (f);
}
/* Convert between the modifier bits Android uses and the modifier
bits Emacs uses. */
static int
android_android_to_emacs_modifiers (struct android_display_info *dpyinfo,
int state)
{
return ((state & ANDROID_CONTROL_MASK) ? ctrl_modifier : 0
| (state & ANDROID_SHIFT_MASK) ? shift_modifier : 0
| (state & ANDROID_ALT_MASK) ? meta_modifier : 0);
}
static int
android_emacs_to_android_modifiers (struct android_display_info *dpyinfo,
intmax_t state)
{
return ((state & ctrl_modifier) ? ANDROID_CONTROL_MASK : 0
| (state & shift_modifier) ? ANDROID_SHIFT_MASK : 0
| (state & meta_modifier) ? ANDROID_ALT_MASK : 0);
}
static void android_frame_rehighlight (struct android_display_info *);
static void
android_lower_frame (struct frame *f)
{
/* TODO. */
}
static void
android_raise_frame (struct frame *f)
{
/* TODO. */
}
static void
android_new_focus_frame (struct android_display_info *dpyinfo,
struct frame *frame)
{
struct frame *old_focus;
old_focus = dpyinfo->focus_frame;
if (frame != dpyinfo->focus_frame)
{
/* Set this before calling other routines, so that they see
the correct value of x_focus_frame. */
dpyinfo->focus_frame = frame;
if (old_focus && old_focus->auto_lower)
android_lower_frame (old_focus);
if (dpyinfo->focus_frame && dpyinfo->focus_frame->auto_raise)
dpyinfo->pending_autoraise_frame = dpyinfo->focus_frame;
else
dpyinfo->pending_autoraise_frame = NULL;
}
android_frame_rehighlight (dpyinfo);
}
static void
android_focus_changed (int type, int state,
struct android_display_info *dpyinfo,
struct frame *frame, struct input_event *bufp)
{
if (type == ANDROID_FOCUS_IN)
{
if (dpyinfo->x_focus_event_frame != frame)
{
android_new_focus_frame (dpyinfo, frame);
dpyinfo->x_focus_event_frame = frame;
bufp->kind = FOCUS_IN_EVENT;
XSETFRAME (bufp->frame_or_window, frame);
}
frame->output_data.android->focus_state |= state;
}
else if (type == ANDROID_FOCUS_OUT)
{
frame->output_data.android->focus_state &= ~state;
if (dpyinfo->x_focus_event_frame == frame)
{
dpyinfo->x_focus_event_frame = 0;
android_new_focus_frame (dpyinfo, 0);
bufp->kind = FOCUS_OUT_EVENT;
XSETFRAME (bufp->frame_or_window, frame);
}
if (frame->pointer_invisible)
android_toggle_invisible_pointer (frame, false);
}
}
static void
android_detect_focus_change (struct android_display_info *dpyinfo,
struct frame *frame,
union android_event *event,
struct input_event *bufp)
{
if (!frame)
return;
switch (event->type)
{
case ANDROID_FOCUS_IN:
case ANDROID_FOCUS_OUT:
android_focus_changed (event->type, FOCUS_EXPLICIT,
dpyinfo, frame, bufp);
break;
default:
break;
}
}
static int
handle_one_android_event (struct android_display_info *dpyinfo,
union android_event *event, int *finish,
@ -149,11 +269,20 @@ handle_one_android_event (struct android_display_info *dpyinfo,
union android_event configureEvent;
struct frame *f, *any, *mouse_frame;
Mouse_HLInfo *hlinfo;
union buffered_input_event inev;
int modifiers, count;
/* It is okay for this to not resemble handle_one_xevent so much.
Differences in event handling code are much less nasty than
stuble differences in the graphics code. */
count = 0;
hlinfo = &dpyinfo->mouse_highlight;
*finish = ANDROID_EVENT_NORMAL;
any = android_window_to_frame (dpyinfo, event->xany.window);
EVENT_INIT (inev.ie);
switch (event->type)
{
case ANDROID_CONFIGURE_NOTIFY:
@ -192,12 +321,15 @@ handle_one_android_event (struct android_display_info *dpyinfo,
case ANDROID_KEY_PRESS:
/* Set f to any. There are no ``outer windows'' on Android. */
f = any;
/* If mouse-highlight is an integer, input clears out
mouse highlighting. */
if (!hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight)
&& (any == 0
|| !EQ (f->tool_bar_window, hlinfo->mouse_face_window)
|| !EQ (f->tab_bar_window, hlinfo->mouse_face_window)))
|| !EQ (any->tool_bar_window, hlinfo->mouse_face_window)
|| !EQ (any->tab_bar_window, hlinfo->mouse_face_window)))
{
mouse_frame = hlinfo->mouse_face_mouse_frame;
@ -208,13 +340,90 @@ handle_one_android_event (struct android_display_info *dpyinfo,
android_flush_dirty_back_buffer_on (mouse_frame);
}
event->xkey.state
|= android_emacs_to_android_modifiers (dpyinfo,
extra_keyboard_modifiers);
modifiers = event->xkey.state;
/* Common for all keysym input events. */
XSETFRAME (inev.ie.frame_or_window, any);
inev.ie.modifiers
= android_android_to_emacs_modifiers (dpyinfo, modifiers);
inev.ie.timestamp = event->xkey.time;
/* First deal with keysyms which have defined translations to
characters. */
if (event->xkey.unicode_char >= 32
&& event->xkey.unicode_char < 128)
{
inev.ie.kind = ASCII_KEYSTROKE_EVENT;
inev.ie.code = event->xkey.unicode_char;
}
else if (event->xkey.unicode_char < 32)
{
/* If the key is a modifier key, just return. */
if (ANDROID_IS_MODIFIER_KEY (event->xkey.keycode))
goto done_keysym;
/* Next, deal with special ``characters'' by giving the
keycode to keyboard.c. */
inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
inev.ie.code = event->xkey.keycode;
}
else
{
/* Finally, deal with Unicode characters. */
inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
inev.ie.code = event->xkey.unicode_char;
}
goto done_keysym;
done_keysym:
goto OTHER;
case ANDROID_FOCUS_IN:
case ANDROID_FOCUS_OUT:
android_detect_focus_change (dpyinfo, any, event, &inev.ie);
goto OTHER;
case ANDROID_WINDOW_ACTION:
f = any;
if (event->xaction.action == 0)
{
/* Action 0 either means to destroy a frame or to create a
new frame, depending on whether or not
event->xaction.window exists. */
if (event->xaction.window)
{
if (!f)
goto OTHER;
inev.ie.kind = DELETE_WINDOW_EVENT;
XSETFRAME (inev.ie.frame_or_window, f);
}
else
/* A new frame must be created. */;
}
goto OTHER;
default:
goto OTHER;
}
OTHER:
if (inev.ie.kind != NO_EVENT)
{
kbd_buffer_store_buffered_event (&inev, hold_quit);
count++;
}
return 0;
return count;
}
static int
@ -378,15 +587,52 @@ android_focus_frame (struct frame *f, bool noactivate)
}
static void
android_frame_rehighlight (struct frame *f)
android_frame_rehighlight (struct android_display_info *dpyinfo)
{
/* TODO */
struct frame *old_highlight;
old_highlight = dpyinfo->highlight_frame;
if (dpyinfo->focus_frame)
{
dpyinfo->highlight_frame
= ((FRAMEP (FRAME_FOCUS_FRAME (dpyinfo->focus_frame)))
? XFRAME (FRAME_FOCUS_FRAME (dpyinfo->focus_frame))
: dpyinfo->focus_frame);
if (!FRAME_LIVE_P (dpyinfo->highlight_frame))
{
fset_focus_frame (dpyinfo->focus_frame, Qnil);
dpyinfo->highlight_frame = dpyinfo->focus_frame;
}
}
else
dpyinfo->highlight_frame = 0;
if (dpyinfo->highlight_frame != old_highlight)
{
/* This is not yet required on Android. */
#if 0
if (old_highlight)
android_frame_unhighlight (old_highlight);
if (dpyinfo->highlight_frame)
android_frame_highlight (dpyinfo->highlight_frame);
#endif
}
}
static void
android_frame_rehighlight_hook (struct frame *f)
{
android_frame_rehighlight (FRAME_DISPLAY_INFO (f));
}
static void
android_frame_raise_lower (struct frame *f, bool raise_flag)
{
/* TODO */
if (raise_flag)
android_raise_frame (f);
else
android_lower_frame (f);
}
void
@ -401,6 +647,10 @@ android_make_frame_visible (struct frame *f)
void
android_make_frame_invisible (struct frame *f)
{
/* Don't keep the highlight on an invisible frame. */
if (FRAME_DISPLAY_INFO (f)->highlight_frame == f)
FRAME_DISPLAY_INFO (f)->highlight_frame = 0;
android_unmap_window (FRAME_ANDROID_WINDOW (f));
SET_FRAME_VISIBLE (f, false);
@ -584,6 +834,8 @@ android_free_frame_resources (struct frame *f)
if (f == dpyinfo->focus_frame)
dpyinfo->focus_frame = 0;
if (f == dpyinfo->x_focus_event_frame)
dpyinfo->x_focus_event_frame = 0;
if (f == dpyinfo->highlight_frame)
dpyinfo->highlight_frame = 0;
if (f == hlinfo->mouse_face_mouse_frame)
@ -783,7 +1035,7 @@ android_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
if (p->overlay_p)
{
clipmask = android_create_pixmap_from_bitmap_data (bits, p->wd, p->h,
1, 0, 0);
1, 0, 1);
gcv.clip_mask = clipmask;
gcv.clip_x_origin = p->x;
@ -1606,158 +1858,47 @@ android_draw_image_foreground (struct glyph_string *s)
if (s->img->pixmap)
{
if (s->img->mask)
unsigned long mask = (ANDROID_GC_CLIP_MASK
| ANDROID_GC_CLIP_X_ORIGIN
| ANDROID_GC_CLIP_Y_ORIGIN
| ANDROID_GC_FUNCTION);
struct android_gc_values xgcv;
struct android_rectangle clip_rect, image_rect, r;
xgcv.clip_mask = s->img->mask;
xgcv.clip_x_origin = x - s->slice.x;
xgcv.clip_y_origin = y - s->slice.y;
xgcv.function = ANDROID_GC_COPY;
android_change_gc (s->gc, mask, &xgcv);
get_glyph_string_clip_rect (s, &clip_rect);
image_rect.x = x;
image_rect.y = y;
image_rect.width = s->slice.width;
image_rect.height = s->slice.height;
if (gui_intersect_rectangles (&clip_rect, &image_rect, &r))
android_copy_area (s->img->pixmap,
FRAME_ANDROID_WINDOW (s->f),
s->gc, s->slice.x + r.x - x,
s->slice.y + r.y - y,
r.width, r.height, r.x, r.y);
/* When the image has a mask, we can expect that at least part
of a mouse highlight or a block cursor will be visible. If
the image doesn't have a mask, make a block cursor visible by
drawing a rectangle around the image. I believe it's looking
better if we do nothing here for mouse-face. */
if (s->hl == DRAW_CURSOR && !s->img->mask)
{
unsigned long mask = (ANDROID_GC_CLIP_MASK
| ANDROID_GC_CLIP_X_ORIGIN
| ANDROID_GC_CLIP_Y_ORIGIN
| ANDROID_GC_FUNCTION);
struct android_gc_values xgcv;
struct android_rectangle clip_rect, image_rect, r;
xgcv.clip_mask = s->img->mask;
xgcv.clip_x_origin = x - s->slice.x;
xgcv.clip_y_origin = y - s->slice.y;
xgcv.function = ANDROID_GC_COPY;
android_change_gc (s->gc, mask, &xgcv);
get_glyph_string_clip_rect (s, &clip_rect);
image_rect.x = x;
image_rect.y = y;
image_rect.width = s->slice.width;
image_rect.height = s->slice.height;
if (gui_intersect_rectangles (&clip_rect, &image_rect, &r))
android_copy_area (s->img->pixmap, FRAME_ANDROID_WINDOW (s->f),
s->gc, s->slice.x + r.x - x,
s->slice.y + r.y - y,
r.x, r.y, r.width, r.height);
int relief = eabs (s->img->relief);
android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc,
x - relief, y - relief,
s->slice.width + relief*2 - 1,
s->slice.height + relief*2 - 1);
}
else
{
unsigned long mask = (ANDROID_GC_CLIP_MASK
| ANDROID_GC_CLIP_X_ORIGIN
| ANDROID_GC_CLIP_Y_ORIGIN
| ANDROID_GC_FUNCTION);
struct android_gc_values xgcv;
struct android_rectangle clip_rect, image_rect, r;
xgcv.clip_mask = s->img->mask;
xgcv.clip_x_origin = x - s->slice.x;
xgcv.clip_y_origin = y - s->slice.y;
xgcv.function = ANDROID_GC_COPY;
android_change_gc (s->gc, mask, &xgcv);
get_glyph_string_clip_rect (s, &clip_rect);
image_rect.x = x;
image_rect.y = y;
image_rect.width = s->slice.width;
image_rect.height = s->slice.height;
if (gui_intersect_rectangles (&clip_rect, &image_rect, &r))
android_copy_area (s->img->pixmap,
FRAME_ANDROID_WINDOW (s->f),
s->gc, s->slice.x + r.x - x,
s->slice.y + r.y - y,
r.x, r.y, r.width, r.height);
/* When the image has a mask, we can expect that at
least part of a mouse highlight or a block cursor will
be visible. If the image doesn't have a mask, make
a block cursor visible by drawing a rectangle around
the image. I believe it's looking better if we do
nothing here for mouse-face. */
if (s->hl == DRAW_CURSOR)
{
int relief = eabs (s->img->relief);
android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc,
x - relief, y - relief,
s->slice.width + relief*2 - 1,
s->slice.height + relief*2 - 1);
}
}
}
else
/* Draw a rectangle if image could not be loaded. */
android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, x, y,
s->slice.width - 1, s->slice.height - 1);
}
/* Draw the foreground of image glyph string S to PIXMAP. */
static void
android_draw_image_foreground_1 (struct glyph_string *s,
android_pixmap pixmap)
{
int x = 0;
int y = s->ybase - s->y - image_ascent (s->img, s->face, &s->slice);
/* If first glyph of S has a left box line, start drawing it to the
right of that line. */
if (s->face->box != FACE_NO_BOX
&& s->first_glyph->left_box_line_p
&& s->slice.x == 0)
x += max (s->face->box_vertical_line_width, 0);
/* If there is a margin around the image, adjust x- and y-position
by that margin. */
if (s->slice.x == 0)
x += s->img->hmargin;
if (s->slice.y == 0)
y += s->img->vmargin;
if (s->img->pixmap)
{
if (s->img->mask)
{
unsigned long mask = (ANDROID_GC_CLIP_MASK
| ANDROID_GC_CLIP_X_ORIGIN
| ANDROID_GC_CLIP_Y_ORIGIN
| ANDROID_GC_FUNCTION);
struct android_gc_values xgcv;
struct android_rectangle clip_rect, image_rect, r;
xgcv.clip_mask = s->img->mask;
xgcv.clip_x_origin = x - s->slice.x;
xgcv.clip_y_origin = y - s->slice.y;
xgcv.function = ANDROID_GC_COPY;
android_change_gc (s->gc, mask, &xgcv);
get_glyph_string_clip_rect (s, &clip_rect);
image_rect.x = x;
image_rect.y = y;
image_rect.width = s->slice.width;
image_rect.height = s->slice.height;
if (gui_intersect_rectangles (&clip_rect, &image_rect, &r))
android_copy_area (s->img->pixmap, pixmap, s->gc,
s->slice.x + r.x - x,
s->slice.y + r.y - y,
r.x, r.y, r.width, r.height);
android_set_clip_mask (s->gc, ANDROID_NONE);
}
else
{
android_copy_area (s->img->pixmap, pixmap, s->gc,
s->slice.x, s->slice.y,
s->slice.width, s->slice.height, x, y);
/* When the image has a mask, we can expect that at
least part of a mouse highlight or a block cursor will
be visible. If the image doesn't have a mask, make
a block cursor visible by drawing a rectangle around
the image. I believe it's looking better if we do
nothing here for mouse-face. */
if (s->hl == DRAW_CURSOR)
{
int r = eabs (s->img->relief);
android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f),
s->gc, x - r, y - r,
s->slice.width + r*2 - 1,
s->slice.height + r*2 - 1);
}
}
android_set_clip_mask (s->gc, ANDROID_NONE);
}
else
/* Draw a rectangle if image could not be loaded. */
@ -1771,7 +1912,6 @@ android_draw_image_glyph_string (struct glyph_string *s)
int box_line_hwidth = max (s->face->box_vertical_line_width, 0);
int box_line_vwidth = max (s->face->box_horizontal_line_width, 0);
int height;
android_pixmap pixmap = ANDROID_NONE;
height = s->height;
if (s->slice.y == 0)
@ -1793,79 +1933,27 @@ android_draw_image_glyph_string (struct glyph_string *s)
if (s->stippled_p)
s->row->stipple_p = true;
if (s->img->mask)
int x = s->x;
int y = s->y;
int width = s->background_width;
if (s->first_glyph->left_box_line_p
&& s->slice.x == 0)
{
/* Create a pixmap as large as the glyph string. Fill it
with the background color. Copy the image to it, using
its mask. Copy the temporary pixmap to the display. */
int depth = FRAME_DISPLAY_INFO (s->f)->n_planes;
/* Create a pixmap as large as the glyph string. */
pixmap = android_create_pixmap (s->background_width,
s->height, depth);
/* Don't clip in the following because we're working on the
pixmap. */
android_set_clip_mask (s->gc, ANDROID_NONE);
/* Fill the pixmap with the background color/stipple. */
if (s->stippled_p)
{
/* Fill background with a stipple pattern. */
android_set_fill_style (s->gc, ANDROID_FILL_OPAQUE_STIPPLED);
android_set_ts_origin (s->gc, - s->x, - s->y);
android_fill_rectangle (pixmap, s->gc,
0, 0, s->background_width, s->height);
android_set_fill_style (s->gc, ANDROID_FILL_OPAQUE_STIPPLED);
android_set_ts_origin (s->gc, 0, 0);
}
else
{
struct android_gc_values xgcv;
android_get_gc_values (s->gc, (ANDROID_GC_FOREGROUND
| ANDROID_GC_BACKGROUND),
&xgcv);
android_set_foreground (s->gc, xgcv.background);
android_fill_rectangle (pixmap, s->gc,
0, 0, s->background_width, s->height);
android_set_background (s->gc, xgcv.foreground);
}
x += box_line_hwidth;
width -= box_line_hwidth;
}
else
{
int x = s->x;
int y = s->y;
int width = s->background_width;
if (s->first_glyph->left_box_line_p
&& s->slice.x == 0)
{
x += box_line_hwidth;
width -= box_line_hwidth;
}
if (s->slice.y == 0)
y += box_line_vwidth;
if (s->slice.y == 0)
y += box_line_vwidth;
android_draw_glyph_string_bg_rect (s, x, y, width, height);
}
android_draw_glyph_string_bg_rect (s, x, y, width, height);
s->background_filled_p = true;
}
/* Draw the foreground. */
if (pixmap != ANDROID_NONE)
{
android_draw_image_foreground_1 (s, pixmap);
android_set_glyph_string_clipping (s);
android_copy_area (pixmap, FRAME_ANDROID_WINDOW (s->f), s->gc,
0, 0, s->background_width, s->height, s->x,
s->y);
android_free_pixmap (pixmap);
}
else
android_draw_image_foreground (s);
android_draw_image_foreground (s);
/* If we must draw a relief around the image, do it. */
if (s->img->relief
@ -3047,7 +3135,7 @@ android_create_terminal (struct android_display_info *dpyinfo)
terminal->mouse_position_hook = android_mouse_position;
terminal->get_focus_frame = android_get_focus_frame;
terminal->focus_frame_hook = android_focus_frame;
terminal->frame_rehighlight_hook = android_frame_rehighlight;
terminal->frame_rehighlight_hook = android_frame_rehighlight_hook;
terminal->frame_raise_lower_hook = android_frame_raise_lower;
terminal->frame_visible_invisible_hook
= android_make_frame_visible_invisible;
@ -3117,9 +3205,12 @@ android_term_init (void)
dpyinfo->color_map = color_map;
/* TODO! */
dpyinfo->resx = 96.0;
dpyinfo->resy = 96.0;
#ifndef ANDROID_STUBIFY
dpyinfo->resx = android_pixel_density_x;
dpyinfo->resy = android_pixel_density_y;
#endif
/* https://lists.gnu.org/r/emacs-devel/2015-11/msg00194.html */
dpyinfo->smallest_font_height = 1;
@ -3130,6 +3221,9 @@ android_term_init (void)
/* The display "connection" is now set up, and it must never go
away. */
terminal->reference_count = 30000;
/* Set the baud rate to the same value it gets set to on X. */
baud_rate = 19200;
}

View file

@ -28,8 +28,8 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
struct android_bitmap_record
{
/* The image backing the bitmap. */
Emacs_Pixmap img;
/* The image backing the bitmap and its mask. */
android_pixmap pixmap, mask;
/* The file from which it comes. */
char *file;
@ -37,8 +37,11 @@ struct android_bitmap_record
/* The number of references to it. */
int refcount;
/* The height and width. */
int height, width;
/* The height and width and the depth. */
int height, width, depth;
/* Whether or not there is a mask. */
bool have_mask;
};
struct android_display_info
@ -95,6 +98,9 @@ struct android_display_info
/* The frame currently with the input focus. */
struct frame *focus_frame;
/* The last frame mentioned in a focus event. */
struct frame *x_focus_event_frame;
/* The frame which currently has the visual highlight, and should
get keyboard input. It points to the focus frame's selected
window's frame, but can differ. */
@ -206,8 +212,24 @@ struct android_output
/* The background for which the above relief GCs were set up.
They are changed only when a different background is involved. */
unsigned long relief_background;
/* Focus state. Only present for consistency with X; it is actually
a boolean. */
int focus_state;
};
enum
{
/* Values for focus_state, used as bit mask. EXPLICIT means we
received a FocusIn for the frame and know it has the focus.
IMPLICIT means we received an EnterNotify and the frame may
have the focus if no window manager is running. FocusOut and
LeaveNotify clears EXPLICIT/IMPLICIT. */
FOCUS_NONE = 0,
FOCUS_IMPLICIT = 1,
FOCUS_EXPLICIT = 2
};
/* Return the Android output data for frame F. */
#define FRAME_ANDROID_OUTPUT(f) ((f)->output_data.android)
#define FRAME_OUTPUT_DATA(f) ((f)->output_data.android)

View file

@ -160,8 +160,8 @@ typedef Emacs_Pixmap Emacs_Pix_Context;
#ifdef HAVE_ANDROID
#include "androidgui.h"
typedef struct android_display_info Display_Info;
typedef Emacs_Pixmap Emacs_Pix_Container;
typedef Emacs_Pixmap Emacs_Pix_Context;
typedef struct android_image *Emacs_Pix_Container;
typedef struct android_image *Emacs_Pix_Context;
#endif
#ifdef HAVE_WINDOW_SYSTEM
@ -3119,6 +3119,13 @@ struct image
int original_width, original_height;
# endif
#endif /* HAVE_X_WINDOWS */
#ifdef HAVE_ANDROID
/* Android images of the image, corresponding to the above Pixmaps.
Non-NULL means it and its Pixmap counterpart may be out of sync
and the latter is outdated. NULL means the X image has been
synchronized to Pixmap. */
struct android_image *ximg, *mask_img;
#endif /* HAVE_ANDROID */
#ifdef HAVE_NTGUI
XFORM xform;
#endif

View file

@ -1440,9 +1440,8 @@ main (int argc, char **argv)
bool only_version = false;
sort_args (argc, argv);
old_argc = argc, argc = 0;
while (argv[argc]
/* Don't allow going past argv. */
&& argc < old_argc) argc++;
/* Don't allow going past argv. */
while (argc < old_argc && argv[argc]) argc++;
skip_args = 0;
if (argmatch (argv, argc, "-version", "--version", 3, NULL, &skip_args))

View file

@ -898,6 +898,10 @@ user_homedir (char const *name)
p[length] = 0;
struct passwd *pw = getpwnam (p);
SAFE_FREE ();
#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
if (pw && !pw->pw_dir && pw->pw_uid == getuid ())
return (char *) android_get_home_directory ();
#endif
if (!pw || (pw->pw_dir && !IS_ABSOLUTE_FILE_NAME (pw->pw_dir)))
return NULL;
return pw->pw_dir;
@ -1888,6 +1892,11 @@ get_homedir (void)
pw = getpwuid (getuid ());
if (pw)
home = pw->pw_dir;
#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
if (!home && pw && pw->pw_uid == getuid ())
return android_get_home_directory ();
#endif
if (!home)
return "";
}

View file

@ -547,8 +547,14 @@ CHECK_FONT_GET_OBJECT (Lisp_Object x)
return XFONT_OBJECT (x);
}
#ifndef HAVE_ANDROID
/* Number of pt per inch (from the TeXbook). */
#define PT_PER_INCH 72.27
#else
/* Android uses this value instead to compensate for different device
dimensions. */
#define PT_PER_INCH 160.00
#endif
/* Return a pixel size (integer) corresponding to POINT size (double)
on resolution DPI. */

View file

@ -1422,25 +1422,30 @@ If BITMAP overrides a standard fringe bitmap, the original bitmap is restored.
On X, we bit-swap the built-in bitmaps and reduce bitmap
from short to char array if width is <= 8 bits.
The Android port tries to follow X as closely as possible, so do
that there too.
On MAC with big-endian CPU, we need to byte-swap each short.
On W32 and MAC (little endian), there's no need to do this.
*/
#if defined (HAVE_X_WINDOWS) || defined (HAVE_PGTK)
static const unsigned char swap_nibble[16] = {
0x0, 0x8, 0x4, 0xc, /* 0000 1000 0100 1100 */
0x2, 0xa, 0x6, 0xe, /* 0010 1010 0110 1110 */
0x1, 0x9, 0x5, 0xd, /* 0001 1001 0101 1101 */
0x3, 0xb, 0x7, 0xf}; /* 0011 1011 0111 1111 */
#endif /* HAVE_X_WINDOWS */
#if defined (HAVE_X_WINDOWS) || defined (HAVE_PGTK) || defined (HAVE_ANDROID)
static const unsigned char swap_nibble[16] =
{
0x0, 0x8, 0x4, 0xc, /* 0000 1000 0100 1100 */
0x2, 0xa, 0x6, 0xe, /* 0010 1010 0110 1110 */
0x1, 0x9, 0x5, 0xd, /* 0001 1001 0101 1101 */
0x3, 0xb, 0x7, 0xf, /* 0011 1011 0111 1111 */
};
#endif
static void
init_fringe_bitmap (int which, struct fringe_bitmap *fb, int once_p)
{
if (once_p || fb->dynamic)
{
#if defined (HAVE_X_WINDOWS)
#if defined (HAVE_X_WINDOWS) || defined (HAVE_ANDROID)
unsigned short *bits = fb->bits;
int j;
@ -1488,7 +1493,7 @@ init_fringe_bitmap (int which, struct fringe_bitmap *fb, int once_p)
}
}
#endif /* not USE_CAIRO */
#endif /* HAVE_X_WINDOWS */
#endif /* HAVE_X_WINDOWS || HAVE_ANDROID */
#if !defined(HAVE_X_WINDOWS) && defined (HAVE_PGTK)
unsigned short *bits = fb->bits;

View file

@ -177,11 +177,14 @@ typedef struct haiku_bitmap_record Bitmap_Record;
#ifdef HAVE_ANDROID
#include "androidterm.h"
typedef struct android_bitmap_record Bitmap_Record;
/* TODO: implement images on Android. */
#define GET_PIXEL(ximg, x, y) 0
#define PUT_PIXEL(ximg, x, y, pixel) ((void) (pixel))
typedef struct android_image XImage;
typedef android_pixmap Pixmap;
#define GET_PIXEL(ximg, x, y) android_get_pixel (ximg, x, y)
#define PUT_PIXEL(ximg, x, y, pixel) android_put_pixel (ximg, x, y, pixel)
#define NO_PIXMAP 0
#define PIX_MASK_RETAIN 0
@ -507,6 +510,18 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
return -1;
#endif /* HAVE_X_WINDOWS */
#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
android_pixmap bitmap;
bitmap = android_create_bitmap_from_data (bits, width, height);
if (!bitmap)
return -1;
#else
((void) dpyinfo);
emacs_abort ();
#endif
#ifdef HAVE_NTGUI
Lisp_Object frame UNINIT; /* The value is not used. */
Emacs_Pixmap bitmap;
@ -619,14 +634,14 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
dpyinfo->bitmaps[id - 1].width = width;
dpyinfo->bitmaps[id - 1].refcount = 1;
#ifdef HAVE_X_WINDOWS
#if defined HAVE_X_WINDOWS && defined HAVE_ANDROID
dpyinfo->bitmaps[id - 1].pixmap = bitmap;
dpyinfo->bitmaps[id - 1].have_mask = false;
dpyinfo->bitmaps[id - 1].depth = 1;
#ifdef USE_CAIRO
dpyinfo->bitmaps[id - 1].stipple = NULL;
#endif /* USE_CAIRO */
#endif /* HAVE_X_WINDOWS */
#endif /* HAVE_X_WINDOWS || HAVE_ANDROID */
#ifdef HAVE_NTGUI
dpyinfo->bitmaps[id - 1].pixmap = bitmap;
@ -637,7 +652,7 @@ image_create_bitmap_from_data (struct frame *f, char *bits,
return id;
}
#if defined HAVE_HAIKU || defined HAVE_NS
#if defined HAVE_HAIKU || defined HAVE_NS || defined HAVE_ANDROID
static char *slurp_file (int, ptrdiff_t *);
static Lisp_Object image_find_image_fd (Lisp_Object, int *);
static bool xbm_read_bitmap_data (struct frame *, char *, char *,
@ -861,8 +876,62 @@ image_create_bitmap_from_file (struct frame *f, Lisp_Object file)
/* This function should never be called when building stubs. */
emacs_abort ();
#else
/* you lose, not yet implemented TODO */
return 0;
ptrdiff_t id, size;
int fd, width, height, rc;
char *contents, *data;
Lisp_Object found;
android_pixmap bitmap;
/* Look for an existing bitmap with the same name. */
for (id = 0; id < dpyinfo->bitmaps_last; ++id)
{
if (dpyinfo->bitmaps[id].refcount
&& dpyinfo->bitmaps[id].file
&& !strcmp (dpyinfo->bitmaps[id].file, SSDATA (file)))
{
++dpyinfo->bitmaps[id].refcount;
return id + 1;
}
}
/* Search bitmap-file-path for the file, if appropriate. */
if (openp (Vx_bitmap_file_path, file, Qnil, &found,
make_fixnum (R_OK), false, false)
< 0)
return -1;
if (!STRINGP (image_find_image_fd (file, &fd))
&& !STRINGP (image_find_image_fd (found, &fd)))
return -1;
contents = slurp_file (fd, &size);
if (!contents)
return -1;
rc = xbm_read_bitmap_data (f, contents, contents + size,
&width, &height, &data, 0);
if (!rc)
{
xfree (contents);
return -1;
}
xfree (contents);
bitmap = android_create_bitmap_from_data (data, width, height);
xfree (data);
id = image_allocate_bitmap_record (f);
dpyinfo->bitmaps[id - 1].pixmap = bitmap;
dpyinfo->bitmaps[id - 1].have_mask = false;
dpyinfo->bitmaps[id - 1].refcount = 1;
dpyinfo->bitmaps[id - 1].file = xlispstrdup (file);
dpyinfo->bitmaps[id - 1].depth = 1;
dpyinfo->bitmaps[id - 1].height = height;
dpyinfo->bitmaps[id - 1].width = width;
return id;
#endif
#endif
}
@ -882,6 +951,13 @@ free_bitmap_record (Display_Info *dpyinfo, Bitmap_Record *bm)
#endif /* USE_CAIRO */
#endif /* HAVE_X_WINDOWS */
#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
android_free_pixmap (bm->pixmap);
if (bm->have_mask)
android_free_pixmap (bm->pixmap);
#endif
#ifdef HAVE_NTGUI
DeleteObject (bm->pixmap);
#endif /* HAVE_NTGUI */
@ -969,7 +1045,7 @@ static void image_unget_x_image (struct image *, bool, Emacs_Pix_Container);
image_unget_x_image (img, mask_p, ximg)
#endif
#ifdef HAVE_X_WINDOWS
#if defined HAVE_X_WINDOWS || defined HAVE_ANDROID
#ifndef USE_CAIRO
static void image_sync_to_pixmaps (struct frame *, struct image *);
@ -983,6 +1059,8 @@ static bool x_create_x_image_and_pixmap (struct frame *, int, int, int,
XImage **, Pixmap *);
static void x_destroy_x_image (XImage *);
#if defined HAVE_X_WINDOWS
/* Create a mask of a bitmap. Note is this not a perfect mask.
It's nicer with some borders in this context */
@ -1079,7 +1157,9 @@ x_create_bitmap_mask (struct frame *f, ptrdiff_t id)
x_destroy_x_image (mask_img);
}
#endif /* HAVE_X_WINDOWS */
#endif
#endif /* HAVE_X_WINDOWS || defined HAVE_ANDROID*/
/***********************************************************************
Image types
@ -1113,7 +1193,7 @@ struct image_type
#if defined HAVE_RSVG || defined HAVE_PNG || defined HAVE_GIF || \
defined HAVE_TIFF || defined HAVE_JPEG || defined HAVE_XPM || \
defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_PGTK || \
defined HAVE_WEBP
defined HAVE_WEBP || defined HAVE_ANDROID
# ifdef WINDOWSNT
# define IMAGE_TYPE_INIT(f) f
# else
@ -1615,7 +1695,7 @@ prepare_image_for_display (struct frame *f, struct image *img)
}
unblock_input ();
}
#elif defined HAVE_X_WINDOWS
#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
if (!img->load_failed_p)
{
block_input ();
@ -1822,7 +1902,7 @@ image_clear_image_1 (struct frame *f, struct image *img, int flags)
/* NOTE (HAVE_NS): background color is NOT an indexed color! */
img->background_valid = 0;
}
#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
if (img->ximg)
{
image_destroy_x_image (img->ximg);
@ -1840,7 +1920,7 @@ image_clear_image_1 (struct frame *f, struct image *img, int flags)
img->mask = NO_PIXMAP;
img->background_transparent_valid = 0;
}
#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
if (img->mask_img)
{
image_destroy_x_image (img->mask_img);
@ -2212,11 +2292,11 @@ image_size_in_bytes (struct image *img)
if (msk)
size += msk->height * msk->bytes_per_line;
#elif defined HAVE_X_WINDOWS
/* Use a nominal depth of 24 bpp for pixmap and 1 bpp for mask,
to avoid having to query the server. */
#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
/* Use a nominal depth of 24 and a bpp of 32 for pixmap and 1 bpp
for mask, to avoid having to query the server. */
if (img->pixmap != NO_PIXMAP)
size += img->width * img->height * 3;
size += img->width * img->height * 4;
if (img->mask != NO_PIXMAP)
size += img->width * img->height / 8;
@ -3195,9 +3275,12 @@ mark_image_cache (struct image_cache *c)
/***********************************************************************
X / NS / W32 support code
Most of this code is shared with Android to make
it easier to maintain.
***********************************************************************/
#ifdef HAVE_X_WINDOWS
#if defined HAVE_X_WINDOWS || defined HAVE_ANDROID
static bool
x_check_image_size (XImage *ximg, int width, int height)
{
@ -3213,7 +3296,11 @@ x_check_image_size (XImage *ximg, int width, int height)
int bitmap_pad, depth, bytes_per_line;
if (ximg)
{
#ifndef HAVE_ANDROID
bitmap_pad = ximg->bitmap_pad;
#else
bitmap_pad = (ximg->depth == 1 ? 8 : 32);
#endif
depth = ximg->depth;
bytes_per_line = ximg->bytes_per_line;
}
@ -3231,16 +3318,23 @@ static bool
x_create_x_image_and_pixmap (struct frame *f, int width, int height, int depth,
XImage **ximg, Pixmap *pixmap)
{
#ifndef HAVE_ANDROID
Display *display = FRAME_X_DISPLAY (f);
Drawable drawable = FRAME_X_DRAWABLE (f);
#endif
eassert (input_blocked_p ());
if (depth <= 0)
depth = FRAME_DISPLAY_INFO (f)->n_planes;
#ifndef HAVE_ANDROID
*ximg = XCreateImage (display, FRAME_X_VISUAL (f),
depth, ZPixmap, 0, NULL, width, height,
depth > 16 ? 32 : depth > 8 ? 16 : 8, 0);
#else
*ximg = android_create_image (depth, ANDROID_Z_PIXMAP, NULL, width,
height);
#endif
if (*ximg == NULL)
{
image_error ("Unable to allocate X image");
@ -3260,7 +3354,15 @@ x_create_x_image_and_pixmap (struct frame *f, int width, int height, int depth,
(*ximg)->data = xmalloc ((*ximg)->bytes_per_line * height);
/* Allocate a pixmap of the same size. */
#ifndef HAVE_ANDROID
*pixmap = XCreatePixmap (display, drawable, width, height, depth);
#else
#ifndef ANDROID_STUBIFY
*pixmap = android_create_pixmap (width, height, depth);
#else
emacs_abort ();
#endif
#endif
if (*pixmap == NO_PIXMAP)
{
x_destroy_x_image (*ximg);
@ -3281,7 +3383,11 @@ x_destroy_x_image (XImage *ximg)
ximg->data = NULL;
}
#ifndef HAVE_ANDROID
XDestroyImage (ximg);
#else
android_destroy_image (ximg);
#endif
}
# if !defined USE_CAIRO && defined HAVE_XRENDER
@ -3348,7 +3454,7 @@ x_create_xrender_picture (struct frame *f, Emacs_Pixmap pixmap, int depth)
static bool
image_check_image_size (Emacs_Pix_Container ximg, int width, int height)
{
#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
return x_check_image_size (ximg, width, height);
#else
/* FIXME: Implement this check for the HAVE_NS and HAVE_NTGUI cases.
@ -3372,16 +3478,6 @@ image_create_x_image_and_pixmap_1 (struct frame *f, int width, int height, int d
Emacs_Pix_Container *pimg,
Emacs_Pixmap *pixmap, Picture *picture)
{
#ifdef HAVE_ANDROID
#ifdef ANDROID_STUBIFY
/* This function should never be called when building stubs. */
emacs_abort ();
#else
/* you lose, not yet implemented TODO */
return false;
#endif
#endif
#ifdef USE_CAIRO
eassert (input_blocked_p ());
@ -3396,7 +3492,7 @@ image_create_x_image_and_pixmap_1 (struct frame *f, int width, int height, int d
*pimg = *pixmap;
return 1;
#elif defined HAVE_X_WINDOWS
#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
if (!x_create_x_image_and_pixmap (f, width, height, depth, pimg, pixmap))
return 0;
# ifdef HAVE_XRENDER
@ -3542,7 +3638,7 @@ image_create_x_image_and_pixmap_1 (struct frame *f, int width, int height, int d
static void
image_destroy_x_image (Emacs_Pix_Container pimg)
{
#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
x_destroy_x_image (pimg);
#else
eassert (input_blocked_p ());
@ -3581,7 +3677,9 @@ gui_put_x_image (struct frame *f, Emacs_Pix_Container pimg,
XPutImage (FRAME_X_DISPLAY (f), pixmap, gc, pimg, 0, 0, 0, 0,
pimg->width, pimg->height);
XFreeGC (FRAME_X_DISPLAY (f), gc);
#endif /* HAVE_X_WINDOWS */
#elif defined HAVE_ANDROID
android_put_image (pixmap, pimg);
#endif
#ifdef HAVE_NS
eassert (pimg == pixmap);
@ -3618,7 +3716,7 @@ static void
image_put_x_image (struct frame *f, struct image *img, Emacs_Pix_Container ximg,
bool mask_p)
{
#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
if (!mask_p)
{
eassert (img->ximg == NULL);
@ -3636,7 +3734,7 @@ image_put_x_image (struct frame *f, struct image *img, Emacs_Pix_Container ximg,
#endif
}
#if defined HAVE_X_WINDOWS && !defined USE_CAIRO
#if (defined HAVE_X_WINDOWS || defined HAVE_ANDROID) && !defined USE_CAIRO
/* Put the X images recorded in IMG on frame F into pixmaps, then free
the X images and their buffers. */
@ -3690,19 +3788,9 @@ image_unget_x_image_or_dc (struct image *img, bool mask_p,
static Emacs_Pix_Container
image_get_x_image (struct frame *f, struct image *img, bool mask_p)
{
#ifdef HAVE_ANDROID
#ifdef ANDROID_STUBIFY
/* This function should never be called when building stubs. */
emacs_abort ();
#else
/* you lose, not yet implemented TODO */
return 0;
#endif
#endif
#if defined USE_CAIRO || defined (HAVE_HAIKU)
return !mask_p ? img->pixmap : img->mask;
#elif defined HAVE_X_WINDOWS
#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
XImage *ximg_in_img = !mask_p ? img->ximg : img->mask_img;
if (ximg_in_img)
@ -3712,9 +3800,15 @@ image_get_x_image (struct frame *f, struct image *img, bool mask_p)
return XGetImage (FRAME_X_DISPLAY (f), !mask_p ? img->pixmap : img->mask,
0, 0, img->original_width, img->original_height, ~0, ZPixmap);
#endif
#ifndef HAVE_ANDROID
else
return XGetImage (FRAME_X_DISPLAY (f), !mask_p ? img->pixmap : img->mask,
0, 0, img->width, img->height, ~0, ZPixmap);
#else
else
return android_get_image (!mask_p ? img->pixmap : img->mask,
ANDROID_Z_PIXMAP);
#endif
#elif defined (HAVE_NS)
Emacs_Pix_Container pixmap = !mask_p ? img->pixmap : img->mask;
@ -3727,13 +3821,18 @@ static void
image_unget_x_image (struct image *img, bool mask_p, Emacs_Pix_Container ximg)
{
#ifdef USE_CAIRO
#elif defined HAVE_X_WINDOWS
#elif defined HAVE_X_WINDOWS || defined HAVE_ANDROID
XImage *ximg_in_img = !mask_p ? img->ximg : img->mask_img;
if (ximg_in_img)
eassert (ximg == ximg_in_img);
#ifdef HAVE_ANDROID
else
android_destroy_image (ximg);
#else
else
XDestroyImage (ximg);
#endif
#elif defined (HAVE_NS)
ns_release_object (ximg);
#endif
@ -4256,6 +4355,15 @@ Create_Pixmap_From_Bitmap_Data (struct frame *f, struct image *img, char *data,
img->picture = x_create_xrender_picture (f, img->pixmap, 0);
# endif
#elif defined HAVE_ANDROID
#ifndef ANDROID_STUBIFY
img->pixmap
= android_create_pixmap_from_bitmap_data (data, img->width, img->height,
fg, bg,
FRAME_DISPLAY_INFO (f)->n_planes);
#else
emacs_abort ();
#endif
#elif defined HAVE_NTGUI
img->pixmap
= w32_create_pixmap_from_bitmap_data (img->width, img->height, data);
@ -4686,7 +4794,8 @@ xbm_load (struct frame *f, struct image *img)
XPM images
***********************************************************************/
#if defined (HAVE_XPM) || defined (HAVE_NS) || defined (HAVE_PGTK)
#if defined (HAVE_XPM) || defined (HAVE_NS) || defined (HAVE_PGTK) \
|| defined (HAVE_ANDROID)
static bool xpm_image_p (Lisp_Object object);
static bool xpm_load (struct frame *f, struct image *img);
@ -4716,7 +4825,8 @@ static bool xpm_load (struct frame *f, struct image *img);
#endif /* not HAVE_NTGUI */
#endif /* HAVE_XPM */
#if defined HAVE_XPM || defined USE_CAIRO || defined HAVE_NS || defined HAVE_HAIKU
#if defined HAVE_XPM || defined USE_CAIRO || defined HAVE_NS \
|| defined HAVE_HAIKU || defined HAVE_ANDROID
/* Indices of image specification fields in xpm_format, below. */
@ -4736,7 +4846,8 @@ enum xpm_keyword_index
XPM_LAST
};
#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_PGTK
#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU \
|| defined HAVE_PGTK || defined HAVE_ANDROID
/* Vector of image_keyword structures describing the format
of valid XPM image specifications. */
@ -4978,7 +5089,8 @@ init_xpm_functions (void)
#endif /* WINDOWSNT */
#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_PGTK
#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU \
|| defined HAVE_PGTK || defined HAVE_ANDROID
/* Value is true if COLOR_SYMBOLS is a valid color symbols list
for XPM images. Such a list must consist of conses whose car and
cdr are strings. */
@ -5014,9 +5126,9 @@ xpm_image_p (Lisp_Object object)
&& (! fmt[XPM_COLOR_SYMBOLS].count
|| xpm_valid_color_symbols_p (fmt[XPM_COLOR_SYMBOLS].value)));
}
#endif /* HAVE_XPM || HAVE_NS || HAVE_HAIKU || HAVE_PGTK */
#endif /* HAVE_XPM || HAVE_NS || HAVE_HAIKU || HAVE_PGTK || HAVE_ANDROID */
#endif /* HAVE_XPM || USE_CAIRO || HAVE_NS || HAVE_HAIKU */
#endif /* HAVE_XPM || USE_CAIRO || HAVE_NS || HAVE_HAIKU || HAVE_ANDROID */
#if defined HAVE_XPM && defined HAVE_X_WINDOWS && !defined USE_GTK
ptrdiff_t
@ -5389,10 +5501,12 @@ xpm_load (struct frame *f, struct image *img)
#if (defined USE_CAIRO && defined HAVE_XPM) \
|| (defined HAVE_NS && !defined HAVE_XPM) \
|| (defined HAVE_HAIKU && !defined HAVE_XPM) \
|| (defined HAVE_PGTK && !defined HAVE_XPM)
|| (defined HAVE_PGTK && !defined HAVE_XPM) \
|| (defined HAVE_ANDROID && !defined HAVE_XPM)
/* XPM support functions for NS and Haiku where libxpm is not available, and for
Cairo. Only XPM version 3 (without any extensions) is supported. */
/* XPM support functions for NS, Haiku and Android where libxpm is not
available, and for Cairo. Only XPM version 3 (without any
extensions) is supported. */
static void xpm_put_color_table_v (Lisp_Object, const char *,
int, Lisp_Object);
@ -6492,11 +6606,16 @@ image_pixmap_draw_cross (struct frame *f, Emacs_Pixmap pixmap,
#elif HAVE_HAIKU
be_draw_cross_on_pixmap (pixmap, x, y, width, height, color);
#elif HAVE_ANDROID
#ifdef ANDROID_STUBIFY
/* This function should never be called when building stubs. */
emacs_abort ();
#ifndef ANDROID_STUBIFY
struct android_gc *gc;
gc = android_create_gc (0, NULL);
android_set_foreground (gc, color);
android_draw_line (pixmap, gc, x, y, x + width - 1, y + height - 1);
android_draw_line (pixmap, gc, x, y + height - 1, x + width - 1, y);
android_free_gc (gc);
#else
/* you lose, not yet implemented TODO */
emacs_abort ();
#endif
#endif
}
@ -6552,7 +6671,7 @@ image_disable_image (struct frame *f, struct image *img)
#define MaskForeground(f) PIX_MASK_DRAW
#endif /* USE_CAIRO || HAVE_HAIKU */
#if !defined USE_CAIRO && !defined HAVE_HAIKU && !defined HAVE_ANDROID
#if !defined USE_CAIRO && !defined HAVE_HAIKU
image_sync_to_pixmaps (f, img);
#endif /* !USE_CAIRO && !HAVE_HAIKU */
image_pixmap_draw_cross (f, img->pixmap, 0, 0, img->width, img->height,
@ -12079,7 +12198,8 @@ static struct image_type const image_types[] =
{ SYMBOL_INDEX (Qjpeg), jpeg_image_p, jpeg_load, image_clear_image,
IMAGE_TYPE_INIT (init_jpeg_functions) },
#endif
#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU || defined HAVE_PGTK
#if defined HAVE_XPM || defined HAVE_NS || defined HAVE_HAIKU \
|| defined HAVE_PGTK || defined HAVE_ANDROID
{ SYMBOL_INDEX (Qxpm), xpm_image_p, xpm_load, image_clear_image,
IMAGE_TYPE_INIT (init_xpm_functions) },
#endif
@ -12138,7 +12258,7 @@ syms_of_image (void)
DEFVAR_LISP ("image-types", Vimage_types,
doc: /* List of potentially supported image types.
Each element of the list is a symbol for an image type, like `jpeg' or `png'.
To check whether it is really supported, use `image-type-available-p'. */);
check whether it is really supported, use `image-type-available-p'. */);
Vimage_types = Qnil;
DEFVAR_LISP ("max-image-size", Vmax_image_size,
@ -12241,7 +12361,8 @@ non-numeric, there is no explicit limit on the size of images. */);
add_image_type (Qxbm);
#if defined (HAVE_XPM) || defined (HAVE_NS) \
|| defined (HAVE_HAIKU) || defined (HAVE_PGTK)
|| defined (HAVE_HAIKU) || defined (HAVE_PGTK) \
|| defined (HAVE_ANDROID)
DEFSYM (Qxpm, "xpm");
add_image_type (Qxpm);
#endif

View file

@ -4909,7 +4909,58 @@ static const char *const lispy_accent_keys[] =
"dead-horn",
};
#ifdef HAVE_NTGUI
#ifdef HAVE_ANDROID
#define FUNCTION_KEY_OFFSET 0
const char *const lispy_function_keys[] =
{
/* All elements in this array default to 0, except for the few
function keys that Emacs recognizes. */
[111] = "escape",
[121] = "break",
[122] = "home",
[123] = "end",
[124] = "insert",
[131] = "f1",
[132] = "f2",
[133] = "f3",
[134] = "f4",
[135] = "f5",
[136] = "f6",
[137] = "f7",
[138] = "f8",
[139] = "f9",
[140] = "f10",
[141] = "f11",
[142] = "f12",
[160] = "kp-ret",
[19] = "up",
[20] = "down",
[213] = "muhenkan",
[214] = "henkan",
[215] = "hiragana-katakana",
[218] = "kana",
[21] = "left",
[22] = "right",
[259] = "help",
[268] = "kp-up-left",
[269] = "kp-down-left",
[270] = "kp-up-right",
[271] = "kp-down-right",
[277] = "cut",
[278] = "copy",
[279] = "paste",
[28] = "clear",
[4] = "back",
[61] = "tab",
[66] = "return",
[67] = "backspace",
[82] = "menu",
[92] = "page-up",
[93] = "page-down",
};
#elif defined HAVE_NTGUI
#define FUNCTION_KEY_OFFSET 0x0
const char *const lispy_function_keys[] =

View file

@ -2073,7 +2073,7 @@ static void
build_load_history (Lisp_Object filename, bool entire)
{
Lisp_Object tail, prev, newelt;
Lisp_Object tem, tem2;
Lisp_Object tem, tem2, association;
bool foundit = 0;
tail = Vload_history;
@ -2088,7 +2088,7 @@ build_load_history (Lisp_Object filename, bool entire)
{
foundit = 1;
/* If we're loading the entire file, remove old data. */
/* If we're loading the entire file, remove old data. */
if (entire)
{
if (NILP (prev))
@ -2096,8 +2096,8 @@ build_load_history (Lisp_Object filename, bool entire)
else
Fsetcdr (prev, XCDR (tail));
}
/* Otherwise, cons on new symbols that are not already members. */
/* Otherwise, cons on new symbols that are not already
members. */
else
{
tem2 = Vcurrent_load_list;
@ -2122,8 +2122,16 @@ build_load_history (Lisp_Object filename, bool entire)
front of load-history, the most-recently-loaded position. Also
do this if we didn't find an existing member for the file. */
if (entire || !foundit)
Vload_history = Fcons (Fnreverse (Vcurrent_load_list),
Vload_history);
{
association = Fnreverse (Vcurrent_load_list);
if (!NILP (association) && STRINGP (XCAR (association)))
/* readevalloop can be called with SOURCENAME set to some
nonsense value, meaning the car of ASSOCIATION will be nil
(or worse something else), leading to an invalid
Vload_history. Ignore such invalid entries. */
Vload_history = Fcons (association, Vload_history);
}
}
static void