From cfbc8a5dbcd362b69b37b4e6832ae4a31834364c Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 31 Dec 2022 18:04:18 +0800 Subject: Bring up the Android operating system and its window system * .dir-locals.el (c-mode): Add ANDROID_EXPORT noise macro. * .gitignore: Add new files to ignore. * Makefile.in: Adjust for Android. * admin/merge-gnulib: Add new warning. * configure.ac: Detect Android. Run cross-configuration for Android when appropriate. * etc/DEBUG: Document how to debug Emacs on Android. * java/AndroidManifest.xml: * java/Makefile.in: * java/README: * java/debug.sh: * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): * java/org/gnu/emacs/EmacsApplication.java (EmacsApplication): * java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea): * java/org/gnu/emacs/EmacsDrawLine.java (EmacsDrawLine): * java/org/gnu/emacs/EmacsDrawPoint.java (EmacsDrawPoint): * java/org/gnu/emacs/EmacsDrawRectangle.java (EmacsDrawRectangle): * java/org/gnu/emacs/EmacsDrawable.java (EmacsDrawable): * java/org/gnu/emacs/EmacsFillPolygon.java (EmacsFillPolygon): * java/org/gnu/emacs/EmacsFillRectangle.java (EmacsFillRectangle): * java/org/gnu/emacs/EmacsFontDriver.java (EmacsFontDriver): * java/org/gnu/emacs/EmacsGC.java (EmacsGC): * java/org/gnu/emacs/EmacsHandleObject.java (EmacsHandleObject): * java/org/gnu/emacs/EmacsNative.java (EmacsNative): * java/org/gnu/emacs/EmacsPaintQueue.java (EmacsPaintQueue): * java/org/gnu/emacs/EmacsPaintReq.java (EmacsPaintReq): * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): * java/org/gnu/emacs/EmacsSdk7FontDriver.java (EmacsSdk7FontDriver): * java/org/gnu/emacs/EmacsService.java (class Holder) (EmacsService): * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): * java/org/gnu/emacs/EmacsThread.java (EmacsThread): * java/org/gnu/emacs/EmacsView.java (EmacsView): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): New files and classes. * lib-src/Makefile.in (srcdir): * lib/Makefile.in (VPATH): (HAVE_NATIVE_COMP): (libgnu_a_SOURCES): (DEPFLAGS): Configure correctly for cross-compiling. * lib/faccessat.c: * lib/fpending.c (__fpending): * lib/open.c: * lib/unistd.c (_GL_UNISTD_INLINE): Temporary adjustments to gnulib. * lisp/frame.el (display-graphic-p): (display-screens): (display-pixel-height): (display-pixel-width): (display-mm-height): (display-mm-width): (display-backing-store): (display-save-under): (display-planes): (display-color-cells): (display-visual-class): Adjust for new window system `android'. * lisp/image/wallpaper.el (x-open-connection): Add declaration. * lisp/loadup.el (featurep): Load up files for Android. * lisp/net/eww.el (eww-form-submit, eww-form-file) (eww-form-checkbox, eww-form-select): Adjust faces for android. * lisp/term/android-win.el: New file. * src/Makefile.in: Add new targets emacs.so and android-emacs, then adjust for cross compilation. * src/alloc.c (cleanup_vector): Clean up Android font entities as well. (garbage_collect): Mark androidterm. * src/android-emacs.c (main): * src/android.c (ANDROID_THROW, enum android_fd_table_entry_flags) (struct android_emacs_service, struct android_emacs_pixmap) (struct android_graphics_point, struct android_event_container) (struct android_event_queue, android_run_select_thread) (android_handle_sigusr1, android_init_events, android_pending) (android_next_event, android_write_event, android_select) (android_run_debug_thread, android_user_full_name) (android_get_asset_name, android_fstat, android_fstatat) (android_file_access_p, android_hack_asset_fd, android_open) (android_close, JNICALL, android_init_emacs_service) (android_init_emacs_pixmap, android_init_graphics_point) (MAX_HANDLE, struct android_handle_entry, android_alloc_id) (android_destroy_handle, android_resolve_handle) (android_resolve_handle2, android_change_window_attributes) (android_create_window, android_set_window_background) (android_destroy_window, android_init_android_rect_class) (android_init_emacs_gc_class, android_create_gc, android_free_gc) (android_change_gc, android_set_clip_rectangles) (android_reparent_window, android_lookup_method) (android_clear_window, android_map_window, android_unmap_window) (android_resize_window, android_move_window, android_swap_buffers) (android_get_gc_values, android_set_foreground) (android_fill_rectangle, android_create_pixmap_from_bitmap_data) (android_set_clip_mask, android_set_fill_style, android_copy_area) (android_free_pixmap, android_set_background, android_fill_polygon) (android_draw_rectangle, android_draw_point, android_draw_line) (android_create_pixmap, android_set_ts_origin, android_clear_area): * src/android.h (ANDROID_EXPORT): * src/androidfns.c (android_display_info_for_name) (check_android_display_info, check_x_display_info, gamma_correct) (android_defined_color, android_decode_color) (android_implicitly_set_name, android_explicitly_set_name) (android_set_tool_bar_lines, android_change_tool_bar_height) (android_set_tab_bar_lines, android_change_tab_bar_height) (android_set_scroll_bar_default_height) (android_set_scroll_bar_default_width, android_icon_verify) (android_icon, android_make_gc, android_free_gcs) (unwind_create_frame, do_unwind_create_frame) (android_default_font_parameter, android_create_frame_window) (Fx_create_frame, Fxw_color_defined_p, Fxw_color_values) (Fxw_display_color_p, Fx_display_grayscale_p) (Fx_display_pixel_width, Fx_display_pixel_height) (Fx_display_planes, Fx_display_color_cells, Fx_display_screens) (Fx_display_mm_width, Fx_display_mm_height) (Fx_display_backing_store, Fx_display_visual_class) (Fx_display_monitor_attributes_list, Fx_frame_geometry) (Fx_frame_list_z_order, Fx_frame_restack) (Fx_mouse_absolute_pixel_position) (Fx_set_mouse_absolute_pixel_position, Fandroid_get_connection) (Fx_display_list, Fx_show_tip, Fx_hide_tip) (android_set_background_color, android_set_border_color) (android_set_cursor_color, android_set_cursor_type) (android_set_foreground_color) (android_set_child_frame_border_width) (android_set_internal_border_width, android_set_menu_bar_lines) (android_set_mouse_color, android_set_title, android_set_alpha) (android_frame_parm_handlers, syms_of_androidfns): * src/androidfont.c (struct android_emacs_font_driver) (struct android_emacs_font_spec, struct android_emacs_font_metrics) (struct android_emacs_font_object, struct android_integer) (struct androidfont_info, struct androidfont_entity) (android_init_font_driver, android_init_font_spec) (android_init_font_metrics, android_init_integer) (android_init_font_object, androidfont_get_cache) (androidfont_from_lisp, androidfont_from_java, androidfont_list) (androidfont_match, androidfont_draw, androidfont_open_font) (androidfont_close_font, androidfont_has_char) (androidfont_encode_char, androidfont_text_extents) (androidfont_list_family, androidfont_driver) (syms_of_androidfont_for_pdumper, syms_of_androidfont) (init_androidfont, android_finalize_font_entity): * src/androidgui.h (_ANDROID_GUI_H_, struct android_rectangle) (struct android_point, enum android_gc_function) (enum android_gc_value_mask, enum android_fill_style) (enum android_window_value_mask) (struct android_set_window_attributes, struct android_gc_values) (struct android_gc, enum android_swap_action, enum android_shape) (enum android_coord_mode, struct android_swap_info) (NativeRectangle, struct android_any_event) (struct android_key_event, struct android_configure_event) (union android_event): * src/androidterm.c (android_window_to_frame, android_clear_frame) (android_ring_bell, android_toggle_invisible_pointer) (android_update_begin, android_update_end, show_back_buffer) (android_flush_dirty_back_buffer_on, handle_one_android_event) (android_read_socket, android_frame_up_to_date) (android_buffer_flipping_unblocked_hook) (android_query_frame_background_color, android_parse_color) (android_alloc_nearest_color, android_query_colors) (android_mouse_position, android_get_focus_frame) (android_focus_frame, android_frame_rehighlight) (android_frame_raise_lower, android_make_frame_visible) (android_make_frame_invisible) (android_make_frame_visible_invisible, android_fullscreen_hook) (android_iconify_frame, android_set_window_size_1) (android_set_window_size, android_set_offset, android_set_alpha) (android_new_font, android_bitmap_icon, android_free_pixmap_hook) (android_free_frame_resources, android_delete_frame) (android_delete_terminal, android_scroll_run) (android_after_update_window_line, android_flip_and_flush) (android_clear_rectangle, android_reset_clip_rectangles) (android_clip_to_row, android_draw_fringe_bitmap) (android_set_cursor_gc, android_set_mouse_face_gc) (android_set_mode_line_face_gc, android_set_glyph_string_gc) (android_set_glyph_string_clipping) (android_set_glyph_string_clipping_exactly) (android_compute_glyph_string_overhangs) (android_clear_glyph_string_rect) (android_draw_glyph_string_background, android_fill_triangle) (android_make_point, android_inside_rect_p, android_clear_point) (android_draw_relief_rect, android_draw_box_rect) (HIGHLIGHT_COLOR_DARK_BOOST_LIMIT, android_setup_relief_color) (android_setup_relief_colors, android_draw_glyph_string_box) (android_draw_glyph_string_bg_rect, android_draw_image_relief) (android_draw_image_foreground, android_draw_image_foreground_1) (android_draw_image_glyph_string) (android_draw_stretch_glyph_string, android_draw_underwave) (android_draw_glyph_string_foreground) (android_draw_composite_glyph_string_foreground) (android_draw_glyphless_glyph_string_foreground) (android_draw_glyph_string, android_define_frame_cursor) (android_clear_frame_area, android_clear_under_internal_border) (android_draw_hollow_cursor, android_draw_bar_cursor) (android_draw_window_cursor, android_draw_vertical_window_border) (android_draw_window_divider, android_redisplay_interface) (frame_set_mouse_pixel_position, get_keysym_name) (android_create_terminal, android_term_init, syms_of_androidterm) (mark_androidterm): * src/androidterm.h (_ANDROID_TERM_H_, struct android_display_info) (struct android_output, FRAME_ANDROID_OUTPUT, XSCROLL_BAR): New files. * src/dired.c (file_attributes): Do not use openat on Android. * src/dispextern.h (No_Cursor): Define appropriately on Android. (struct glyph_string, struct face): Make gc field of type struct android_gc on Android. * src/dispnew.c (clear_current_matrices, clear_desired_matrices) (adjust_frame_glyphs_for_window_redisplay, free_glyphs) (update_frame, scrolling, char_ins_del_cost, update_frame_line) (init_display_interactive): Disable text terminal support completely on Android. Fix non-toolkit menus for non-X systems. * src/editfns.c (Fuser_full_name): Call android_user_full_name. * src/emacs.c (android_emacs_init): Make main this on Android. Prohibit argv sorting from exceeding end of argv. * src/epaths.in: Add path definitions for Android. * src/fileio.c (file_access_p): Call android_file_access_p. (file_name_directory): Avoid using openat on Android. (Fcopy_file): Adjust to call sys_fstat instead. (file_directory_p): (Finsert_file_contents): (write_region): Likewise. * src/filelock.c: * src/fns.c (Flocale_info): Pacify warning on Android. * src/font.c (font_make_entity_android): New function. * src/font.h: * src/frame.c (Fframep): (Fwindow_system): Handle new window system `android'. Update doc strings. (Fmake_terminal_frame): Disable on Android. (gui_display_get_resource): Disable get_string_resource_hook on Android. (syms_of_frame): New defsym `android'. * src/frame.h (GCALIGNED_STRUCT): Add new output data for Android. (ENUM_BF): Expand enumerator size. (FRAME_ANDROID_P, FRAME_WINDOW_P, MOUSE_HL_INFO): Add definitions for Android. * src/image.c (GET_PIXEL): (image_create_bitmap_from_file): (image_create_x_image_and_pixmap_1): (image_get_x_image): (slurp_file): (lookup_rgb_color): (image_to_emacs_colors): (image_from_emacs_colors): (image_pixmap_draw_cross): (image_disable_image): (MaskForeground): (gif_load): Add stubs for Android. * src/lisp.h: * src/lread.c (safe_to_load_version, maybe_swap_for_eln1, openp): * src/pdumper.c (pdumper_load): Call sys_fstat instead of fstat. * src/process.c (wait_reading_process_output): Use android_select instead of pselect. * src/scroll.c: Disable on Android. * src/sysdep.c (widen_foreground_group, reset_sys_modes) (init_signals, emacs_fstatat, sys_fstat): New function. (emacs_open, emacs_open_noquit, emacs_close): Implement differently on Android. (close_output_streams): Disable what is not required on Android. * src/term.c (OUTPUT1_IF, encode_terminal_code, string_cost) (string_cost_one_line, per_line_cost, calculate_costs) (struct fkey_table, tty_append_glyph, produce_glyphs) (tty_capable_p, Fsuspend_tty, Fresume_tty, device, init_tty) (maybe_fatal, syms_of_term): Disable text terminal support on Android. * src/termhooks.h (enum output_method): Add android output method. (GCALIGNED_STRUCT, TERMINAL_FONT_CACHE): Define for Android. * src/terminal.c (Fterminal_live_p): Implement for Android. * src/verbose.mk.in (AM_V_GLOBALS): Add JAVAC and DX. * src/xdisp.c (redisplay_internal): Disable text terminals on Android. (display_menu_bar): (display_tty_menu_item): (draw_row_with_mouse_face): (expose_frame): Make the non toolkit menu bar work on Android. * src/xfaces.c (GCGraphicsExposures): (x_create_gc): (x_free_gc): (Fx_load_color_file): Define for Android. * xcompile/Makefile.in (top_srcdir): (top_builddir): * xcompile/README: * xcompile/langinfo.h (nl_langinfo): New files. --- java/org/gnu/emacs/EmacsWindow.java | 336 ++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 java/org/gnu/emacs/EmacsWindow.java (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java new file mode 100644 index 00000000000..28db04a261d --- /dev/null +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -0,0 +1,336 @@ +/* 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 . */ + +package org.gnu.emacs; + +import java.lang.IllegalStateException; +import java.util.ArrayList; +import java.util.List; + +import android.graphics.Rect; +import android.graphics.Canvas; +import android.graphics.Bitmap; +import android.graphics.Point; + +import android.view.View; +import android.view.ViewGroup; +import android.view.KeyEvent; + +/* This defines a window, which is a handle. Windows represent a + rectangular subset of the screen with their own contents. + + Windows either have a parent window, in which case their views are + attached to the parent's view, or are "floating", in which case + their views are attached to the parent activity (if any), else + nothing. + + Views are also drawables, meaning they can accept drawing + requests. */ + +public class EmacsWindow extends EmacsHandleObject + implements EmacsDrawable +{ + /* The view associated with the window. */ + public EmacsView view; + + /* The geometry of the window. */ + private Rect rect; + + /* The parent window, or null if it is the root window. */ + private EmacsWindow parent; + + /* List of all children in stacking order. This must be kept + consistent! */ + private ArrayList children; + + /* The EmacsActivity currently attached, if it exists. */ + private EmacsActivity attached; + + /* The window background scratch GC. foreground is always the + window background. */ + private EmacsGC scratchGC; + + public + EmacsWindow (short handle, final EmacsWindow parent, int x, int y, + int width, int height) + { + super (handle); + + rect = new Rect (x, y, x + width, y + height); + + /* Create the view from the context's UI thread. */ + view = EmacsService.SERVICE.getEmacsView (this); + this.parent = parent; + children = new ArrayList (); + + /* 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 + { + /* 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); + } + }); + } + } + + scratchGC = new EmacsGC ((short) 0); + } + + public void + changeWindowBackground (int pixel) + { + /* scratchGC is used as the argument to a FillRectangles req. */ + scratchGC.foreground = pixel; + scratchGC.markDirty (); + } + + public Rect + getGeometry () + { + synchronized (this) + { + /* Huh, this is it. */ + return rect; + } + } + + @Override + public void + destroyHandle () throws IllegalStateException + { + synchronized (this) + { + if (!children.isEmpty ()) + throw new IllegalStateException ("Trying to destroy window with " + + "children!"); + } + + /* Notice that the child has been destroyed. */ + EmacsService.SERVICE.noticeChildDestroyed (this); + + /* Remove the view from its parent and make it invisible. */ + view.post (new Runnable () { + public void + run () + { + view.setVisibility (View.GONE); + + if (view.getParent () != null) + ((ViewGroup) view.getParent ()).removeView (view); + + if (attached != null) + attached.makeAvailable (); + } + }); + + super.destroyHandle (); + } + + public void + setActivity (EmacsActivity activity) + { + synchronized (this) + { + activity = activity; + } + } + + public void + viewLayout (int left, int top, int right, int bottom) + { + synchronized (this) + { + rect.left = left; + rect.top = top; + rect.right = right; + rect.bottom = bottom; + + EmacsNative.sendConfigureNotify (this.handle, + System.currentTimeMillis (), + left, top, rect.width (), + rect.height ()); + } + } + + public void + requestViewLayout () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.requestLayout (); + } + }); + } + + public void + resizeWindow (int width, int height) + { + synchronized (this) + { + rect.right = rect.left + width; + rect.bottom = rect.top + height; + } + } + + public void + moveWindow (int x, int y) + { + int width, height; + + synchronized (this) + { + width = rect.width (); + height = rect.height (); + + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; + + requestViewLayout (); + } + } + + public void + mapWindow () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.setVisibility (View.VISIBLE); + } + }); + } + + public void + unmapWindow () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.setVisibility (View.GONE); + } + }); + } + + @Override + public Canvas + lockCanvas () + { + if (view.canvas != null) + return view.canvas; + + return null; + } + + @Override + public void + unlockCanvas () + { + + } + + @Override + public void + damageRect (Rect damageRect) + { + view.damageRect (damageRect); + } + + public void + swapBuffers () + { + /* Before calling swapBuffers, make sure to flush the paint + queue. */ + EmacsService.SERVICE.flushPaintQueue (); + view.post (new Runnable () { + @Override + public void + run () + { + view.swapBuffers (); + } + }); + } + + public void + clearWindow () + { + synchronized (this) + { + EmacsService.SERVICE.fillRectangle (this, scratchGC, + 0, 0, rect.width (), + rect.height ()); + } + } + + public void + clearArea (int x, int y, int width, int height) + { + EmacsService.SERVICE.fillRectangle (this, scratchGC, + x, y, width, height); + } + + @Override + public Bitmap + getBitmap () + { + return view.bitmap; + } + + public void + onKeyDown (int keyCode, KeyEvent event) + { + EmacsNative.sendKeyPress (this.handle, + event.getEventTime (), + event.getModifiers (), + keyCode); + } + + public void + onKeyUp (int keyCode, KeyEvent event) + { + EmacsNative.sendKeyRelease (this.handle, + event.getEventTime (), + event.getModifiers (), + keyCode); + } +}; -- cgit v1.3 From a32963e11f9f8e5d22b0d754d34a867f3b178ed2 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 2 Jan 2023 21:38:19 +0800 Subject: 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: : Set launchMode appropriately. : 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. --- Makefile.in | 17 +- configure.ac | 20 +- java/AndroidManifest.xml | 9 +- java/Makefile.in | 3 +- java/org/gnu/emacs/EmacsActivity.java | 153 +++-- java/org/gnu/emacs/EmacsCopyArea.java | 135 ++++- java/org/gnu/emacs/EmacsDrawRectangle.java | 8 +- java/org/gnu/emacs/EmacsFontDriver.java | 26 +- java/org/gnu/emacs/EmacsMultitaskActivity.java | 25 + java/org/gnu/emacs/EmacsNative.java | 18 +- java/org/gnu/emacs/EmacsPaintQueue.java | 24 +- java/org/gnu/emacs/EmacsPixmap.java | 29 + java/org/gnu/emacs/EmacsSdk23FontDriver.java | 43 ++ java/org/gnu/emacs/EmacsSdk7FontDriver.java | 188 +++++- java/org/gnu/emacs/EmacsService.java | 83 +-- java/org/gnu/emacs/EmacsSurfaceView.java | 37 +- java/org/gnu/emacs/EmacsView.java | 45 +- java/org/gnu/emacs/EmacsWindow.java | 143 +++-- .../gnu/emacs/EmacsWindowAttachmentManager.java | 166 ++++++ lisp/cus-edit.el | 6 +- lisp/faces.el | 2 +- src/android.c | 627 +++++++++++++++++++-- src/android.h | 15 + src/androidfns.c | 5 +- src/androidfont.c | 270 ++++++--- src/androidgui.h | 81 +++ src/androidterm.c | 544 ++++++++++-------- src/androidterm.h | 30 +- src/dispextern.h | 11 +- src/emacs.c | 5 +- src/fileio.c | 9 + src/font.h | 6 + src/fringe.c | 23 +- src/image.c | 249 +++++--- src/keyboard.c | 53 +- src/lread.c | 20 +- 36 files changed, 2435 insertions(+), 693 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsMultitaskActivity.java create mode 100644 java/org/gnu/emacs/EmacsSdk23FontDriver.java create mode 100644 java/org/gnu/emacs/EmacsWindowAttachmentManager.java (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/Makefile.in b/Makefile.in index d8c5ba555d3..6ca1b1dc382 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/configure.ac b/configure.ac index da8d753afca..70dbfa10b35 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml index 75aa5bdf409..06417017ae3 100644 --- a/java/AndroidManifest.xml +++ b/java/AndroidManifest.xml @@ -31,13 +31,15 @@ android:targetSdkVersion="28"/> - + @@ -45,6 +47,9 @@ + + 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 focusedActivities; + + /* The currently focused window. */ + public static EmacsWindow focusedWindow; + static { - /* Set up the list of available activities. */ - availableActivities = new ArrayList (); + focusedActivities = new ArrayList (); }; + 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; - - for (EmacsWindow iterWindow - : EmacsService.availableChildren) - { - synchronized (iterWindow) - { - if (!iterWindow.isDestroyed ()) - attachChild (iterWindow); - - return; - } - } + finish (); + } - 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. */ - - if (window != null) - { - layout.removeView (window.view); + /* 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 (); + } - /* 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; - } + @Override + public void + onWindowFocusChanged (boolean isFocused) + { + if (isFocused && !focusedActivities.contains (this)) + focusedActivities.add (this); + else + focusedActivities.remove (this); - /* Finally, remove this activity from the list of available - activities. */ - availableActivities.remove (this); - super.onStop (); + invalidateFocus (); } }; diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index f34d1ecde01..0dd5b2c1fb6 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -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); - - if (maskBitmap == null) + /* 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 (!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); } } } diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index 462bf7c85b5..e3f28227146 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -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); diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java index f419e71059d..9f40aa04c44 100644 --- a/java/org/gnu/emacs/EmacsFontDriver.java +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -21,6 +21,8 @@ package org.gnu.emacs; import java.util.List; +import android.os.Build; + public abstract class EmacsFontDriver { /* Font weights. */ @@ -75,6 +77,7 @@ public abstract class EmacsFontDriver public Integer size; public Integer spacing; public Integer avgwidth; + public Integer dpi; @Override public String @@ -88,7 +91,8 @@ public abstract class EmacsFontDriver + " weight: " + weight + " slant: " + slant + " spacing: " + spacing - + " avgwidth: " + avgwidth); + + " avgwidth: " + avgwidth + + " dpi: " + dpi); } }; @@ -99,6 +103,17 @@ public abstract class EmacsFontDriver 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 EmacsFontDriver 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 (); } }; diff --git a/java/org/gnu/emacs/EmacsMultitaskActivity.java b/java/org/gnu/emacs/EmacsMultitaskActivity.java new file mode 100644 index 00000000000..dbdc99a3559 --- /dev/null +++ b/java/org/gnu/emacs/EmacsMultitaskActivity.java @@ -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 . */ + +package org.gnu.emacs; + +public class EmacsMultitaskActivity extends EmacsActivity +{ + +} diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 6550e6fa2a1..c80339031a8 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -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 class EmacsNative /* 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 { diff --git a/java/org/gnu/emacs/EmacsPaintQueue.java b/java/org/gnu/emacs/EmacsPaintQueue.java index 5af5868d3b9..f4840dbf5ae 100644 --- a/java/org/gnu/emacs/EmacsPaintQueue.java +++ b/java/org/gnu/emacs/EmacsPaintQueue.java @@ -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 (); } } } diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index ccd5f1e0043..897902c5b57 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -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 () diff --git a/java/org/gnu/emacs/EmacsSdk23FontDriver.java b/java/org/gnu/emacs/EmacsSdk23FontDriver.java new file mode 100644 index 00000000000..34e2b1803a2 --- /dev/null +++ b/java/org/gnu/emacs/EmacsSdk23FontDriver.java @@ -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 . */ + +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; + } +}; diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index 5a8cdbfc75b..437f38e62db 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -28,16 +28,19 @@ import java.util.List; 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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver 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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } }; - private class Sdk7FontEntity extends FontEntity + protected class Sdk7FontEntity extends FontEntity { /* The typeface. */ public Sdk7Typeface typeface; @@ -177,19 +183,17 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver 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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver 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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } }; + 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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver 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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver 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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver && !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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver 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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver @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 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver { 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; + } }; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 311226e6f7e..41a45b0bd85 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -28,6 +28,8 @@ import android.graphics.Canvas; 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.Build; import android.os.Looper; import android.os.IBinder; import android.os.Handler; + import android.util.Log; +import android.util.DisplayMetrics; class Holder { @@ -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 availableChildren; - - static - { - availableChildren = new ArrayList (); - }; + /* 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 @@ public class EmacsService extends Service 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 @@ public class EmacsService extends Service + " and libDir = " + libDir); EmacsNative.setEmacsParams (manager, filesDir, libDir, + (float) pixelDensityX, + (float) pixelDensityY, this); /* Start the thread that runs Emacs. */ @@ -147,7 +152,8 @@ public class EmacsService extends Service } EmacsView - getEmacsView (final EmacsWindow window) + getEmacsView (final EmacsWindow window, final int visibility, + final boolean isFocusedByDefault) { Runnable runnable; final Holder view; @@ -161,6 +167,8 @@ public class EmacsService extends Service synchronized (this) { view.thing = new EmacsView (window); + view.thing.setVisibility (visibility); + view.thing.setFocusedByDefault (isFocusedByDefault); notify (); } } @@ -183,48 +191,6 @@ public class EmacsService extends Service 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 @@ public class EmacsService extends Service 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 @@ public class EmacsService extends Service { window.clearArea (x, y, width, height); } + + public void + appendPaintOperation (EmacsPaintReq op) + { + ensurePaintQueue (); + paintQueue.appendPaintOperation (op); + checkFlush (); + } }; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 194f6ad37a3..b8b828e4820 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -22,6 +22,8 @@ package org.gnu.emacs; 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); } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 237946d6366..7b48eaf0aa6 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -19,17 +19,20 @@ along with GNU Emacs. If not, see . */ 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 @@ public class EmacsView extends ViewGroup } public void - swapBuffers () + swapBuffers (boolean force) { Bitmap back; Canvas canvas; @@ -185,14 +189,25 @@ public class EmacsView extends ViewGroup 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 @@ public class EmacsView extends ViewGroup 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 @@ public class EmacsView extends ViewGroup window.onKeyUp (keyCode, event); return true; } + + @Override + public void + onFocusChanged (boolean gainFocus, int direction, + Rect previouslyFocusedRect) + { + window.onFocusChanged (gainFocus); + super.onFocusChanged (gainFocus, direction, + previouslyFocusedRect); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 28db04a261d..26e788a20a8 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -32,6 +32,8 @@ import android.view.View; 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 children; + public ArrayList 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; - children = new ArrayList (); - /* The window is unmapped by default. */ - view.setVisibility (View.GONE); + /* Create the list of children. */ + children = new ArrayList (); - /* 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); + } + }); + } + else + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () { - parent.children.add (this); - parent.view.post (new Runnable () { - @Override - public void - run () - { - parent.view.addView (view); - } - }); + 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); + + EmacsActivity.invalidateFocus (); - /* Notice that the child has been destroyed. */ - EmacsService.SERVICE.noticeChildDestroyed (this); + 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); } }; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java new file mode 100644 index 00000000000..34be2ab8789 --- /dev/null +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -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 . */ + +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 consumers; + private List windows; + + public + EmacsWindowAttachmentManager () + { + consumers = new LinkedList (); + windows = new LinkedList (); + } + + 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 (); + } + } +}; diff --git a/lisp/cus-edit.el b/lisp/cus-edit.el index 65eb066a554..6e71bfa6db6 100644 --- a/lisp/cus-edit.el +++ b/lisp/cus-edit.el @@ -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)) diff --git a/lisp/faces.el b/lisp/faces.el index 0dd89be4738..2779bfc295f 100644 --- a/lisp/faces.el +++ b/lisp/faces.el @@ -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")) diff --git a/src/android.c b/src/android.c index dd841cf383a..db12e244275 100644 --- a/src/android.c +++ b/src/android.c @@ -24,6 +24,7 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include #include #include @@ -46,6 +47,7 @@ bool android_init_gui; #include #include +#include #include #include @@ -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, "", "(S[IIII)V"); + FIND_METHOD (constructor_mutable, "", "(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 diff --git a/src/android.h b/src/android.h index 6bdcd38ed68..4d702fe2079 100644 --- a/src/android.h +++ b/src/android.h @@ -28,6 +28,8 @@ along with GNU Emacs. If not, see . */ #include #include #include + +#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 diff --git a/src/androidfns.c b/src/androidfns.c index e9e1ae3d52e..a78b35fc8ff 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -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; diff --git a/src/androidfont.c b/src/androidfont.c index e312e55c54a..b2c83201b06 100644 --- a/src/androidfont.c +++ b/src/androidfont.c @@ -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); - } - - 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 (); - - for (i = 0; i < nglyphs; ++i) - { - metrics_object - = (*android_java_env)->AllocObject (android_java_env, - font_metrics_class.class); + verify (sizeof (unsigned int) == sizeof (jint)); - 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); - } + /* Always true on every Android device. */ + (*android_java_env)->SetIntArrayRegion (android_java_env, + codepoint_array, + 0, nglyphs, + (jint *) code); - (*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 = diff --git a/src/androidgui.h b/src/androidgui.h index 43ccc86e5c7..b0ea820cfdf 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -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, diff --git a/src/androidterm.c b/src/androidterm.c index bb3c2a29737..0279da4f58d 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -39,6 +39,8 @@ struct android_display_info *x_display_list; #ifndef ANDROID_STUBIFY +#include + 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.x, r.y, r.width, r.height); - } - 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.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); - - /* 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); - } + 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) - { - /* 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; + int x = s->x; + int y = s->y; + int width = s->background_width; - 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); - } - } - else + if (s->first_glyph->left_box_line_p + && s->slice.x == 0) { - int x = s->x; - int y = s->y; - int width = s->background_width; + x += box_line_hwidth; + width -= box_line_hwidth; + } - 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; } diff --git a/src/androidterm.h b/src/androidterm.h index 3a0c9f60555..c834ffb70e5 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -28,8 +28,8 @@ along with GNU Emacs. If not, see . */ 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) diff --git a/src/dispextern.h b/src/dispextern.h index e521dffc37c..c9b3495934b 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -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 diff --git a/src/emacs.c b/src/emacs.c index 9ef58ca412e..d3c53dd8051 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -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)) diff --git a/src/fileio.c b/src/fileio.c index 1d22c51ebaa..0f205283d7e 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -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 ""; } diff --git a/src/font.h b/src/font.h index c1ab26b87cb..1db94a9f3f8 100644 --- a/src/font.h +++ b/src/font.h @@ -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. */ diff --git a/src/fringe.c b/src/fringe.c index 5d7c8dca998..f10bc424239 100644 --- a/src/fringe.c +++ b/src/fringe.c @@ -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; diff --git a/src/image.c b/src/image.c index 986dd7aada4..08d092e2c6e 100644 --- a/src/image.c +++ b/src/image.c @@ -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 diff --git a/src/keyboard.c b/src/keyboard.c index 7bf89ac7d4b..8eec833a9ca 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -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[] = diff --git a/src/lread.c b/src/lread.c index eb029f01623..15273ad34fa 100644 --- a/src/lread.c +++ b/src/lread.c @@ -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 -- cgit v1.3 From 695e26079eb60d10ffe25bb8ae91ebc6131fb27d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 8 Jan 2023 15:39:02 +0800 Subject: Update Java part of Android port * java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea, perform) (paintTo): * java/org/gnu/emacs/EmacsDrawLine.java (EmacsDrawLine): * java/org/gnu/emacs/EmacsDrawPoint.java (EmacsDrawPoint): * java/org/gnu/emacs/EmacsDrawRectangle.java (EmacsDrawRectangle) (paintTo): * java/org/gnu/emacs/EmacsDrawable.java (EmacsDrawable): * java/org/gnu/emacs/EmacsFillPolygon.java (EmacsFillPolygon): * java/org/gnu/emacs/EmacsFillRectangle.java (EmacsFillRectangle): * java/org/gnu/emacs/EmacsFontDriver.java (EmacsFontDriver): * java/org/gnu/emacs/EmacsGC.java (EmacsGC): * java/org/gnu/emacs/EmacsNative.java (EmacsNative): * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): * java/org/gnu/emacs/EmacsSdk23FontDriver.java (EmacsSdk23FontDriver): * java/org/gnu/emacs/EmacsSdk7FontDriver.java (EmacsSdk7FontDriver, textExtents1, textExtents, draw): * java/org/gnu/emacs/EmacsService.java (EmacsService, copyArea): * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): * java/org/gnu/emacs/EmacsView.java (EmacsView, onLayout) (onFocusChanged): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, run) (resizeWindow, lockCanvas, getBitmap, onKeyDown, onKeyUp) (onActivityDetached): Move rendering to main thread. Make drawing operations completely static. --- java/org/gnu/emacs/EmacsCopyArea.java | 131 +++++++++------------- java/org/gnu/emacs/EmacsDrawLine.java | 120 +++++--------------- java/org/gnu/emacs/EmacsDrawPoint.java | 11 +- java/org/gnu/emacs/EmacsDrawRectangle.java | 160 +++++++++++++-------------- java/org/gnu/emacs/EmacsDrawable.java | 1 - java/org/gnu/emacs/EmacsFillPolygon.java | 128 ++++++--------------- java/org/gnu/emacs/EmacsFillRectangle.java | 152 ++++++++++++------------- java/org/gnu/emacs/EmacsFontDriver.java | 2 +- java/org/gnu/emacs/EmacsGC.java | 77 ++++++------- java/org/gnu/emacs/EmacsNative.java | 22 ++++ java/org/gnu/emacs/EmacsPixmap.java | 9 +- java/org/gnu/emacs/EmacsSdk23FontDriver.java | 71 ++++++++++++ java/org/gnu/emacs/EmacsSdk7FontDriver.java | 139 +++++++---------------- java/org/gnu/emacs/EmacsService.java | 122 ++------------------ java/org/gnu/emacs/EmacsSurfaceView.java | 22 ++-- java/org/gnu/emacs/EmacsView.java | 133 ++++++++++++++++------ java/org/gnu/emacs/EmacsWindow.java | 141 +++++++++++++++++++---- 17 files changed, 685 insertions(+), 756 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 0dd5b2c1fb6..00e817bb97d 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -27,54 +27,16 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Xfermode; -public class EmacsCopyArea implements EmacsPaintReq +public class EmacsCopyArea { - private int src_x, src_y, dest_x, dest_y, width, height; - private EmacsDrawable destination, source; - private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu, overAlu; + private static Xfermode overAlu; static { overAlu = new PorterDuffXfermode (Mode.SRC_OVER); - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); }; - public - 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; - this.src_y = src_y; - this.width = width; - this.height = height; - this.dest_x = dest_x; - this.dest_y = dest_y; - this.immutableGC = immutableGC; - } - - @Override - public Rect - getRect () - { - return new Rect (dest_x, dest_y, dest_x + width, - dest_y + height); - } - - @Override - public EmacsDrawable - getDrawable () - { - return destination; - } - - private void + private static void insetRectBy (Rect rect, int left, int top, int right, int bottom) { @@ -84,30 +46,38 @@ public class EmacsCopyArea implements EmacsPaintReq rect.bottom -= bottom; } - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + public static void + perform (EmacsDrawable source, EmacsGC gc, + EmacsDrawable destination, + int src_x, int src_y, int width, int height, + int dest_x, int dest_y) { - int alu; + int i; Bitmap bitmap; - Paint maskPaint; - Canvas maskCanvas; + Paint maskPaint, paint; + Canvas maskCanvas, canvas; Bitmap srcBitmap, maskBitmap, clipBitmap; Rect rect, maskRect, srcRect, dstRect, maskDestRect; boolean needFill; /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - alu = immutableGC.function; + paint = gc.gcPaint; + + canvas = destination.lockCanvas (); + + if (canvas == null) + return; + + canvas.save (); + + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } /* A copy must be created or drawBitmap could end up overwriting itself. */ @@ -137,14 +107,10 @@ public class EmacsCopyArea implements EmacsPaintReq if (src_y + height > srcBitmap.getHeight ()) height = srcBitmap.getHeight () - src_y; - rect = getRect (); - - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + rect = new Rect (dest_x, dest_y, dest_x + width, + dest_y + height); - if (immutableGC.clip_mask == null) + if (gc.clip_mask == null) { bitmap = Bitmap.createBitmap (srcBitmap, src_x, src_y, width, @@ -156,17 +122,17 @@ public class EmacsCopyArea implements EmacsPaintReq /* 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; + clipBitmap = gc.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 + maskRect = new Rect (gc.clip_x_origin, + gc.clip_y_origin, + (gc.clip_x_origin + clipBitmap.getWidth ()), - (immutableGC.clip_y_origin + (gc.clip_y_origin + clipBitmap.getHeight ())); - clipBitmap = immutableGC.clip_mask.bitmap; + clipBitmap = gc.clip_mask.bitmap; if (!maskRect.setIntersect (dstRect, maskRect)) /* There is no intersection between the clip mask and the @@ -191,20 +157,21 @@ public class EmacsCopyArea implements EmacsPaintReq /* Draw the mask onto the maskBitmap. */ maskCanvas = new Canvas (maskBitmap); - 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); + maskPaint = new Paint (); + maskRect.offset (-gc.clip_x_origin, + -gc.clip_y_origin); + maskCanvas.drawBitmap (gc.clip_mask.bitmap, + maskRect, + new Rect (0, 0, + maskRect.width (), + maskRect.height ()), + maskPaint); + maskRect.offset (gc.clip_x_origin, + gc.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); + maskPaint.setXfermode (EmacsGC.srcInAlu); /* Draw the source. */ maskDestRect = new Rect (0, 0, srcRect.width (), @@ -215,6 +182,10 @@ public class EmacsCopyArea implements EmacsPaintReq /* Finally, draw the mask bitmap to the destination. */ paint.setXfermode (overAlu); canvas.drawBitmap (maskBitmap, null, maskRect, paint); + gc.resetXfermode (); } + + canvas.restore (); + destination.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index 6389031bbfa..8941d4c217f 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -29,109 +29,49 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Xfermode; -public class EmacsDrawLine implements EmacsPaintReq +public class EmacsDrawLine { - private int x, y, x2, y2; - private EmacsDrawable drawable; - private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu; - - static + public static void + perform (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int x2, int y2) { - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); - }; + Rect rect; + Canvas canvas; + Paint paint; + int i; - public - EmacsDrawLine (EmacsDrawable drawable, int x, int y, - int x2, int y2, EmacsGC immutableGC) - { - this.drawable = drawable; - this.x = x; - this.y = y; - this.x2 = x2; - this.y2 = y2; - this.immutableGC = immutableGC; - } + /* TODO implement stippling. */ + if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; - @Override - public Rect - getRect () - { - return new Rect (Math.min (x, x2 + 1), + paint = gc.gcPaint; + rect = new Rect (Math.min (x, x2 + 1), Math.min (y, y2 + 1), Math.max (x2 + 1, x), Math.max (y2 + 1, y)); - } - - @Override - public EmacsDrawable - getDrawable () - { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) - { - int alu; - Paint maskPaint; - Canvas maskCanvas; - Bitmap maskBitmap; - Rect rect, srcRect; - int width, height; + canvas = drawable.lockCanvas (); - /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + if (canvas == null) return; - alu = immutableGC.function; - rect = getRect (); - width = rect.width (); - height = rect.height (); - - paint.setStyle (Paint.Style.STROKE); - - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + canvas.save (); - if (immutableGC.clip_mask == null) - { - paint.setColor (immutableGC.foreground | 0xff000000); - canvas.drawLine ((float) x, (float) y, - (float) x2, (float) y2, - paint); - } - else + if (gc.real_clip_rects != null) { - maskPaint = new Paint (); - maskBitmap - = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) - return; - - maskPaint.setXfermode (srcInAlu); - maskPaint.setColor (immutableGC.foreground | 0xff000000); - maskCanvas = new Canvas (maskBitmap); - srcRect = new Rect (0, 0, maskBitmap.getWidth (), - maskBitmap.getHeight ()); - maskCanvas.drawLine (0.0f, 0.0f, (float) Math.abs (x - x2), - (float) Math.abs (y - y2), maskPaint); - canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); } - paint.setXfermode (null); + paint.setStyle (Paint.Style.STROKE); + + if (gc.clip_mask == null) + canvas.drawLine ((float) x, (float) y, + (float) x2, (float) y2, + paint); + + /* DrawLine with clip mask not implemented; it is not used by + Emacs. */ + canvas.restore (); + drawable.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsDrawPoint.java b/java/org/gnu/emacs/EmacsDrawPoint.java index 772757ff424..3bc7be17961 100644 --- a/java/org/gnu/emacs/EmacsDrawPoint.java +++ b/java/org/gnu/emacs/EmacsDrawPoint.java @@ -19,12 +19,13 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -public class EmacsDrawPoint extends EmacsDrawRectangle +public class EmacsDrawPoint { - public - EmacsDrawPoint (EmacsDrawable drawable, int x, int y, - EmacsGC immutableGC) + public static void + perform (EmacsDrawable drawable, + EmacsGC immutableGC, int x, int y) { - super (drawable, x, y, 1, 1, immutableGC); + EmacsDrawRectangle.perform (drawable, immutableGC, + x, y, 1, 1); } } diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index e3f28227146..b42e9556e8c 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -22,110 +22,104 @@ package org.gnu.emacs; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.Xfermode; -public class EmacsDrawRectangle implements EmacsPaintReq -{ - private int x, y, width, height; - private EmacsDrawable drawable; - private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu; - - static - { - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); - }; - - public - EmacsDrawRectangle (EmacsDrawable drawable, int x, int y, - int width, int height, - EmacsGC immutableGC) - { - this.drawable = drawable; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.immutableGC = immutableGC; - } +import android.util.Log; - @Override - public Rect - getRect () - { - /* 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 - public EmacsDrawable - getDrawable () - { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) +public class EmacsDrawRectangle +{ + public static void + perform (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int width, int height) { - int alu; - Paint maskPaint; + int i; + Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; - Rect rect, srcRect; + Rect rect; + Rect maskRect, dstRect; + Canvas canvas; + Bitmap clipBitmap; /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - alu = immutableGC.function; - rect = new Rect (x, y, x + width, y + height); + canvas = drawable.lockCanvas (); - paint.setStyle (Paint.Style.STROKE); - paint.setStrokeWidth (1); + if (canvas == null) + return; - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + canvas.save (); - if (immutableGC.clip_mask == null) + if (gc.real_clip_rects != null) { - paint.setColor (immutableGC.foreground | 0xff000000); - canvas.drawRect (rect, paint); + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); } + + paint = gc.gcPaint; + rect = new Rect (x, y, x + width, y + height); + + paint.setStyle (Paint.Style.STROKE); + + if (gc.clip_mask == null) + canvas.drawRect (rect, paint); else { - maskPaint = new Paint (); - maskBitmap - = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) + /* 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 = gc.clip_mask.bitmap; + dstRect = new Rect (x, y, x + width, y + height); + maskRect = new Rect (gc.clip_x_origin, + gc.clip_y_origin, + (gc.clip_x_origin + + clipBitmap.getWidth ()), + (gc.clip_y_origin + + clipBitmap.getHeight ())); + clipBitmap = gc.clip_mask.bitmap; + + if (!maskRect.setIntersect (dstRect, maskRect)) + /* There is no intersection between the clip mask and the + dest rect. */ return; - maskPaint.setXfermode (srcInAlu); - maskPaint.setColor (immutableGC.foreground | 0xff000000); + /* 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); - srcRect = new Rect (0, 0, maskBitmap.getWidth (), - maskBitmap.getHeight ()); - maskCanvas.drawRect (srcRect, maskPaint); - canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + maskRect.offset (-gc.clip_x_origin, + -gc.clip_y_origin); + maskCanvas.drawBitmap (gc.clip_mask.bitmap, + maskRect, new Rect (0, 0, + maskRect.width (), + maskRect.height ()), + paint); + maskRect.offset (gc.clip_x_origin, + gc.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 (EmacsGC.srcInAlu); + maskPaint.setStyle (Paint.Style.STROKE); + + /* Draw the source. */ + maskCanvas.drawRect (maskRect, maskPaint); + + /* Finally, draw the mask bitmap to the destination. */ + paint.setXfermode (null); + canvas.drawBitmap (maskBitmap, null, maskRect, paint); } - paint.setXfermode (null); + canvas.restore (); + drawable.damageRect (new Rect (x, y, x + width + 1, + y + height + 1)); } } diff --git a/java/org/gnu/emacs/EmacsDrawable.java b/java/org/gnu/emacs/EmacsDrawable.java index 19062137213..6a6199ff214 100644 --- a/java/org/gnu/emacs/EmacsDrawable.java +++ b/java/org/gnu/emacs/EmacsDrawable.java @@ -26,7 +26,6 @@ import android.graphics.Canvas; public interface EmacsDrawable { public Canvas lockCanvas (); - public void unlockCanvas (); public void damageRect (Rect damageRect); public Bitmap getBitmap (); public boolean isDestroyed (); diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java b/java/org/gnu/emacs/EmacsFillPolygon.java index 3198c7f07c4..42b73886dff 100644 --- a/java/org/gnu/emacs/EmacsFillPolygon.java +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -26,34 +26,35 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.Xfermode; -public class EmacsFillPolygon implements EmacsPaintReq +public class EmacsFillPolygon { - private EmacsDrawable drawable; - private EmacsGC immutableGC; - private Path path; + public static void + perform (EmacsDrawable drawable, EmacsGC gc, Point points[]) + { + Canvas canvas; + Path path; + Paint paint; + Rect rect; + RectF rectF; + int i; - private static Xfermode xorAlu, srcInAlu; + canvas = drawable.lockCanvas (); - static - { - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); - }; + if (canvas == null) + return; - public - EmacsFillPolygon (EmacsDrawable drawable, Point points[], - EmacsGC immutableGC) - { - int i; + paint = gc.gcPaint; + + canvas.save (); - this.drawable = drawable; - this.immutableGC = immutableGC; + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } /* Build the path from the given array of points. */ path = new Path (); @@ -67,84 +68,25 @@ public class EmacsFillPolygon implements EmacsPaintReq path.close (); } - } - - @Override - public Rect - getRect () - { - RectF rect; - rect = new RectF (0, 0, 0, 0); - path.computeBounds (rect, true); + /* Compute the damage rectangle. */ + rectF = new RectF (0, 0, 0, 0); + path.computeBounds (rectF, true); - return new Rect ((int) Math.floor (rect.left), - (int) Math.floor (rect.top), - (int) Math.ceil (rect.right), - (int) Math.ceil (rect.bottom)); - } + rect = new Rect ((int) Math.floor (rectF.left), + (int) Math.floor (rectF.top), + (int) Math.ceil (rectF.right), + (int) Math.ceil (rectF.bottom)); - @Override - public EmacsDrawable - getDrawable () - { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) - { - int alu; - Paint maskPaint; - Canvas maskCanvas; - Bitmap maskBitmap; - Rect rect; - - /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) - return; - - alu = immutableGC.function; - rect = getRect (); + paint.setStyle (Paint.Style.FILL); - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + if (gc.clip_mask == null) + canvas.drawPath (path, paint); - paint.setStyle (Paint.Style.FILL); + canvas.restore (); + drawable.damageRect (rect); - if (immutableGC.clip_mask == null) - { - paint.setColor (immutableGC.foreground | 0xff000000); - canvas.drawPath (path, paint); - } - else - { - maskPaint = new Paint (); - maskBitmap = immutableGC.clip_mask.bitmap; - maskBitmap = maskBitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) - return; - - maskPaint.setXfermode (srcInAlu); - maskPaint.setColor (immutableGC.foreground | 0xff000000); - maskCanvas = new Canvas (maskBitmap); - path.offset (-rect.left, -rect.top, null); - maskCanvas.drawPath (path, maskPaint); - canvas.drawBitmap (maskBitmap, new Rect (0, 0, rect.width (), - rect.height ()), - rect, paint); - } + /* FillPolygon with clip mask not implemented; it is not used by + Emacs. */ } } diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index 7246c13de7f..b733b417d6b 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -22,108 +22,102 @@ package org.gnu.emacs; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.Xfermode; import android.util.Log; -public class EmacsFillRectangle implements EmacsPaintReq +public class EmacsFillRectangle { - private int x, y, width, height; - private EmacsDrawable drawable; - private EmacsGC immutableGC; - private static Xfermode xorAlu, srcInAlu; - - static - { - xorAlu = new PorterDuffXfermode (Mode.XOR); - srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); - }; - - public - EmacsFillRectangle (EmacsDrawable drawable, int x, int y, - int width, int height, - EmacsGC immutableGC) - { - this.drawable = drawable; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.immutableGC = immutableGC; - } - - @Override - public Rect - getRect () - { - return new Rect (x, y, x + width, y + height); - } - - @Override - public EmacsDrawable - getDrawable () + public static void + perform (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int width, int height) { - return drawable; - } - - @Override - public EmacsGC - getGC () - { - return immutableGC; - } - - @Override - public void - paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) - { - int alu; - Paint maskPaint; + int i; + Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; - Rect rect, srcRect; + Rect rect; + Rect maskRect, dstRect; + Canvas canvas; + Bitmap clipBitmap; /* TODO implement stippling. */ - if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - alu = immutableGC.function; - rect = getRect (); + canvas = drawable.lockCanvas (); - paint.setStyle (Paint.Style.FILL); + if (canvas == null) + return; - if (alu == EmacsGC.GC_COPY) - paint.setXfermode (null); - else - paint.setXfermode (xorAlu); + canvas.save (); - if (immutableGC.clip_mask == null) + if (gc.real_clip_rects != null) { - paint.setColor (immutableGC.foreground | 0xff000000); - canvas.drawRect (rect, paint); + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); } + + paint = gc.gcPaint; + rect = new Rect (x, y, x + width, y + height); + + paint.setStyle (Paint.Style.FILL); + + if (gc.clip_mask == null) + canvas.drawRect (rect, paint); else { - maskPaint = new Paint (); - maskBitmap - = immutableGC.clip_mask.bitmap.copy (Bitmap.Config.ARGB_8888, - true); - - if (maskBitmap == null) + /* 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 = gc.clip_mask.bitmap; + dstRect = new Rect (x, y, x + width, y + height); + maskRect = new Rect (gc.clip_x_origin, + gc.clip_y_origin, + (gc.clip_x_origin + + clipBitmap.getWidth ()), + (gc.clip_y_origin + + clipBitmap.getHeight ())); + clipBitmap = gc.clip_mask.bitmap; + + if (!maskRect.setIntersect (dstRect, maskRect)) + /* There is no intersection between the clip mask and the + dest rect. */ return; - maskPaint.setXfermode (srcInAlu); - maskPaint.setColor (immutableGC.foreground | 0xff000000); + /* 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); - srcRect = new Rect (0, 0, maskBitmap.getWidth (), - maskBitmap.getHeight ()); - maskCanvas.drawRect (srcRect, maskPaint); - canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + maskRect.offset (-gc.clip_x_origin, + -gc.clip_y_origin); + maskCanvas.drawBitmap (gc.clip_mask.bitmap, + maskRect, new Rect (0, 0, + maskRect.width (), + maskRect.height ()), + paint); + maskRect.offset (gc.clip_x_origin, + gc.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 (EmacsGC.srcInAlu); + + /* Draw the source. */ + maskCanvas.drawRect (maskRect, maskPaint); + + /* Finally, draw the mask bitmap to the destination. */ + paint.setXfermode (null); + canvas.drawBitmap (maskBitmap, null, maskRect, paint); } - paint.setXfermode (null); + canvas.restore (); + drawable.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java index 9f40aa04c44..1d1e6f7b33f 100644 --- a/java/org/gnu/emacs/EmacsFontDriver.java +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -164,7 +164,7 @@ public abstract class EmacsFontDriver public static EmacsFontDriver createFontDriver () { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) return new EmacsSdk23FontDriver (); return new EmacsSdk7FontDriver (); diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index 0becb04519d..caa5c91edd4 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -20,6 +20,11 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; import android.graphics.Rect; +import android.graphics.Paint; + +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Xfermode; /* X like graphics context structures. Keep the enums in synch with androidgui.h! */ @@ -32,14 +37,21 @@ public class EmacsGC extends EmacsHandleObject public static final int GC_FILL_SOLID = 0; public static final int GC_FILL_OPAQUE_STIPPLED = 1; + public static final Xfermode xorAlu, srcInAlu; + public int function, fill_style; public int foreground, background; public int clip_x_origin, clip_y_origin; public int ts_origin_x, ts_origin_y; - public Rect clip_rects[]; + public Rect clip_rects[], real_clip_rects[]; public EmacsPixmap clip_mask, stipple; - private boolean dirty; - private EmacsGC immutableGC; + public Paint gcPaint; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + } /* The following fields are only set on immutable GCs. */ @@ -55,62 +67,41 @@ public class EmacsGC extends EmacsHandleObject fill_style = GC_FILL_SOLID; function = GC_COPY; foreground = 0; - background = 0xffffffff; + background = 0xffffff; + gcPaint = new Paint (); } - public - EmacsGC (EmacsGC source) - { - super ((short) 0); + /* Mark this GC as dirty. Apply parameters to the paint and + recompute real_clip_rects. */ + public void + markDirty () + { int i; - function = source.function; - fill_style = source.fill_style; - foreground = source.foreground; - background = source.background; - clip_x_origin = source.clip_x_origin; - clip_y_origin = source.clip_y_origin; - clip_rects = source.clip_rects; - clip_mask = source.clip_mask; - stipple = source.stipple; - ts_origin_x = source.ts_origin_x; - ts_origin_y = source.ts_origin_y; - - /* Offset all the clip rects by ts_origin_x and ts_origin_y. */ - if ((ts_origin_x != 0 || ts_origin_y != 0) && clip_rects != null) { - clip_rects = new Rect[clip_rects.length]; + real_clip_rects = new Rect[clip_rects.length]; for (i = 0; i < clip_rects.length; ++i) { - clip_rects[i] = new Rect (source.clip_rects[i]); - clip_rects[i].offset (ts_origin_x, - ts_origin_y); + real_clip_rects[i] = new Rect (clip_rects[i]); + real_clip_rects[i].offset (ts_origin_x, ts_origin_y); } } - } + else + real_clip_rects = clip_rects; - /* Mark this GC as dirty. This means immutableGC will return a new - copy of this GC the next time it is called. */ + gcPaint.setColor (foreground | 0xff000000); + gcPaint.setXfermode (function == GC_XOR + ? xorAlu : srcInAlu); + } public void - markDirty () + resetXfermode () { - dirty = true; + gcPaint.setXfermode (function == GC_XOR + ? xorAlu : srcInAlu); } - - public EmacsGC - immutableGC () - { - if (immutableGC == null || dirty) - { - immutableGC = new EmacsGC (this); - dirty = false; - } - - return immutableGC; - }; }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index c80339031a8..ae48ce30408 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -79,6 +79,28 @@ public class EmacsNative /* Send an ANDROID_WINDOW_ACTION event. */ public static native void sendWindowAction (short window, int action); + /* Send an ANDROID_ENTER_NOTIFY event. */ + public static native void sendEnterNotify (short window, int x, int y, + long time); + + /* Send an ANDROID_LEAVE_NOTIFY event. */ + public static native void sendLeaveNotify (short window, int x, int y, + long time); + + /* Send an ANDROID_MOTION_NOTIFY event. */ + public static native void sendMotionNotify (short window, int x, int y, + long time); + + /* Send an ANDROID_BUTTON_PRESS event. */ + public static native void sendButtonPress (short window, int x, int y, + long time, int state, + int button); + + /* Send an ANDROID_BUTTON_RELEASE event. */ + public static native void sendButtonRelease (short window, int x, int y, + long time, int state, + int button); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index 897902c5b57..15452f007c4 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -93,6 +93,8 @@ public class EmacsPixmap extends EmacsHandleObject break; } + bitmap.eraseColor (0xff000000); + this.width = width; this.height = height; this.depth = depth; @@ -108,13 +110,6 @@ public class EmacsPixmap extends EmacsHandleObject return canvas; } - @Override - public void - unlockCanvas () - { - - } - @Override public void damageRect (Rect damageRect) diff --git a/java/org/gnu/emacs/EmacsSdk23FontDriver.java b/java/org/gnu/emacs/EmacsSdk23FontDriver.java index 34e2b1803a2..11e128d5769 100644 --- a/java/org/gnu/emacs/EmacsSdk23FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk23FontDriver.java @@ -20,9 +20,80 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; import android.graphics.Paint; +import android.graphics.Rect; public class EmacsSdk23FontDriver extends EmacsSdk7FontDriver { + private void + textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics, + Paint paint, Rect bounds) + { + char[] text; + + text = new char[2]; + text[0] = (char) code; + text[1] = 'c'; + + paint.getTextBounds (text, 0, 1, bounds); + + metrics.lbearing = (short) bounds.left; + metrics.rbearing = (short) bounds.right; + metrics.ascent = (short) -bounds.top; + metrics.descent = (short) bounds.bottom; + metrics.width + = (short) paint.getRunAdvance (text, 0, 1, 0, 1, false, 1); + } + + @Override + public void + 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 (); + + 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 + 1]; + + for (i = 0; i < code.length; ++i) + text[i] = (char) code[i]; + + text[code.length] = 'c'; + + paintCache.getTextBounds (text, 0, code.length, + boundsCache); + width = paintCache.getRunAdvance (text, 0, code.length, 0, + code.length, + false, 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) width; + } + } + @Override public int hasChar (FontSpec font, char charCode) diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index 437f38e62db..8a9426050ae 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -243,93 +243,6 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } }; - 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; @@ -498,14 +411,11 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver Paint paint, Rect bounds) { char[] text; - float[] width; text = new char[1]; text[0] = (char) code; paint.getTextBounds (text, 0, 1, bounds); - width = new float[1]; - paint.getTextWidths (text, 0, 1, width); /* bounds is the bounding box of the glyph corresponding to CODE. Translate these into XCharStruct values. @@ -526,7 +436,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver metrics.rbearing = (short) bounds.right; metrics.ascent = (short) -bounds.top; metrics.descent = (short) bounds.bottom; - metrics.width = (short) Math.round (width[0]); + metrics.width = (short) paint.measureText ("" + text[0]); } @Override @@ -563,7 +473,8 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver for (i = 0; i < code.length; ++i) text[i] = (char) code[i]; - paintCache.getTextBounds (text, 0, 1, boundsCache); + paintCache.getTextBounds (text, 0, code.length, + boundsCache); width = paintCache.measureText (text, 0, code.length); fontMetrics.lbearing = (short) boundsCache.left; @@ -587,11 +498,12 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver int[] chars, int x, int y, int backgroundWidth, boolean withBackground) { - Rect backgroundRect; + Rect backgroundRect, bounds; Sdk7FontObject sdk7FontObject; - Sdk7DrawString op; char[] charsArray; int i; + Canvas canvas; + Paint paint; sdk7FontObject = (Sdk7FontObject) fontObject; charsArray = new char[chars.length]; @@ -605,12 +517,41 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver backgroundRect.right = x + backgroundWidth; backgroundRect.bottom = y + sdk7FontObject.descent; - op = new Sdk7DrawString (sdk7FontObject, charsArray, - gc.immutableGC (), drawable, - withBackground, - backgroundRect, x, y); + canvas = drawable.lockCanvas (); + + if (canvas == null) + return 0; + + canvas.save (); + paint = gc.gcPaint; + + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } + + paint.setStyle (Paint.Style.FILL); + + if (withBackground) + { + paint.setColor (gc.background | 0xff000000); + canvas.drawRect (backgroundRect, paint); + paint.setColor (gc.foreground | 0xff000000); + } - EmacsService.SERVICE.appendPaintOperation (op); + paint.setTextSize (sdk7FontObject.pixelSize); + paint.setTypeface (sdk7FontObject.typeface.typeface); + paint.setAntiAlias (true); + canvas.drawText (charsArray, 0, chars.length, x, y, paint); + + canvas.restore (); + bounds = new Rect (); + paint.getTextBounds (charsArray, 0, chars.length, bounds); + bounds.offset (x, y); + bounds.union (backgroundRect); + drawable.damageRect (bounds); + paint.setAntiAlias (false); return 1; } }; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 41a45b0bd85..4444b7f1c56 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -19,7 +19,6 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -import java.lang.Runnable; import java.io.IOException; import java.util.List; import java.util.ArrayList; @@ -59,7 +58,6 @@ public class EmacsService extends Service private EmacsThread thread; private Handler handler; - private EmacsPaintQueue paintQueue; /* Display metrics used by font backends. */ public DisplayMetrics metrics; @@ -191,126 +189,42 @@ public class EmacsService extends Service return view.thing; } - /* 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 - gets too big. */ - - - - private void - ensurePaintQueue () - { - if (paintQueue == null) - paintQueue = new EmacsPaintQueue (); - } - - public void - flushPaintQueue () - { - final EmacsPaintQueue queue; - - if (paintQueue == null) - return; - - if (paintQueue.numRequests < 1) - /* No requests to flush. */ - return; - - queue = paintQueue; - - handler.post (new Runnable () { - @Override - public void - run () - { - queue.run (); - } - }); - - /* Clear the paint queue. */ - paintQueue = null; - } - - private void - checkFlush () - { - if (paintQueue != null - && paintQueue.numRequests > MAX_PENDING_REQUESTS) - flushPaintQueue (); - } - public void fillRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsFillRectangle (drawable, x, y, - width, height, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsFillRectangle.perform (drawable, gc, x, y, + width, height); } public void fillPolygon (EmacsDrawable drawable, EmacsGC gc, Point points[]) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsFillPolygon (drawable, points, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsFillPolygon.perform (drawable, gc, points); } public void drawRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsDrawRectangle (drawable, x, y, - width, height, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsDrawRectangle.perform (drawable, gc, x, y, + width, height); } public void drawLine (EmacsDrawable drawable, EmacsGC gc, int x, int y, int x2, int y2) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsDrawLine (drawable, x, y, - x2, y2, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsDrawLine.perform (drawable, gc, x, y, + x2, y2); } public void drawPoint (EmacsDrawable drawable, EmacsGC gc, int x, int y) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsDrawPoint (drawable, x, y, - gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsDrawPoint.perform (drawable, gc, x, y); } public void @@ -319,15 +233,9 @@ public class EmacsService extends Service int srcX, int srcY, int width, int height, int destX, int destY) { - EmacsPaintReq req; - - ensurePaintQueue (); - - req = new EmacsCopyArea (srcDrawable, dstDrawable, - srcX, srcY, width, height, destX, - destY, gc.immutableGC ()); - paintQueue.appendPaintOperation (req); - checkFlush (); + EmacsCopyArea.perform (srcDrawable, gc, dstDrawable, + srcX, srcY, width, height, destX, + destY); } public void @@ -342,12 +250,4 @@ public class EmacsService extends Service { window.clearArea (x, y, width, height); } - - public void - appendPaintOperation (EmacsPaintReq op) - { - ensurePaintQueue (); - paintQueue.appendPaintOperation (op); - checkFlush (); - } }; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index b8b828e4820..5efb8882263 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -29,6 +29,7 @@ import android.graphics.Rect; public class EmacsSurfaceView extends SurfaceView { + public Object surfaceChangeLock; private boolean created; public @@ -36,33 +37,36 @@ public class EmacsSurfaceView extends SurfaceView { super (view.getContext ()); + surfaceChangeLock = new Object (); + getHolder ().addCallback (new SurfaceHolder.Callback () { @Override public void 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); + view.swapBuffers (); } @Override public void surfaceCreated (SurfaceHolder holder) { - created = true; - - /* Force a buffer swap now to get the contents of the Emacs - view on screen. */ - view.swapBuffers (true); + synchronized (surfaceChangeLock) + { + created = true; + view.swapBuffers (); + } } @Override public void surfaceDestroyed (SurfaceHolder holder) { - created = false; + synchronized (surfaceChangeLock) + { + created = false; + } } }); } diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 7b48eaf0aa6..169a1e42ee3 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -23,6 +23,7 @@ import android.content.res.ColorStateList; import android.view.View; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.ViewGroup; import android.graphics.Bitmap; @@ -64,6 +65,14 @@ public class EmacsView extends ViewGroup /* The associated surface view. */ private EmacsSurfaceView surfaceView; + /* Whether or not a configure event must be sent for the next layout + event regardless of what changed. */ + public boolean mustReportLayout; + + /* If non-null, whether or not bitmaps must be recreated upon the + next call to getBitmap. */ + private Rect bitmapDirty; + public EmacsView (EmacsWindow window) { @@ -81,6 +90,43 @@ public class EmacsView extends ViewGroup addView (this.surfaceView); } + private void + handleDirtyBitmap () + { + /* Recreate the front and back buffer bitmaps. */ + bitmap + = Bitmap.createBitmap (bitmapDirty.width (), + bitmapDirty.height (), + Bitmap.Config.ARGB_8888); + bitmap.eraseColor (0xffffffff); + + /* And canvases. */ + canvas = new Canvas (bitmap); + + /* If Emacs is drawing to the bitmap right now from the + main thread, the image contents are lost until the next + ConfigureNotify and complete garbage. Sorry! */ + bitmapDirty = null; + } + + public synchronized Bitmap + getBitmap () + { + if (bitmapDirty != null) + handleDirtyBitmap (); + + return bitmap; + } + + public synchronized Canvas + getCanvas () + { + if (bitmapDirty != null) + handleDirtyBitmap (); + + return canvas; + } + @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) @@ -114,7 +160,7 @@ public class EmacsView extends ViewGroup } @Override - protected void + protected synchronized void onLayout (boolean changed, int left, int top, int right, int bottom) { @@ -122,19 +168,15 @@ public class EmacsView extends ViewGroup View child; Rect windowRect; - if (changed) + if (changed || mustReportLayout) { + mustReportLayout = false; window.viewLayout (left, top, right, bottom); - - /* Recreate the front and back buffer bitmaps. */ - bitmap - = Bitmap.createBitmap (right - left, bottom - top, - Bitmap.Config.ARGB_8888); - - /* And canvases. */ - canvas = new Canvas (bitmap); } + if (changed) + bitmapDirty = new Rect (left, top, right, bottom); + count = getChildCount (); for (i = 0; i < count; ++i) @@ -159,47 +201,60 @@ public class EmacsView extends ViewGroup } } - public void + public synchronized void damageRect (Rect damageRect) { damageRegion.union (damageRect); } - public void + /* This method is called from both the UI thread and the Emacs + thread. */ + + public synchronized void swapBuffers (boolean force) { - Bitmap back; Canvas canvas; Rect damageRect; + Bitmap bitmap; if (damageRegion.isEmpty ()) return; - if (!surfaceView.isCreated ()) - return; + bitmap = getBitmap (); - if (bitmap == null) - return; + /* Emacs must take the following lock to ensure the access to the + canvas occurs with the surface created. Otherwise, Android + will throttle calls to lockCanvas. */ + + synchronized (surfaceView.surfaceChangeLock) + { + damageRect = damageRegion.getBounds (); - /* Lock the canvas with the specified damage. */ - damageRect = damageRegion.getBounds (); - canvas = surfaceView.lockCanvas (damageRect); + if (!surfaceView.isCreated ()) + return; - /* Return if locking the canvas failed. */ - if (canvas == null) - return; + if (bitmap == null) + return; + + /* Lock the canvas with the specified damage. */ + canvas = surfaceView.lockCanvas (damageRect); - /* Copy from the back buffer to the canvas. If damageRect was - made empty, then draw the entire back buffer. */ + /* Return if locking the canvas failed. */ + if (canvas == null) + return; - if (damageRect.isEmpty ()) - canvas.drawBitmap (bitmap, 0f, 0f, paint); - else - 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. */ - /* Unlock the canvas and clear the damage. */ - surfaceView.unlockCanvasAndPost (canvas); - damageRegion.setEmpty (); + 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 @@ -241,4 +296,18 @@ public class EmacsView extends ViewGroup super.onFocusChanged (gainFocus, direction, previouslyFocusedRect); } + + @Override + public boolean + onGenericMotionEvent (MotionEvent motion) + { + return window.onSomeKindOfMotionEvent (motion); + } + + @Override + public boolean + onTouchEvent (MotionEvent motion) + { + return window.onSomeKindOfMotionEvent (motion); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 26e788a20a8..a9a24b61869 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -31,8 +31,13 @@ import android.graphics.Point; import android.view.View; import android.view.ViewGroup; import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.InputDevice; import android.content.Intent; +import android.util.Log; + +import android.os.Build; /* This defines a window, which is a handle. Windows represent a rectangular subset of the screen with their own contents. @@ -68,6 +73,10 @@ public class EmacsWindow extends EmacsHandleObject window background. */ private EmacsGC scratchGC; + /* The button state and keyboard modifier mask at the time of the + last button press or release event. */ + private int lastButtonState, lastModifiers; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height) @@ -212,6 +221,7 @@ public class EmacsWindow extends EmacsHandleObject public void run () { + view.mustReportLayout = true; view.requestLayout (); } }); @@ -224,6 +234,8 @@ public class EmacsWindow extends EmacsHandleObject { rect.right = rect.left + width; rect.bottom = rect.top + height; + + requestViewLayout (); } } @@ -279,17 +291,7 @@ public class EmacsWindow extends EmacsHandleObject public Canvas lockCanvas () { - if (view.canvas != null) - return view.canvas; - - return null; - } - - @Override - public void - unlockCanvas () - { - + return view.getCanvas (); } @Override @@ -302,17 +304,7 @@ public class EmacsWindow extends EmacsHandleObject public void swapBuffers () { - /* Before calling swapBuffers, make sure to flush the paint - queue. */ - EmacsService.SERVICE.flushPaintQueue (); - view.post (new Runnable () { - @Override - public void - run () - { - view.swapBuffers (); - } - }); + view.swapBuffers (); } public void @@ -337,7 +329,7 @@ public class EmacsWindow extends EmacsHandleObject public Bitmap getBitmap () { - return view.bitmap; + return view.getBitmap (); } public void @@ -357,6 +349,7 @@ public class EmacsWindow extends EmacsHandleObject recognized as an ASCII key press event. */ event.getUnicodeChar (state)); + lastModifiers = event.getModifiers (); } public void @@ -372,6 +365,7 @@ public class EmacsWindow extends EmacsHandleObject event.getModifiers (), keyCode, event.getUnicodeChar (state)); + lastModifiers = event.getModifiers (); } public void @@ -386,4 +380,105 @@ public class EmacsWindow extends EmacsHandleObject /* Destroy the associated frame when the activity is detached. */ EmacsNative.sendWindowAction (this.handle, 0); } + + /* Look through the button state to determine what button EVENT was + generated from. DOWN is true if EVENT is a button press event, + false otherwise. Value is the X number of the button. */ + + private int + whatButtonWasIt (MotionEvent event, boolean down) + { + int eventState, notIn; + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + /* Earlier versions of Android only support one mouse + button. */ + return 1; + + eventState = event.getButtonState (); + notIn = (down ? eventState & ~lastButtonState + : lastButtonState & ~eventState); + + if ((notIn & MotionEvent.BUTTON_PRIMARY) != 0) + return 1; + + if ((notIn & MotionEvent.BUTTON_SECONDARY) != 0) + return 3; + + if ((notIn & MotionEvent.BUTTON_TERTIARY) != 0) + return 2; + + /* Not a real value. */ + return 4; + } + + public boolean + onSomeKindOfMotionEvent (MotionEvent event) + { + if (!event.isFromSource (InputDevice.SOURCE_CLASS_POINTER)) + return false; + + switch (event.getAction ()) + { + case MotionEvent.ACTION_HOVER_ENTER: + EmacsNative.sendEnterNotify (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime ()); + return true; + + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_HOVER_MOVE: + EmacsNative.sendMotionNotify (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime ()); + return true; + + case MotionEvent.ACTION_HOVER_EXIT: + EmacsNative.sendLeaveNotify (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime ()); + return true; + + case MotionEvent.ACTION_BUTTON_PRESS: + /* Find the button which was pressed. */ + EmacsNative.sendButtonPress (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime (), + lastModifiers, + whatButtonWasIt (event, true)); + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + return true; + + lastButtonState = event.getButtonState (); + return true; + + case MotionEvent.ACTION_BUTTON_RELEASE: + /* Find the button which was released. */ + EmacsNative.sendButtonRelease (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime (), + lastModifiers, + whatButtonWasIt (event, false)); + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + return true; + + lastButtonState = event.getButtonState (); + return true; + + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_UP: + /* Emacs must return true even though touch events are not yet + handled, because the value of this function is used by the + system to decide whether or not Emacs gets ACTION_MOVE + events. */ + return true; + } + + return false; + } }; -- cgit v1.3 From f9732131cf3c67e24db74a3d49f256d3c189a7e3 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 13 Jan 2023 15:53:08 +0800 Subject: Update Android port * configure.ac (ANDROID_MIN_SDK): New variable. (DX): Remove and replace with D8. (XCONFIGURE): Check for the minimum version of Android the cross compiler compiles for. Generate java/AndroidManifest.xml from java/AndroidManifest.xml.in. Allow using Zlib on Android. * java/AndroidManifest.xml.in: New file. Use the minimum SDK detected by configure. * java/Makefile.in (top_srcdir, version): New variables. (DX, D8): Replace with D8. (ANDROID_MIN_SDK, APK_NAME): New variables. (.PHONY): (.PRECIOUS): (classes.dex): (emacs.apk): Generate $(APK_NAME) instead of `emacs.apk'. * java/debug.sh: New option --attach-existing. Attach to an existing Emacs instance when specified. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field `isPaused'. (invalidateFocus1): Fix infinite recursion. (detachWindow): Deiconify window. (attachWindow): Iconify the window if the activity is paused. (onCreate): Use the ``no title bar'' theme. (onPause, onResume): New functions. * java/org/gnu/emacs/EmacsNative.java (sendTouchUp, sendTouchDown) (sendTouchMove, sendWheel, sendIconified, sendDeiconified): New functions. * java/org/gnu/emacs/EmacsSdk7FontDriver.java (Sdk7Typeface): (list): Remove logging for code that is mostly going to be unused. * java/org/gnu/emacs/EmacsService.java (ringBell, queryTree) (getScreenWidth, getScreenHeight, detectMouse): New functions. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView) (surfaceChanged, surfaceCreated, surfaceDestroyed): Add extra debug logging. Avoid deadlock in surfaceCreated. * java/org/gnu/emacs/EmacsView.java (EmacsView): Try very hard to make the SurfaceView respect Z order. It didn't work. (handleDirtyBitmap): Copy over the contents from the old bitmap. (explicitlyDirtyBitmap): New function. (onLayout): Don't dirty bitmap if unnecessary. (damageRect, swapBuffers): Don't synchronize so hard. (onTouchEvent): Call window.onTouchEvent instead. (moveChildToBack, raise, lower): New functions. * java/org/gnu/emacs/EmacsWindow.java (Coordinate): New subclass. (pointerMap, isMapped, isIconified, dontFocusOnMap) (dontAcceptFocus): New fields. (EmacsWindow): Don't immediately register unmapped window. (viewLayout): Send configure event outside the lock. (requestViewLayout): Explicitly dirty the bitmap. (mapWindow): Register the window now. Respect dontFocusOnMap. (unmapWindow): Unregister the window now. (figureChange, onTouchEvent): New functions. (onSomeKindOfMotionEvent): Handle scroll wheel events. (reparentTo, makeInputFocus, raise, lower, getWindowGeometry) (noticeIconified, noticeDeiconified, setDontAcceptFocus) (setDontFocusOnMap, getDontFocusOnMap): New functions. * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (registerWindow, detachWindow): Synchronize. (noticeIconified, noticeDeiconified): New functions. (copyWindows): New function. * lisp/frame.el (frame-geometry, frame-edges) (mouse-absolute-pixel-position, set-mouse-absolute-pixel-position) (frame-list-z-order, frame-restack, display-mouse-p) (display-monitor-attributes-list): Implement on Android. * lisp/mwheel.el (mouse-wheel-down-event): (mouse-wheel-up-event): (mouse-wheel-left-event): (mouse-wheel-right-event): Define on Android. * src/android.c (struct android_emacs_service): New methods `ringBell', `queryTree', `getScreenWidth', `getScreenHeight', and `detectMouse'. (struct android_event_queue, android_init_events) (android_next_event, android_write_event): Remove write limit. (android_file_access_p): Handle directories correcty. (android_close): Fix coding style. (android_fclose): New function. (android_init_emacs_service): Initialize new methods. (android_reparent_window): Implement function. (android_bell, android_set_input_focus, android_raise_window) (android_lower_window, android_query_tree, android_get_geometry) (android_get_screen_width, android_get_screen_height) (android_get_mm_width, android_get_mm_height, android_detect_mouse) (android_set_dont_focus_on_map, android_set_dont_accept_focus): New functions. (struct android_dir): New structure. (android_opendir, android_readdir, android_closedir): New functions. (emacs_abort): Implement here on Android and poke debuggerd into generating a tombstone. * src/android.h: Update prototypes. * src/androidfns.c (android_set_parent_frame): New function. (android_default_font_parameter): Use sane font size by default. (Fx_display_pixel_width, Fx_display_pixel_height) (Fx_display_mm_width, Fx_display_mm_height) (Fx_display_monitor_attributes_list): Rename to start with `android-'. Implement. Fiddle with documentation to introduce Android specific nuances. (Fandroid_display_monitor_attributes_list): New function. (Fx_frame_geometry, frame_geometry): New function. (Fandroid_frame_geometry): Implement correctly. (Fx_frame_list_z_order): Rename to start with `android-'. (android_frame_list_z_order, Fandroid_frame_list_z_order): Implement. (Fx_frame_restack): Rename to start with `android-'. (Fandroid_frame_restack): ``Implement''. (Fx_mouse_absolute_pixel_position): Rename to start with `android-'. (Fandroid_mouse_absolute_pixel_position): ``Implement''. (Fx_set_mouse_absolute_pixel_position): Rename to start with `android-'. (Fandroid_set_mouse_absolute_pixel_position): ``Implement''. (Fandroid_detect_mouse): New function. (android_set_menu_bar_lines): Use FRAME_ANDROID_DRAWABLE when clearing area. (android_set_no_focus_on_map, android_set_no_accept_focus): New functions. (android_frame_parm_handlers): Register new frame parameter handlers. (syms_of_androidfns): Update appropriately. * src/androidfont.c (androidfont_draw): Use FRAME_ANDROID_DRAWABLE instead of FRAME_ANDROID_WINDOW. * src/androidgui.h (enum android_event_type): New events. (struct android_touch_event, struct android_wheel_event) (struct android_iconify_event): New structures. (union android_event): Add new events. * src/androidterm.c (android_clear_frame): Use FRAME_ANDROID_DRAWABLE instead of FRAME_ANDROID_WINDOW. (android_flash, android_ring_bell): Implement bell ringing. (android_toggle_invisible_pointer): Don't TODO function that can't be implemented. (show_back_buffer, android_flush_dirty_back_buffer_on): Check if a buffer flip is required before doing the flip. (android_lower_frame, android_raise_frame): Implement functions. (android_update_tools, android_find_tool): New functions. (handle_one_android_event): Handle new iconification, wheel and touch events. (android_read_socket): Implement pending-autoraise-frames. (android_frame_up_to_date): Implement bell ringing. (android_buffer_flipping_unblocked_hook): Check if a buffer flip is required before doing the flip. (android_focus_frame, android_frame_highlight) (android_frame_unhighlight): New function. (android_frame_rehighlight): Implement functions. (android_iconify_frame): Always display error. (android_set_alpha): Update commentary. (android_free_frame_resources): Free frame touch points. (android_scroll_run, android_flip_and_flush) (android_clear_rectangle, android_draw_fringe_bitmap) (android_draw_glyph_string_background, android_fill_triangle) (android_clear_point, android_draw_relief_rect) (android_draw_box_rect, android_draw_glyph_string_bg_rect) (android_draw_image_foreground, android_draw_stretch_glyph_string) (android_draw_underwave, android_draw_glyph_string_foreground) (android_draw_composite_glyph_string_foreground) (android_draw_glyphless_glyph_string_foreground) (android_draw_glyph_string, android_clear_frame_area) (android_clear_under_internal_border, android_draw_hollow_cursor) (android_draw_bar_cursor, android_draw_vertical_window_border) (android_draw_window_divider): Use FRAME_ANDROID_DRAWABLE instead of FRAME_ANDROID_WINDOW for drawing operations. * src/androidterm.h (struct android_touch_point): New structure. (struct android_output): New fields. (FRAME_ANDROID_NEED_BUFFER_FLIP): New macro. * src/dired.c (emacs_readdir, open_directory) (directory_files_internal_unwind, read_dirent) (directory_files_internal, file_name_completion): Add indirection over readdir and opendir. Use android variants on Android. * src/dispnew.c (Fopen_termscript): * src/fileio.c (fclose_unwind): Use emacs_fclose. (Faccess_file): Call android_file_access_p. (file_accessible_directory_p): Append right suffix to Android assets directory. (do_auto_save_unwind): Use emacs_fclose. * src/keyboard.c (lispy_function_keys): Use right function key for page up and page down. (Fopen_dribble_file): Use emacs_fclose. * src/lisp.h: New prototype emacs_fclose. * src/lread.c (close_infile_unwind): Use emacs_fclose. * src/sfnt.c (sfnt_curve_is_flat): Fix area-squared computation. (sfnt_prepare_raster): Compute raster width and height consistently with outline building. (sfnt_build_outline_edges): Use the same offsets used to set offy and offx. (main): Adjust debug code. * src/sfntfont-android.c (sfntfont_android_saturate32): Delete function. (sfntfont_android_blend, sfntfont_android_blendrgb): Remove unnecessary debug code. (sfntfont_android_composite_bitmap): Prevent out of bounds write. (sfntfont_android_put_glyphs): Use FRAME_ANDROID_DRAWABLE. (init_sfntfont_android): Initialize Monospace Serif font to something sensible. * src/sfntfont.c (sfntfont_text_extents): Clear glyph metrics before summing up pcm. (sfntfont_draw): Use s->font instead of s->face->font. * src/sysdep.c (emacs_fclose): Wrap around android_fclose on android. * src/term.c (Fsuspend_tty): (delete_tty): Use emacs_fclose. * src/verbose.mk.in (AM_V_DX): Replace with D8 version. --- configure.ac | 35 +- java/AndroidManifest.xml.in | 84 +++ java/Makefile.in | 30 +- java/debug.sh | 45 +- java/org/gnu/emacs/EmacsActivity.java | 37 +- java/org/gnu/emacs/EmacsNative.java | 25 +- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 7 - java/org/gnu/emacs/EmacsService.java | 116 ++++ java/org/gnu/emacs/EmacsSurfaceView.java | 22 +- java/org/gnu/emacs/EmacsView.java | 189 +++++-- java/org/gnu/emacs/EmacsWindow.java | 498 +++++++++++++++-- .../gnu/emacs/EmacsWindowAttachmentManager.java | 54 +- lisp/frame.el | 32 +- lisp/mwheel.el | 12 +- src/android.c | 551 ++++++++++++++++++- src/android.h | 23 + src/androidfns.c | 471 ++++++++++++++-- src/androidfont.c | 8 +- src/androidgui.h | 83 +++ src/androidterm.c | 589 ++++++++++++++++++--- src/androidterm.h | 28 + src/dired.c | 50 +- src/dispnew.c | 3 +- src/fileio.c | 42 +- src/keyboard.c | 6 +- src/lisp.h | 2 + src/lread.c | 2 +- src/sfnt.c | 35 +- src/sfntfont-android.c | 41 +- src/sfntfont.c | 5 +- src/sysdep.c | 17 +- src/term.c | 10 +- src/verbose.mk.in | 2 +- 33 files changed, 2809 insertions(+), 345 deletions(-) create mode 100644 java/AndroidManifest.xml.in (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/configure.ac b/configure.ac index 68de5f02b97..333a28cc142 100644 --- a/configure.ac +++ b/configure.ac @@ -816,8 +816,8 @@ a valid path to android.jar. See config.log for more details.]) Please verify that the path to the SDK build tools you specified is correct]) fi - AC_PATH_PROGS([DX], [dx d8], [], "${SDK_BUILD_TOOLS}:$PATH") - if test "DX" = ""; then + AC_PATH_PROGS([D8], [d8], [], "${SDK_BUILD_TOOLS}:$PATH") + if test "D8" = ""; then AC_MSG_ERROR([The Android dexer was not found. Please verify that the path to the SDK build tools you specified is correct]) fi @@ -867,6 +867,25 @@ in the ANDROID_CC variable when you ran configure.]) ANDROID_ABI=$android_abi + dnl Obtain the minimum SDK version of the resulting Emacs binary + dnl built with this NDK. + + ANDROID_MIN_SDK=8 + AC_MSG_CHECKING([for the lowest Android version Emacs can run on]) + [android_sdk=`echo "$cc_target" | grep -oE 'android([0-9][0-9]?)'`] + + if test -n "$android_sdk"; then + android_sdk=`echo "$android_sdk" | sed -n 's/android//p'` + AC_MSG_RESULT([$android_sdk]) + ANDROID_MIN_SDK=$android_sdk + else + AC_MSG_RESULT([unknown ($cc_target); assuming 8]) + AC_MSG_WARN([configure could not determine the versions of Android \ +a binary built with this compiler will run on. The generated application \ +package will likely install on older systems but crash on startup.]) + fi + AC_SUBST([ANDROID_MIN_SDK]) + # Save confdefs.h and config.log for now. mv -f confdefs.h _confdefs.h mv -f config.log _config.log @@ -899,7 +918,7 @@ fi AC_SUBST([ANDROID]) AC_SUBST([JAVAC]) AC_SUBST([AAPT]) -AC_SUBST([DX]) +AC_SUBST([D8]) AC_SUBST([ZIPALIGN]) AC_SUBST([ANDROID_JAR]) AC_SUBST([ANDROID_ABI]) @@ -914,7 +933,7 @@ fi AC_SUBST([XCONFIGURE]) if test "$ANDROID" = "yes"; then - # When --with-android is specified, all build options must be + # When --with-android is specified, almost all build options must be # disabled, both within the recursive invocation of configure and # outside. with_xpm=no @@ -938,11 +957,12 @@ if test "$ANDROID" = "yes"; then with_gpm=no with_dbus=no with_gsettings=no - with_selinx=no + with_selinux=no with_gnutls=no - with_zlib=no with_modules=no with_threads=no + + # zlib is available in android. fi dnl This used to use changequote, but, apart from 'changequote is evil' @@ -7211,6 +7231,9 @@ fi ARCH_INDEPENDENT_CONFIG_FILES([java/Makefile]) ARCH_INDEPENDENT_CONFIG_FILES([xcompile/Makefile]) +# Make java/AndroidManifest.xml +ARCH_INDEPENDENT_CONFIG_FILES([java/AndroidManifest.xml]) + AC_OUTPUT if test ! "$with_mailutils"; then diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in new file mode 100644 index 00000000000..b680137a9d0 --- /dev/null +++ b/java/AndroidManifest.xml.in @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/Makefile.in b/java/Makefile.in index 31bd22b5a2e..05e61dede89 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -18,13 +18,15 @@ # along with GNU Emacs. If not, see . top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +version = @version@ -include ${top_builddir}/src/verbose.mk SHELL = @SHELL@ JAVAC = @JAVAC@ AAPT = @AAPT@ -DX = @DX@ +D8 = @D8@ ZIPALIGN = @ZIPALIGN@ JARSIGNER = @JARSIGNER@ ANDROID_JAR = @ANDROID_JAR@ @@ -39,6 +41,12 @@ SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 JAVA_FILES = $(shell find . -type f -name *.java) CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) +# Compute the name for the Emacs application package. This should be: +# emacs---.apk + +ANDROID_MIN_SDK = @ANDROID_MIN_SDK@ +APK_NAME = emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk + # How this stuff works. # emacs.apk depends on emacs.apk-in, which is simply a ZIP archive @@ -55,7 +63,7 @@ CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) # assets/lisp/ .PHONY: emacs.apk-in all -all: emacs.apk +all: $(APK_NAME) # Binaries to cross-compile. CROSS_BINS = ../xcompile/src/android-emacs ../xcompile/lib-src/ctags \ @@ -118,6 +126,18 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml pushd install_temp; $(AAPT) add ../$@ `find assets -type f`; popd rm -rf install_temp +# Makefile itself. +.PRECIOUS: ../config.status Makefile +../config.status: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4 + $(MAKE) -C $(dir $@) $(notdir $@) +Makefile: ../config.status $(top_builddir)/java/Makefile.in + $(MAKE) -C .. java/$@ + +# AndroidManifest.xml: +AndroidManifest.xml: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4 \ + AndroidManifest.xml.in + pushd ..; ./config.status java/AndroidManifest.xml; popd + .SUFFIXES: .java .class .java.class &: $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $< @@ -126,7 +146,7 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml # nested classes. classes.dex: $(CLASS_FILES) - $(AM_V_DX) $(DX) --classpath $(ANDROID_JAR) \ + $(AM_V_D8) $(D8) --classpath $(ANDROID_JAR) \ $(subst $$,\$$,$(shell find . -type f -name *.class)) # When emacs.keystore expires, regenerate it with: @@ -136,7 +156,7 @@ classes.dex: $(CLASS_FILES) .PHONY: clean maintainer-clean -emacs.apk: classes.dex emacs.apk-in emacs.keystore +$(APK_NAME): classes.dex emacs.apk-in emacs.keystore cp -f emacs.apk-in $@.unaligned $(AAPT) add $@.unaligned classes.dex $(JARSIGNER) $(SIGN_EMACS) $@.unaligned "Emacs keystore" @@ -144,7 +164,7 @@ emacs.apk: classes.dex emacs.apk-in emacs.keystore rm -f $@.unaligned clean: - rm -f emacs.apk emacs.apk-in *.dex *.unaligned *.class + rm -f *.apk emacs.apk-in *.dex *.unaligned *.class rm -rf install-temp find . -name '*.class' -delete diff --git a/java/debug.sh b/java/debug.sh index dd710dc31af..3e3e3d9c281 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -30,6 +30,7 @@ activity=org.gnu.emacs.EmacsActivity gdb_port=5039 jdb_port=64013 jdb=no +attach_existing=no while [ $# -gt 0 ]; do case "$1" in @@ -48,6 +49,7 @@ while [ $# -gt 0 ]; do echo " --port PORT run the GDB server on a specific port" echo " --jdb-port PORT run the JDB server on a specific port" echo " --jdb run JDB instead of GDB" + echo " --attach-existing attach to an existing process" echo " --help print this message" echo "" echo "Available devices:" @@ -63,6 +65,9 @@ while [ $# -gt 0 ]; do "--port" ) gdb_port=$1 ;; + "--attach-existing" ) + attach_existing=yes + ;; "--" ) shift gdbargs=$@ @@ -120,30 +125,32 @@ package_pids=`awk -- '{ print $1 }' <<< $package_pids` -# Finally, kill each existing process. -for pid in $package_pids; do - echo "Killing existing process $pid..." - adb -s $device shell run-as $package kill -9 $pid &> /dev/null -done - -# Now run the main activity. This must be done as the adb user and -# not as the package user. -echo "Starting activity $activity and attaching debugger" - -# Exit if the activity could not be started. -adb -s $device shell am start -D "$package/$activity" -if [ ! $? ]; then - exit 1; -fi +if [ "$attach_existing" != "yes" ]; then + # Finally, kill each existing process. + for pid in $package_pids; do + echo "Killing existing process $pid..." + adb -s $device shell run-as $package kill -9 $pid &> /dev/null + done + + # Now run the main activity. This must be done as the adb user and + # not as the package user. + echo "Starting activity $activity and attaching debugger" + + # Exit if the activity could not be started. + adb -s $device shell am start -D "$package/$activity" + if [ ! $? ]; then + exit 1; + fi -# Now look for processes matching the package again. -package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` + # Now look for processes matching the package again. + package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` -# Next, remove lines matching "ps" itself. -package_pids=`awk -- '{ + # Next, remove lines matching "ps" itself. + package_pids=`awk -- '{ if (!match ($0, /(PID|ps)/)) print $1 }' <<< $package_pids` +fi pid=$package_pids num_pids=`wc -w <<< "$package_pids"` diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 67f411a38c3..2b661024842 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -48,6 +48,9 @@ public class EmacsActivity extends Activity /* The currently focused window. */ public static EmacsWindow focusedWindow; + /* Whether or not this activity is paused. */ + private boolean isPaused; + static { focusedActivities = new ArrayList (); @@ -60,7 +63,7 @@ public class EmacsActivity extends Activity focusedWindow = window; for (EmacsWindow child : window.children) - invalidateFocus1 (window); + invalidateFocus1 (child); } public static void @@ -103,6 +106,9 @@ public class EmacsActivity extends Activity /* Clear the window's pointer to this activity and remove the window's view. */ window.setConsumer (null); + + /* The window can't be iconified any longer. */ + window.noticeDeiconified (); layout.removeView (window.view); window = null; @@ -114,6 +120,8 @@ public class EmacsActivity extends Activity public void attachWindow (EmacsWindow child) { + Log.d (TAG, "attachWindow: " + child); + if (window != null) throw new IllegalStateException ("trying to attach window when one" + " already exists"); @@ -124,6 +132,10 @@ public class EmacsActivity extends Activity layout.addView (window.view); child.setConsumer (this); + /* If the activity is iconified, send that to the window. */ + if (isPaused) + window.noticeIconified (); + /* Invalidate the focus. */ invalidateFocus (); } @@ -148,6 +160,9 @@ public class EmacsActivity extends Activity { FrameLayout.LayoutParams params; + /* Set the theme to one without a title bar. */ + setTheme (android.R.style.Theme_NoTitleBar); + params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); @@ -192,4 +207,24 @@ public class EmacsActivity extends Activity invalidateFocus (); } + + @Override + public void + onPause () + { + isPaused = true; + + EmacsWindowAttachmentManager.MANAGER.noticeIconified (this); + super.onResume (); + } + + @Override + public void + onResume () + { + isPaused = false; + + EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); + super.onResume (); + } }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index ae48ce30408..a11e509cd7f 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -96,11 +96,34 @@ public class EmacsNative long time, int state, int button); - /* Send an ANDROID_BUTTON_RELEASE event. */ + /* Send an ANDROID_BUTTON_RELEASE event. */ public static native void sendButtonRelease (short window, int x, int y, long time, int state, int button); + /* Send an ANDROID_TOUCH_DOWN event. */ + public static native void sendTouchDown (short window, int x, int y, + long time, int pointerID); + + /* Send an ANDROID_TOUCH_UP event. */ + public static native void sendTouchUp (short window, int x, int y, + long time, int pointerID); + + /* Send an ANDROID_TOUCH_MOVE event. */ + public static native void sendTouchMove (short window, int x, int y, + long time, int pointerID); + + /* Send an ANDROID_WHEEL event. */ + public static native void sendWheel (short window, int x, int y, + long time, int state, + float xDelta, float yDelta); + + /* Send an ANDROID_ICONIFIED event. */ + public static native void sendIconified (short window); + + /* Send an ANDROID_DEICONIFIED event. */ + public static native void sendDeiconified (short window); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index 8a9426050ae..c0f24c7433a 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -149,8 +149,6 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } else familyName = fileName; - - Log.d (TAG, "Initialized new typeface " + familyName); } @Override @@ -321,17 +319,12 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver list = new LinkedList (); - Log.d (TAG, ("Looking for fonts matching font spec: " - + fontSpec.toString ())); - for (i = 0; i < typefaceList.length; ++i) { if (checkMatch (typefaceList[i], fontSpec)) list.add (new Sdk7FontEntity (typefaceList[i])); } - Log.d (TAG, "Found font entities: " + list.toString ()); - return (FontEntity[]) list.toArray (new FontEntity[0]); } diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 4444b7f1c56..01a1695f385 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -28,20 +28,27 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.view.View; +import android.view.InputDevice; import android.annotation.TargetApi; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; + import android.os.Build; import android.os.Looper; import android.os.IBinder; import android.os.Handler; +import android.os.Vibrator; +import android.os.VibratorManager; +import android.os.VibrationEffect; import android.util.Log; import android.util.DisplayMetrics; +import android.hardware.input.InputManager; + class Holder { T thing; @@ -250,4 +257,113 @@ public class EmacsService extends Service { window.clearArea (x, y, width, height); } + + @SuppressWarnings ("deprecation") + public void + ringBell () + { + Vibrator vibrator; + VibrationEffect effect; + VibratorManager vibratorManager; + Object tem; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + tem = getSystemService (Context.VIBRATOR_MANAGER_SERVICE); + vibratorManager = (VibratorManager) tem; + vibrator = vibratorManager.getDefaultVibrator (); + } + else + vibrator + = (Vibrator) getSystemService (Context.VIBRATOR_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + effect + = VibrationEffect.createOneShot (50, + VibrationEffect.DEFAULT_AMPLITUDE); + vibrator.vibrate (effect); + } + else + vibrator.vibrate (50); + } + + public short[] + queryTree (EmacsWindow window) + { + short[] array; + List windowList; + int i; + + if (window == null) + /* Just return all the windows without a parent. */ + windowList = EmacsWindowAttachmentManager.MANAGER.copyWindows (); + else + windowList = window.children; + + array = new short[windowList.size () + 1]; + i = 1; + + array[0] = window.parent != null ? 0 : window.parent.handle; + + for (EmacsWindow treeWindow : windowList) + array[i++] = treeWindow.handle; + + return array; + } + + public int + getScreenWidth (boolean mmWise) + { + DisplayMetrics metrics; + + metrics = getResources ().getDisplayMetrics (); + + if (!mmWise) + return metrics.widthPixels; + else + return (int) ((metrics.widthPixels / metrics.xdpi) * 2540.0); + } + + public int + getScreenHeight (boolean mmWise) + { + DisplayMetrics metrics; + + metrics = getResources ().getDisplayMetrics (); + + if (!mmWise) + return metrics.heightPixels; + else + return (int) ((metrics.heightPixels / metrics.ydpi) * 2540.0); + } + + public boolean + detectMouse () + { + InputManager manager; + InputDevice device; + int[] ids; + int i; + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.JELLY_BEAN) + return false; + + manager = (InputManager) getSystemService (Context.INPUT_SERVICE); + ids = manager.getInputDeviceIds (); + + for (i = 0; i < ids.length; ++i) + { + device = manager.getInputDevice (ids[i]); + + if (device == null) + continue; + + if (device.supportsSource (InputDevice.SOURCE_MOUSE)) + return true; + } + + return false; + } }; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 5efb8882263..f713818d4bc 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -27,8 +27,12 @@ import android.os.Build; import android.graphics.Canvas; import android.graphics.Rect; +import android.util.Log; + public class EmacsSurfaceView extends SurfaceView { + private static final String TAG = "EmacsSurfaceView"; + public Object surfaceChangeLock; private boolean created; @@ -45,6 +49,7 @@ public class EmacsSurfaceView extends SurfaceView surfaceChanged (SurfaceHolder holder, int format, int width, int height) { + Log.d (TAG, "surfaceChanged: " + view); view.swapBuffers (); } @@ -54,9 +59,13 @@ public class EmacsSurfaceView extends SurfaceView { synchronized (surfaceChangeLock) { + Log.d (TAG, "surfaceCreated: " + view); created = true; - view.swapBuffers (); } + + /* Drop the lock when doing this, or a deadlock can + result. */ + view.swapBuffers (); } @Override @@ -65,6 +74,7 @@ public class EmacsSurfaceView extends SurfaceView { synchronized (surfaceChangeLock) { + Log.d (TAG, "surfaceDestroyed: " + view); created = false; } } @@ -93,6 +103,16 @@ public class EmacsSurfaceView extends SurfaceView return holder.lockCanvas (damage); } + @Override + protected void + onLayout (boolean changed, int left, int top, int right, + int bottom) + { + Log.d (TAG, ("onLayout: " + left + " " + top + " " + right + + " " + bottom + " -- " + changed + " visibility " + + getVisibility ())); + } + /* This method is only used during debugging when it seems damage isn't working correctly. */ diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 169a1e42ee3..41acabab97b 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -87,12 +87,27 @@ public class EmacsView extends ViewGroup /* Create the surface view. */ this.surfaceView = new EmacsSurfaceView (this); + this.surfaceView.setZOrderMediaOverlay (true); addView (this.surfaceView); + + /* Not sure exactly what this does but it makes things magically + work. Why is something as simple as XRaiseWindow so involved + on Android? */ + setChildrenDrawingOrderEnabled (true); + + /* Get rid of the foreground and background tint. */ + setBackgroundTintList (null); + setForegroundTintList (null); } private void handleDirtyBitmap () { + Bitmap oldBitmap; + + /* Save the old bitmap. */ + oldBitmap = bitmap; + /* Recreate the front and back buffer bitmaps. */ bitmap = Bitmap.createBitmap (bitmapDirty.width (), @@ -103,12 +118,23 @@ public class EmacsView extends ViewGroup /* And canvases. */ canvas = new Canvas (bitmap); - /* If Emacs is drawing to the bitmap right now from the - main thread, the image contents are lost until the next - ConfigureNotify and complete garbage. Sorry! */ + /* Copy over the contents of the old bitmap. */ + if (oldBitmap != null) + canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ()); + bitmapDirty = null; } + public synchronized void + explicitlyDirtyBitmap (Rect rect) + { + if (bitmapDirty == null + && (bitmap == null + || rect.width () != bitmap.getWidth () + || rect.height () != bitmap.getHeight ())) + bitmapDirty = rect; + } + public synchronized Bitmap getBitmap () { @@ -168,25 +194,31 @@ public class EmacsView extends ViewGroup View child; Rect windowRect; + count = getChildCount (); + if (changed || mustReportLayout) { mustReportLayout = false; window.viewLayout (left, top, right, bottom); } - if (changed) + if (changed + /* Check that a change has really happened. */ + && (bitmapDirty == null + || bitmapDirty.width () != right - left + || bitmapDirty.height () != bottom - top)) bitmapDirty = new Rect (left, top, right, bottom); - count = getChildCount (); - for (i = 0; i < count; ++i) { child = getChildAt (i); + Log.d (TAG, "onLayout: " + child); + if (child == surfaceView) /* The child is the surface view, so give it the entire view. */ - child.layout (left, top, right, bottom); + child.layout (0, 0, right - left, bottom - top); else if (child.getVisibility () != GONE) { if (!(child instanceof EmacsView)) @@ -201,59 +233,68 @@ public class EmacsView extends ViewGroup } } - public synchronized void + public void damageRect (Rect damageRect) { - damageRegion.union (damageRect); + synchronized (damageRegion) + { + damageRegion.union (damageRect); + } } /* This method is called from both the UI thread and the Emacs thread. */ - public synchronized void + public void swapBuffers (boolean force) { Canvas canvas; Rect damageRect; Bitmap bitmap; - if (damageRegion.isEmpty ()) - return; + /* Code must always take damageRegion, and then surfaceChangeLock, + never the other way around! */ - bitmap = getBitmap (); + synchronized (damageRegion) + { + if (damageRegion.isEmpty ()) + return; - /* Emacs must take the following lock to ensure the access to the - canvas occurs with the surface created. Otherwise, Android - will throttle calls to lockCanvas. */ + bitmap = getBitmap (); - synchronized (surfaceView.surfaceChangeLock) - { - damageRect = damageRegion.getBounds (); + /* Emacs must take the following lock to ensure the access to the + canvas occurs with the surface created. Otherwise, Android + will throttle calls to lockCanvas. */ - if (!surfaceView.isCreated ()) - return; + synchronized (surfaceView.surfaceChangeLock) + { + damageRect = damageRegion.getBounds (); - if (bitmap == null) - return; + if (!surfaceView.isCreated ()) + return; - /* Lock the canvas with the specified damage. */ - canvas = surfaceView.lockCanvas (damageRect); + if (bitmap == null) + return; - /* Return if locking the canvas failed. */ - if (canvas == null) - return; + /* Lock the canvas with the specified damage. */ + canvas = surfaceView.lockCanvas (damageRect); - /* Copy from the back buffer to the canvas. If damageRect was - made empty, then draw the entire back buffer. */ + /* Return if locking the canvas failed. */ + if (canvas == null) + return; - if (damageRect.isEmpty ()) - canvas.drawBitmap (bitmap, 0f, 0f, paint); - else - 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. */ - /* Unlock the canvas and clear the damage. */ - surfaceView.unlockCanvasAndPost (canvas); - damageRegion.setEmpty (); + 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 (); + } } } @@ -308,6 +349,78 @@ public class EmacsView extends ViewGroup public boolean onTouchEvent (MotionEvent motion) { - return window.onSomeKindOfMotionEvent (motion); + return window.onTouchEvent (motion); + } + + private void + moveChildToBack (View child) + { + int index; + + index = indexOfChild (child); + + if (index > 0) + { + detachViewFromParent (index); + + /* The view at 0 is the surface view. */ + attachViewToParent (child, 1, + child.getLayoutParams()); + } + } + + + /* The following two functions must not be called if the view has no + parent, or is parented to an activity. */ + + public void + raise () + { + EmacsView parent; + + parent = (EmacsView) getParent (); + + Log.d (TAG, "raise: parent " + parent); + + if (parent.indexOfChild (this) + == parent.getChildCount () - 1) + return; + + parent.bringChildToFront (this); + + /* Yes, all of this is really necessary! */ + parent.requestLayout (); + parent.invalidate (); + requestLayout (); + invalidate (); + + /* The surface view must be destroyed and recreated. */ + removeView (surfaceView); + addView (surfaceView, 0); + } + + public void + lower () + { + EmacsView parent; + + parent = (EmacsView) getParent (); + + Log.d (TAG, "lower: parent " + parent); + + if (parent.indexOfChild (this) == 1) + return; + + parent.moveChildToBack (this); + + /* Yes, all of this is really necessary! */ + parent.requestLayout (); + parent.invalidate (); + requestLayout (); + invalidate (); + + /* The surface view must be removed and attached again. */ + removeView (surfaceView); + addView (surfaceView, 0); } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index a9a24b61869..1f8596dba50 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -22,6 +22,7 @@ package org.gnu.emacs; import java.lang.IllegalStateException; import java.util.ArrayList; import java.util.List; +import java.util.HashMap; import android.graphics.Rect; import android.graphics.Canvas; @@ -50,9 +51,29 @@ import android.os.Build; Views are also drawables, meaning they can accept drawing requests. */ +/* Help wanted. What does not work includes `EmacsView.raise', + `EmacsView.lower', reparenting a window onto another window. + + All three are likely undocumented restrictions within + EmacsSurface. */ + public class EmacsWindow extends EmacsHandleObject implements EmacsDrawable { + private static final String TAG = "EmacsWindow"; + + private class Coordinate + { + /* Integral coordinate. */ + int x, y; + + Coordinate (int x, int y) + { + this.x = x; + this.y = y; + } + }; + /* The view associated with the window. */ public EmacsView view; @@ -60,12 +81,16 @@ public class EmacsWindow extends EmacsHandleObject private Rect rect; /* The parent window, or null if it is the root window. */ - private EmacsWindow parent; + public EmacsWindow parent; /* List of all children in stacking order. This must be kept consistent! */ public ArrayList children; + /* Map between pointer identifiers and last known position. Used to + compute which pointer changed upon a touch event. */ + private HashMap pointerMap; + /* The window consumer currently attached, if it exists. */ private EmacsWindowAttachmentManager.WindowConsumer attached; @@ -77,6 +102,14 @@ public class EmacsWindow extends EmacsHandleObject last button press or release event. */ private int lastButtonState, lastModifiers; + /* Whether or not the window is mapped, and whether or not it is + deiconified. */ + private boolean isMapped, isIconified; + + /* Whether or not to ask for focus upon being mapped, and whether or + not the window should be focusable. */ + private boolean dontFocusOnMap, dontAcceptFocus; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height) @@ -84,6 +117,7 @@ public class EmacsWindow extends EmacsHandleObject super (handle); rect = new Rect (x, y, x + width, y + height); + pointerMap = new HashMap (); /* Create the view from the context's UI thread. The window is unmapped, so the view is GONE. */ @@ -97,7 +131,7 @@ public class EmacsWindow extends EmacsHandleObject if (parent != null) { parent.children.add (this); - parent.view.post (new Runnable () { + EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override public void run () @@ -106,23 +140,6 @@ public class EmacsWindow extends EmacsHandleObject } }); } - 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); } @@ -159,7 +176,7 @@ public class EmacsWindow extends EmacsHandleObject + "children!"); /* Remove the view from its parent and make it invisible. */ - view.post (new Runnable () { + EmacsService.SERVICE.runOnUiThread (new Runnable () { public void run () { @@ -174,7 +191,7 @@ public class EmacsWindow extends EmacsHandleObject parent = (View) view.getParent (); - if (parent != null && attached == null) + if (parent != null) ((ViewGroup) parent).removeView (view); manager.detachWindow (EmacsWindow.this); @@ -199,24 +216,33 @@ public class EmacsWindow extends EmacsHandleObject public void viewLayout (int left, int top, int right, int bottom) { + int rectWidth, rectHeight; + synchronized (this) { rect.left = left; rect.top = top; rect.right = right; rect.bottom = bottom; - - EmacsNative.sendConfigureNotify (this.handle, - System.currentTimeMillis (), - left, top, rect.width (), - rect.height ()); } + + rectWidth = right - left; + rectHeight = bottom - top; + + EmacsNative.sendConfigureNotify (this.handle, + System.currentTimeMillis (), + left, top, rectWidth, + rectHeight); } public void requestViewLayout () { - view.post (new Runnable () { + /* This is necessary because otherwise subsequent drawing on the + Emacs thread may be lost. */ + view.explicitlyDirtyBitmap (rect); + + EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override public void run () @@ -261,28 +287,77 @@ public class EmacsWindow extends EmacsHandleObject public void mapWindow () { - view.post (new Runnable () { - @Override - public void - run () - { + if (isMapped) + return; - view.setVisibility (View.VISIBLE); - /* Eventually this should check no-focus-on-map. */ - view.requestFocus (); - } - }); + isMapped = true; + + if (parent == null) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + EmacsWindowAttachmentManager manager; + + /* Make the view visible, first of all. */ + view.setVisibility (View.VISIBLE); + + 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); + + if (!getDontFocusOnMap ()) + /* Eventually this should check no-focus-on-map. */ + view.requestFocus (); + } + }); + } + else + { + /* Do the same thing as above, but don't register this + window. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.setVisibility (View.VISIBLE); + + if (!getDontFocusOnMap ()) + /* Eventually this should check no-focus-on-map. */ + view.requestFocus (); + } + }); + } } public void unmapWindow () { + if (!isMapped) + return; + + isMapped = false; + view.post (new Runnable () { @Override public void run () { + EmacsWindowAttachmentManager manager; + + manager = EmacsWindowAttachmentManager.MANAGER; + view.setVisibility (View.GONE); + + /* Now that the window is unmapped, unregister it as + well. */ + manager.detachWindow (EmacsWindow.this); } }); } @@ -413,6 +488,161 @@ public class EmacsWindow extends EmacsHandleObject return 4; } + /* Return the ID of the pointer which changed in EVENT. Value is -1 + if it could not be determined, else the pointer that changed, or + -2 if -1 would have been returned, but there is also a pointer + that is a mouse. */ + + private int + figureChange (MotionEvent event) + { + int pointerID, i, truncatedX, truncatedY, pointerIndex; + Coordinate coordinate; + boolean mouseFlag; + + /* pointerID is always initialized but the Java compiler is too + dumb to know that. */ + pointerID = -1; + mouseFlag = false; + + switch (event.getActionMasked ()) + { + case MotionEvent.ACTION_DOWN: + /* Primary pointer pressed with index 0. */ + + /* Detect mice. If this is a mouse event, give it to + onSomeKindOfMotionEvent. */ + if ((Build.VERSION.SDK_INT + >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + && event.getToolType (0) == MotionEvent.TOOL_TYPE_MOUSE) + return -2; + + pointerID = event.getPointerId (0); + pointerMap.put (pointerID, + new Coordinate ((int) event.getX (0), + (int) event.getY (0))); + break; + + case MotionEvent.ACTION_UP: + /* Primary pointer released with index 0. */ + pointerID = event.getPointerId (0); + pointerMap.remove (pointerID); + break; + + case MotionEvent.ACTION_POINTER_DOWN: + /* New pointer. Find the pointer ID from the index and place + it in the map. */ + pointerIndex = event.getActionIndex (); + pointerID = event.getPointerId (pointerIndex); + pointerMap.put (pointerID, + new Coordinate ((int) event.getX (pointerID), + (int) event.getY (pointerID))); + break; + + case MotionEvent.ACTION_POINTER_UP: + /* Pointer removed. Remove it from the map. */ + pointerIndex = event.getActionIndex (); + pointerID = event.getPointerId (pointerIndex); + pointerMap.remove (pointerID); + break; + + default: + + /* Loop through each pointer in the event. */ + for (i = 0; i < event.getPointerCount (); ++i) + { + pointerID = event.getPointerId (i); + + /* Look up that pointer in the map. */ + coordinate = pointerMap.get (pointerID); + + if (coordinate != null) + { + /* See if coordinates have changed. */ + truncatedX = (int) event.getX (i); + truncatedY = (int) event.getY (i); + + if (truncatedX != coordinate.x + || truncatedY != coordinate.y) + { + /* The pointer changed. Update the coordinate and + break out of the loop. */ + coordinate.x = truncatedX; + coordinate.y = truncatedY; + + break; + } + } + + /* See if this is a mouse. If so, set the mouseFlag. */ + if ((Build.VERSION.SDK_INT + >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + && event.getToolType (i) == MotionEvent.TOOL_TYPE_MOUSE) + mouseFlag = true; + } + + /* Set the pointer ID to -1 if the loop failed to find any + changed pointer. If a mouse pointer was found, set it to + -2. */ + if (i == event.getPointerCount ()) + pointerID = (mouseFlag ? -2 : -1); + } + + /* Return the pointer ID. */ + return pointerID; + } + + public boolean + onTouchEvent (MotionEvent event) + { + int pointerID, index; + + /* Extract the ``touch ID'' (or in Android, the ``pointer + ID''.) */ + pointerID = figureChange (event); + + if (pointerID < 0) + { + /* If this is a mouse event, give it to + onSomeKindOfMotionEvent. */ + if (pointerID == -2) + return onSomeKindOfMotionEvent (event); + + return false; + } + + /* Find the pointer index corresponding to the event. */ + index = event.findPointerIndex (pointerID); + + switch (event.getActionMasked ()) + { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + /* Touch down event. */ + EmacsNative.sendTouchDown (this.handle, (int) event.getX (index), + (int) event.getY (index), + event.getEventTime (), pointerID); + return true; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + /* Touch up event. */ + EmacsNative.sendTouchUp (this.handle, (int) event.getX (index), + (int) event.getY (index), + event.getEventTime (), pointerID); + return true; + + case MotionEvent.ACTION_MOVE: + /* Pointer motion event. */ + EmacsNative.sendTouchMove (this.handle, (int) event.getX (index), + (int) event.getY (index), + event.getEventTime (), pointerID); + return true; + } + + return false; + } + public boolean onSomeKindOfMotionEvent (MotionEvent event) { @@ -472,13 +702,201 @@ public class EmacsWindow extends EmacsHandleObject case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_UP: - /* Emacs must return true even though touch events are not yet - handled, because the value of this function is used by the - system to decide whether or not Emacs gets ACTION_MOVE + /* Emacs must return true even though touch events are not + handled here, because the value of this function is used by + the system to decide whether or not Emacs gets ACTION_MOVE events. */ return true; + + case MotionEvent.ACTION_SCROLL: + /* Send a scroll event with the specified deltas. */ + EmacsNative.sendWheel (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime (), + lastModifiers, + event.getAxisValue (MotionEvent.AXIS_HSCROLL), + event.getAxisValue (MotionEvent.AXIS_VSCROLL)); + return true; } return false; } + + public void + reparentTo (final EmacsWindow otherWindow, int x, int y) + { + int width, height; + + /* Reparent this window to the other window. */ + + if (parent != null) + parent.children.remove (this); + + if (otherWindow != null) + otherWindow.children.add (this); + + parent = otherWindow; + + /* Move this window to the new location. */ + synchronized (this) + { + width = rect.width (); + height = rect.height (); + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; + } + + /* Now do the work necessary on the UI thread to reparent the + window. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + EmacsWindowAttachmentManager manager; + View parent; + + /* First, detach this window if necessary. */ + manager = EmacsWindowAttachmentManager.MANAGER; + manager.detachWindow (EmacsWindow.this); + + /* Also unparent this view. */ + parent = (View) view.getParent (); + + if (parent != null) + ((ViewGroup) parent).removeView (view); + + /* Next, either add this window as a child of the new + parent's view, or make it available again. */ + if (otherWindow != null) + otherWindow.view.addView (view); + else if (EmacsWindow.this.isMapped) + manager.registerWindow (EmacsWindow.this); + + /* Request relayout. */ + view.requestLayout (); + } + }); + } + + public void + makeInputFocus (long time) + { + /* TIME is currently ignored. Request the input focus now. */ + + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.requestFocus (); + } + }); + } + + public void + raise () + { + /* This does nothing here. */ + if (parent == null) + return; + + /* Remove and add this view again. */ + parent.children.remove (this); + parent.children.add (this); + + /* Request a relayout. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.raise (); + } + }); + } + + public void + lower () + { + /* This does nothing here. */ + if (parent == null) + return; + + /* Remove and add this view again. */ + parent.children.remove (this); + parent.children.add (this); + + /* Request a relayout. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.lower (); + } + }); + } + + public int[] + getWindowGeometry () + { + int[] array; + Rect rect; + + array = new int[4]; + rect = getGeometry (); + + array[0] = rect.left; + array[1] = rect.top; + array[2] = rect.width (); + array[3] = rect.height (); + + return array; + } + + public void + noticeIconified () + { + isIconified = true; + EmacsNative.sendIconified (this.handle); + } + + public void + noticeDeiconified () + { + isIconified = false; + EmacsNative.sendDeiconified (this.handle); + } + + public synchronized void + setDontAcceptFocus (final boolean dontAcceptFocus) + { + this.dontAcceptFocus = dontAcceptFocus; + + /* Update the view's focus state. */ + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.setFocusable (!dontAcceptFocus); + view.setFocusableInTouchMode (!dontAcceptFocus); + } + }); + } + + public synchronized void + setDontFocusOnMap (final boolean dontFocusOnMap) + { + this.dontFocusOnMap = dontFocusOnMap; + } + + public synchronized boolean + getDontFocusOnMap () + { + return dontFocusOnMap; + } }; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 34be2ab8789..15eb3bb65c2 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -19,6 +19,7 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -68,7 +69,7 @@ public class EmacsWindowAttachmentManager }; private List consumers; - private List windows; + public List windows; public EmacsWindowAttachmentManager () @@ -98,12 +99,19 @@ public class EmacsWindowAttachmentManager EmacsNative.sendWindowAction ((short) 0, 0); } - public void + public synchronized void registerWindow (EmacsWindow window) { Intent intent; - Log.d (TAG, "registerWindow " + window); + Log.d (TAG, "registerWindow (maybe): " + window); + + if (windows.contains (window)) + /* The window is already registered. */ + return; + + Log.d (TAG, "registerWindow: " + window); + windows.add (window); for (WindowConsumer consumer : consumers) @@ -146,7 +154,7 @@ public class EmacsWindowAttachmentManager consumers.remove (consumer); } - public void + public synchronized void detachWindow (EmacsWindow window) { WindowConsumer consumer; @@ -162,5 +170,43 @@ public class EmacsWindowAttachmentManager consumers.remove (consumer); consumer.destroy (); } + + windows.remove (window); + } + + public void + noticeIconified (WindowConsumer consumer) + { + EmacsWindow window; + + Log.d (TAG, "noticeIconified " + consumer); + + /* If a window is attached, send the appropriate iconification + events. */ + window = consumer.getAttachedWindow (); + + if (window != null) + window.noticeIconified (); + } + + public void + noticeDeiconified (WindowConsumer consumer) + { + EmacsWindow window; + + Log.d (TAG, "noticeDeiconified " + consumer); + + /* If a window is attached, send the appropriate iconification + events. */ + window = consumer.getAttachedWindow (); + + if (window != null) + window.noticeDeiconified (); + } + + public synchronized List + copyWindows () + { + return new ArrayList (windows); } }; diff --git a/lisp/frame.el b/lisp/frame.el index 436323f7d58..d35df71a6cc 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -1647,6 +1647,7 @@ live frame and defaults to the selected one." (declare-function ns-frame-geometry "nsfns.m" (&optional frame)) (declare-function pgtk-frame-geometry "pgtkfns.c" (&optional frame)) (declare-function haiku-frame-geometry "haikufns.c" (&optional frame)) +(declare-function android-frame-geometry "androidfns.c" (&optional frame)) (defun frame-geometry (&optional frame) "Return geometric attributes of FRAME. @@ -1700,6 +1701,8 @@ and width values are in pixels. (pgtk-frame-geometry frame)) ((eq frame-type 'haiku) (haiku-frame-geometry frame)) + ((eq frame-type 'android) + (android-frame-geometry frame)) (t (list '(outer-position 0 . 0) @@ -1826,6 +1829,7 @@ of frames like calls to map a frame or change its visibility." (declare-function ns-frame-edges "nsfns.m" (&optional frame type)) (declare-function pgtk-frame-edges "pgtkfns.c" (&optional frame type)) (declare-function haiku-frame-edges "haikufns.c" (&optional frame type)) +(declare-function android-frame-edges "androidfns.c" (&optional frame type)) (defun frame-edges (&optional frame type) "Return coordinates of FRAME's edges. @@ -1853,6 +1857,8 @@ FRAME." (pgtk-frame-edges frame type)) ((eq frame-type 'haiku) (haiku-frame-edges frame type)) + ((eq frame-type 'android) + (android-frame-edges frame type)) (t (list 0 0 (frame-width frame) (frame-height frame)))))) @@ -1861,6 +1867,7 @@ FRAME." (declare-function ns-mouse-absolute-pixel-position "nsfns.m") (declare-function pgtk-mouse-absolute-pixel-position "pgtkfns.c") (declare-function haiku-mouse-absolute-pixel-position "haikufns.c") +(declare-function android-mouse-absolute-pixel-position "androidfns.c") (defun mouse-absolute-pixel-position () "Return absolute position of mouse cursor in pixels. @@ -1879,6 +1886,8 @@ position (0, 0) of the selected frame's terminal." (pgtk-mouse-absolute-pixel-position)) ((eq frame-type 'haiku) (haiku-mouse-absolute-pixel-position)) + ((eq frame-type 'android) + (android-mouse-absolute-pixel-position)) (t (cons 0 0))))) @@ -1887,6 +1896,8 @@ position (0, 0) of the selected frame's terminal." (declare-function w32-set-mouse-absolute-pixel-position "w32fns.c" (x y)) (declare-function x-set-mouse-absolute-pixel-position "xfns.c" (x y)) (declare-function haiku-set-mouse-absolute-pixel-position "haikufns.c" (x y)) +(declare-function android-set-mouse-absolute-pixel-position + "androidfns.c" (x y)) (defun set-mouse-absolute-pixel-position (x y) "Move mouse pointer to absolute pixel position (X, Y). @@ -1903,7 +1914,9 @@ position (0, 0) of the selected frame's terminal." ((eq frame-type 'w32) (w32-set-mouse-absolute-pixel-position x y)) ((eq frame-type 'haiku) - (haiku-set-mouse-absolute-pixel-position x y))))) + (haiku-set-mouse-absolute-pixel-position x y)) + ((eq frame-type 'android) + (android-set-mouse-absolute-pixel-position x y))))) (defun frame-monitor-attributes (&optional frame) "Return the attributes of the physical monitor dominating FRAME. @@ -1999,6 +2012,7 @@ workarea attribute." ;; TODO: implement this on PGTK. ;; (declare-function pgtk-frame-list-z-order "pgtkfns.c" (&optional display)) (declare-function haiku-frame-list-z-order "haikufns.c" (&optional display)) +(declare-function android-frame-list-z-order "androidfns.c" (&optional display)) (defun frame-list-z-order (&optional display) "Return list of Emacs' frames, in Z (stacking) order. @@ -2024,13 +2038,17 @@ Return nil if DISPLAY contains no Emacs frame." ;; (pgtk-frame-list-z-order display) nil) ((eq frame-type 'haiku) - (haiku-frame-list-z-order display))))) + (haiku-frame-list-z-order display)) + ((eq frame-type 'android) + (android-frame-list-z-order display))))) (declare-function x-frame-restack "xfns.c" (frame1 frame2 &optional above)) (declare-function w32-frame-restack "w32fns.c" (frame1 frame2 &optional above)) (declare-function ns-frame-restack "nsfns.m" (frame1 frame2 &optional above)) (declare-function pgtk-frame-restack "pgtkfns.c" (frame1 frame2 &optional above)) (declare-function haiku-frame-restack "haikufns.c" (frame1 frame2 &optional above)) +(declare-function android-frame-restack "androidfns.c" (frame1 frame2 + &optional above)) (defun frame-restack (frame1 frame2 &optional above) "Restack FRAME1 below FRAME2. @@ -2064,7 +2082,9 @@ Some window managers may refuse to restack windows." ((eq frame-type 'haiku) (haiku-frame-restack frame1 frame2 above)) ((eq frame-type 'pgtk) - (pgtk-frame-restack frame1 frame2 above)))) + (pgtk-frame-restack frame1 frame2 above)) + ((eq frame-type 'android) + (android-frame-restack frame1 frame2 above)))) (error "Cannot restack frames"))) (defun frame-size-changed-p (&optional frame) @@ -2113,6 +2133,8 @@ frame's display)." (> w32-num-mouse-buttons 0))) ((memq frame-type '(x ns haiku pgtk)) t) ;; We assume X, NeXTstep, GTK, and Haiku *always* have a pointing device + ((eq frame-type 'android) + (android-detect-mouse)) (t (or (and (featurep 'xt-mouse) xterm-mouse-mode) @@ -2396,6 +2418,8 @@ If DISPLAY is omitted or nil, it defaults to the selected frame's display." (&optional terminal)) (declare-function haiku-display-monitor-attributes-list "haikufns.c" (&optional terminal)) +(declare-function android-display-monitor-attributes-list "androidfns.c" + (&optional terminal)) (defun display-monitor-attributes-list (&optional display) "Return a list of physical monitor attributes on DISPLAY. @@ -2449,6 +2473,8 @@ monitors." (pgtk-display-monitor-attributes-list display)) ((eq frame-type 'haiku) (haiku-display-monitor-attributes-list display)) + ((eq frame-type 'android) + (android-display-monitor-attributes-list display)) (t (let ((geometry (list 0 0 (display-pixel-width display) (display-pixel-height display)))) diff --git a/lisp/mwheel.el b/lisp/mwheel.el index 1be52d24e34..67d93c3558d 100644 --- a/lisp/mwheel.el +++ b/lisp/mwheel.el @@ -58,7 +58,8 @@ (defcustom mouse-wheel-down-event (if (or (featurep 'w32-win) (featurep 'ns-win) - (featurep 'haiku-win) (featurep 'pgtk-win)) + (featurep 'haiku-win) (featurep 'pgtk-win) + (featurep 'android-win)) 'wheel-up 'mouse-4) "Event used for scrolling down." @@ -79,7 +80,8 @@ (defcustom mouse-wheel-up-event (if (or (featurep 'w32-win) (featurep 'ns-win) - (featurep 'haiku-win) (featurep 'pgtk-win)) + (featurep 'haiku-win) (featurep 'pgtk-win) + (featurep 'android-win)) 'wheel-down 'mouse-5) "Event used for scrolling up." @@ -254,7 +256,8 @@ Also see `mouse-wheel-tilt-scroll'." (defvar mouse-wheel-left-event (if (or (featurep 'w32-win) (featurep 'ns-win) - (featurep 'haiku-win) (featurep 'pgtk-win)) + (featurep 'haiku-win) (featurep 'pgtk-win) + (featurep 'android-win)) 'wheel-left 'mouse-6) "Event used for scrolling left.") @@ -268,7 +271,8 @@ Also see `mouse-wheel-tilt-scroll'." (defvar mouse-wheel-right-event (if (or (featurep 'w32-win) (featurep 'ns-win) - (featurep 'haiku-win) (featurep 'pgtk-win)) + (featurep 'haiku-win) (featurep 'pgtk-win) + (featurep 'android-win)) 'wheel-right 'mouse-7) "Event used for scrolling right.") diff --git a/src/android.c b/src/android.c index 2f852662001..eab18dc6152 100644 --- a/src/android.c +++ b/src/android.c @@ -82,6 +82,11 @@ struct android_emacs_service jmethodID copy_area; jmethodID clear_window; jmethodID clear_area; + jmethodID ring_bell; + jmethodID query_tree; + jmethodID get_screen_width; + jmethodID get_screen_height; + jmethodID detect_mouse; }; struct android_emacs_pixmap @@ -196,9 +201,6 @@ struct android_event_queue /* The thread used to run select. */ pthread_t select_thread; - /* Condition variable for the writing side. */ - pthread_cond_t write_var; - /* Condition variables for the reading side. */ pthread_cond_t read_var; @@ -314,11 +316,6 @@ android_init_events (void) "pthread_mutex_init: %s", strerror (errno)); - if (pthread_cond_init (&event_queue.write_var, NULL)) - __android_log_print (ANDROID_LOG_FATAL, __func__, - "pthread_cond_init: %s", - strerror (errno)); - if (pthread_cond_init (&event_queue.read_var, NULL)) __android_log_print (ANDROID_LOG_FATAL, __func__, "pthread_cond_init: %s", @@ -387,8 +384,7 @@ android_next_event (union android_event *event_return) /* Free the container. */ free (container); - /* Signal that events can now be written. */ - pthread_cond_signal (&event_queue.write_var); + /* Unlock the queue. */ pthread_mutex_unlock (&event_queue.mutex); } @@ -407,12 +403,6 @@ android_write_event (union android_event *event) return; pthread_mutex_lock (&event_queue.mutex); - - /* The event queue is full, wait for events to be read. */ - if (event_queue.num_events >= 1024) - pthread_cond_wait (&event_queue.write_var, - &event_queue.mutex); - container->next = event_queue.events.next; container->last = &event_queue.events; container->next->last = container; @@ -421,6 +411,10 @@ android_write_event (union android_event *event) event_queue.num_events++; pthread_cond_signal (&event_queue.read_var); pthread_mutex_unlock (&event_queue.mutex); + + /* Now set pending_signals to true. This allows C-g to be handled + immediately even without SIGIO. */ + pending_signals = true; } int @@ -604,31 +598,60 @@ android_file_access_p (const char *name, int amode) { AAsset *asset; AAssetDir *directory; + int length; if (!asset_manager) return false; if (!(amode & W_OK) && (name = android_get_asset_name (name))) { + if (!strcmp (name, "") || !strcmp (name, "/")) + /* /assets always exists. */ + return true; + /* Check if the asset exists by opening it. Suboptimal! */ asset = AAssetManager_open (asset_manager, name, AASSET_MODE_UNKNOWN); if (!asset) { - /* See if it's a directory also. */ + /* See if it's a directory as well. To open a directory + with the asset manager, the trailing slash (if specified) + must be removed. */ directory = AAssetManager_openDir (asset_manager, name); if (directory) { + /* Make sure the directory actually has files in it. */ + + if (!AAssetDir_getNextFileName (directory)) + { + AAssetDir_close (directory); + errno = ENOENT; + return false; + } + AAssetDir_close (directory); return true; } + errno = ENOENT; return false; } AAsset_close (asset); + + /* If NAME is a directory name, but it was a regular file, set + errno to ENOTDIR and return false. This is to behave like + faccessat. */ + + length = strlen (name); + if (name[length - 1] == '/') + { + errno = ENOTDIR; + return false; + } + return true; } @@ -819,12 +842,31 @@ int android_close (int fd) { if (fd < ANDROID_MAX_ASSET_FD - && (android_table[fd].flags & ANDROID_FD_TABLE_ENTRY_IS_VALID)) + && (android_table[fd].flags + & ANDROID_FD_TABLE_ENTRY_IS_VALID)) android_table[fd].flags = 0; return close (fd); } +/* Like fclose. However, remove any information associated with + FILE's file descriptor from the asset table as well. */ + +int +android_fclose (FILE *stream) +{ + int fd; + + fd = fileno (stream); + + if (fd != -1 && fd < ANDROID_MAX_ASSET_FD + && (android_table[fd].flags + & ANDROID_FD_TABLE_ENTRY_IS_VALID)) + android_table[fd].flags = 0; + + return fclose (stream); +} + /* Return the current user's ``home'' directory, which is actually the app data directory on Android. */ @@ -1010,7 +1052,12 @@ android_init_emacs_service (void) "(Lorg/gnu/emacs/EmacsWindow;)V"); FIND_METHOD (clear_area, "clearArea", "(Lorg/gnu/emacs/EmacsWindow;IIII)V"); - + FIND_METHOD (ring_bell, "ringBell", "()V"); + FIND_METHOD (query_tree, "queryTree", + "(Lorg/gnu/emacs/EmacsWindow;)[S"); + FIND_METHOD (get_screen_width, "getScreenWidth", "(Z)I"); + FIND_METHOD (get_screen_height, "getScreenHeight", "(Z)I"); + FIND_METHOD (detect_mouse, "detectMouse", "()Z"); #undef FIND_METHOD } @@ -1342,6 +1389,97 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, android_write_event (&event); } +extern JNIEXPORT void JNICALL +NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint pointer_id) +{ + union android_event event; + + event.touch.type = ANDROID_TOUCH_DOWN; + event.touch.window = window; + event.touch.x = x; + event.touch.y = y; + event.touch.time = time; + event.touch.pointer_id = pointer_id; + + android_write_event (&event); +} + +extern JNIEXPORT void JNICALL +NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint pointer_id) +{ + union android_event event; + + event.touch.type = ANDROID_TOUCH_UP; + event.touch.window = window; + event.touch.x = x; + event.touch.y = y; + event.touch.time = time; + event.touch.pointer_id = pointer_id; + + android_write_event (&event); +} + +extern JNIEXPORT void JNICALL +NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint pointer_id) +{ + union android_event event; + + event.touch.type = ANDROID_TOUCH_MOVE; + event.touch.window = window; + event.touch.x = x; + event.touch.y = y; + event.touch.time = time; + event.touch.pointer_id = pointer_id; + + android_write_event (&event); +} + +extern JNIEXPORT void JNICALL +NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jlong time, jint state, + jfloat x_delta, jfloat y_delta) +{ + union android_event event; + + event.wheel.type = ANDROID_WHEEL; + event.wheel.window = window; + event.wheel.x = x; + event.wheel.y = y; + event.wheel.time = time; + event.wheel.state = state; + event.wheel.x_delta = x_delta; + event.wheel.y_delta = y_delta; + + android_write_event (&event); +} + +extern JNIEXPORT void JNICALL +NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object, + jshort window) +{ + union android_event event; + + event.iconified.type = ANDROID_ICONIFIED; + event.iconified.window = window; +} + +extern JNIEXPORT void JNICALL +NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object, + jshort window) +{ + union android_event event; + + event.iconified.type = ANDROID_DEICONIFIED; + event.iconified.window = window; +} + #pragma clang diagnostic pop @@ -1979,10 +2117,22 @@ android_set_clip_rectangles (struct android_gc *gc, int clip_x_origin, } void -android_reparent_window (android_window w, android_window parent, +android_reparent_window (android_window w, android_window parent_handle, int x, int y) { - /* TODO */ + jobject window, parent; + jmethodID method; + + window = android_resolve_handle (w, ANDROID_HANDLE_WINDOW); + parent = android_resolve_handle (parent_handle, + ANDROID_HANDLE_WINDOW); + + method = android_lookup_method ("org/gnu/emacs/EmacsWindow", + "reparentTo", + "(Lorg/gnu/emacs/EmacsWindow;II)V"); + (*android_java_env)->CallVoidMethod (android_java_env, window, + method, + parent, (jint) x, (jint) y); } /* Look up the method with SIGNATURE by NAME in CLASS. Abort if it @@ -2904,6 +3054,163 @@ android_put_image (android_pixmap handle, struct android_image *image) ANDROID_DELETE_LOCAL_REF (bitmap); } +void +android_bell (void) +{ + (*android_java_env)->CallVoidMethod (android_java_env, + emacs_service, + service_class.ring_bell); +} + +void +android_set_input_focus (android_window handle, unsigned long time) +{ + jobject window; + jmethodID make_input_focus; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + make_input_focus = android_lookup_method ("org/gnu/emacs/EmacsWindow", + "makeInputFocus", "(J)V"); + + (*android_java_env)->CallVoidMethod (android_java_env, window, + make_input_focus, (jlong) time); +} + +void +android_raise_window (android_window handle) +{ + jobject window; + jmethodID raise; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + raise = android_lookup_method ("org/gnu/emacs/EmacsWindow", + "raise", "()V"); + + (*android_java_env)->CallVoidMethod (android_java_env, window, + raise); +} + +void +android_lower_window (android_window handle) +{ + jobject window; + jmethodID lower; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + lower = android_lookup_method ("org/gnu/emacs/EmacsWindow", + "lower", "()V"); + + (*android_java_env)->CallVoidMethod (android_java_env, window, + lower); +} + +int +android_query_tree (android_window handle, android_window *root_return, + android_window *parent_return, + android_window **children_return, + unsigned int *nchildren_return) +{ + jobject window, array; + jsize nelements, i; + android_window *children; + jshort *shorts; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + + /* window can be NULL, so this is a service method. */ + array + = (*android_java_env)->CallObjectMethod (android_java_env, + emacs_service, + service_class.query_tree, + window); + if (!array) + { + (*android_java_env)->ExceptionClear (android_java_env); + memory_full (0); + } + + /* The first element of the array is the parent window. The rest + are the children. */ + nelements = (*android_java_env)->GetArrayLength (android_java_env, + array); + eassert (nelements); + + /* Now fill in the children. */ + children = xnmalloc (nelements - 1, sizeof *children); + + shorts + = (*android_java_env)->GetShortArrayElements (android_java_env, array, + NULL); + for (i = 1; i < nelements; ++i) + children[i] = shorts[i]; + + /* Finally, return the parent and other values. */ + *root_return = 0; + *parent_return = shorts[0]; + *children_return = children; + + /* Release the array contents. */ + (*android_java_env)->ReleaseShortArrayElements (android_java_env, array, + shorts, JNI_ABORT); + + ANDROID_DELETE_LOCAL_REF (array); + return 1; +} + +void +android_get_geometry (android_window handle, + android_window *root_return, + int *x_return, int *y_return, + unsigned int *width_return, + unsigned int *height_return, + unsigned int *border_width_return) +{ + jobject window; + jarray window_geometry; + jmethodID get_geometry; + jint *ints; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + get_geometry = android_lookup_method ("org/gnu/emacs/EmacsWindow", + "getWindowGeometry", "()[I"); + + window_geometry + = (*android_java_env)->CallObjectMethod (android_java_env, + window, + get_geometry); + if (!window_geometry) + { + (*android_java_env)->ExceptionClear (android_java_env); + memory_full (0); + } + + /* window_geometry is an array containing x, y, width and + height. border_width is always 0 on Android. */ + eassert ((*android_java_env)->GetArrayLength (android_java_env, + window_geometry) + == 4); + + *root_return = 0; + *border_width_return = 0; + + ints + = (*android_java_env)->GetIntArrayElements (android_java_env, + window_geometry, + NULL); + + *x_return = ints[0]; + *y_return = ints[1]; + *width_return = ints[2]; + *height_return = ints[3]; + + (*android_java_env)->ReleaseIntArrayElements (android_java_env, + window_geometry, + ints, JNI_ABORT); + + /* Now free the local reference. */ + ANDROID_DELETE_LOCAL_REF (window_geometry); +} + /* Low level drawing primitives. */ @@ -2998,6 +3305,86 @@ android_damage_window (android_drawable handle, +/* Other misc system routines. */ + +int +android_get_screen_width (void) +{ + return (*android_java_env)->CallIntMethod (android_java_env, + emacs_service, + service_class.get_screen_width, + (jboolean) false); +} + +int +android_get_screen_height (void) +{ + return (*android_java_env)->CallIntMethod (android_java_env, + emacs_service, + service_class.get_screen_height, + (jboolean) false); +} + +int +android_get_mm_width (void) +{ + return (*android_java_env)->CallIntMethod (android_java_env, + emacs_service, + service_class.get_screen_width, + (jboolean) true); +} + +int +android_get_mm_height (void) +{ + return (*android_java_env)->CallIntMethod (android_java_env, + emacs_service, + service_class.get_screen_height, + (jboolean) true); +} + +bool +android_detect_mouse (void) +{ + return (*android_java_env)->CallBooleanMethod (android_java_env, + emacs_service, + service_class.detect_mouse); +} + +void +android_set_dont_focus_on_map (android_window handle, + bool no_focus_on_map) +{ + jmethodID method; + jobject window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + method = android_lookup_method ("org/gnu/emacs/EmacsWindow", + "setDontFocusOnMap", "(Z)V"); + + (*android_java_env)->CallVoidMethod (android_java_env, window, + method, + (jboolean) no_focus_on_map); +} + +void +android_set_dont_accept_focus (android_window handle, + bool no_accept_focus) +{ + jmethodID method; + jobject window; + + window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); + method = android_lookup_method ("org/gnu/emacs/EmacsWindow", + "setDontAcceptFocus", "(Z)V"); + + (*android_java_env)->CallVoidMethod (android_java_env, window, + method, + (jboolean) no_accept_focus); +} + + + #undef faccessat /* Replace the system faccessat with one which understands AT_EACCESS. @@ -3015,6 +3402,128 @@ faccessat (int dirfd, const char *pathname, int mode, int flags) return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS); } + + +/* Directory listing emulation. */ + +struct android_dir +{ + /* The real DIR *, if it exists. */ + DIR *dir; + + /* Otherwise, the AAssetDir. */ + void *asset_dir; +}; + +/* Like opendir. However, return an asset directory if NAME points to + an asset. */ + +struct android_dir * +android_opendir (const char *name) +{ + struct android_dir *dir; + AAssetDir *asset_dir; + const char *asset_name; + + asset_name = android_get_asset_name (name); + + /* If the asset manager exists and NAME is an asset, return an asset + directory. */ + if (asset_manager && asset_name) + { + asset_dir = AAssetManager_openDir (asset_manager, + asset_name); + + if (!asset_dir) + { + errno = ENOENT; + return NULL; + } + + dir = xmalloc (sizeof *dir); + dir->dir = NULL; + dir->asset_dir = asset_dir; + return dir; + } + + /* Otherwise, open the directory normally. */ + dir = xmalloc (sizeof *dir); + dir->asset_dir = NULL; + dir->dir = opendir (name); + + if (!dir->dir) + { + xfree (dir); + return NULL; + } + + return dir; +} + +/* Like readdir, except it understands asset directories. */ + +struct dirent * +android_readdir (struct android_dir *dir) +{ + static struct dirent dirent; + const char *filename; + + if (dir->asset_dir) + { + filename = AAssetDir_getNextFileName (dir->asset_dir); + errno = 0; + + if (!filename) + return NULL; + + memset (&dirent, 0, sizeof dirent); + dirent.d_ino = 0; + dirent.d_off = 0; + dirent.d_reclen = sizeof dirent; + dirent.d_type = DT_UNKNOWN; + strncpy (dirent.d_name, filename, + sizeof dirent.d_name - 1); + return &dirent; + } + + return readdir (dir->dir); +} + +/* Like closedir, but it also closes asset manager directories. */ + +void +android_closedir (struct android_dir *dir) +{ + if (dir->dir) + closedir (dir->dir); + else + AAssetDir_close (dir->asset_dir); + + xfree (dir); +} + + + +/* emacs_abort implementation for Android. This logs a stack + trace. */ + +void +emacs_abort (void) +{ + volatile char *foo; + + __android_log_print (ANDROID_LOG_FATAL, __func__, + "emacs_abort called, please review the ensuing" + " stack trace"); + + /* Cause a NULL pointer dereference to make debuggerd generate a + tombstone. */ + foo = NULL; + *foo = '\0'; + + abort (); +} + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ diff --git a/src/android.h b/src/android.h index 4cf194f23cf..cddd315b9a3 100644 --- a/src/android.h +++ b/src/android.h @@ -27,7 +27,9 @@ along with GNU Emacs. If not, see . */ #ifndef ANDROID_STUBIFY #include #include + #include +#include #include @@ -53,6 +55,7 @@ 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 int android_fclose (FILE *); extern const char *android_get_home_directory (void); extern double android_pixel_density_x, android_pixel_density_y; @@ -71,6 +74,26 @@ extern unsigned char *android_lock_bitmap (android_window, jobject *); extern void android_damage_window (android_window, struct android_rectangle *); +extern int android_get_screen_width (void); +extern int android_get_screen_height (void); +extern int android_get_mm_width (void); +extern int android_get_mm_height (void); +extern bool android_detect_mouse (void); + +extern void android_set_dont_focus_on_map (android_window, bool); +extern void android_set_dont_accept_focus (android_window, bool); + + + +/* Directory listing emulation. */ + +struct android_dir; + +extern struct android_dir *android_opendir (const char *); +extern struct dirent *android_readdir (struct android_dir *); +extern void android_closedir (struct android_dir *); + + #endif diff --git a/src/androidfns.c b/src/androidfns.c index 96c2746a21a..459e407b901 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -21,6 +21,7 @@ along with GNU Emacs. If not, see . */ #include #include "lisp.h" +#include "android.h" #include "androidterm.h" #include "blockinput.h" #include "keyboard.h" @@ -151,6 +152,36 @@ android_decode_color (struct frame *f, Lisp_Object color_name, int mono_color) signal_error ("Undefined color", color_name); } +static void +android_set_parent_frame (struct frame *f, Lisp_Object new_value, + Lisp_Object old_value) +{ + struct frame *p; + + p = NULL; + + if (!NILP (new_value) + && (!FRAMEP (new_value) + || !FRAME_LIVE_P (p = XFRAME (new_value)) + || !FRAME_ANDROID_P (p))) + { + store_frame_param (f, Qparent_frame, old_value); + error ("Invalid specification of `parent-frame'"); + } + + if (p != FRAME_PARENT_FRAME (f)) + { + block_input (); + android_reparent_window (FRAME_ANDROID_WINDOW (f), + (p ? FRAME_ANDROID_WINDOW (p) + : FRAME_DISPLAY_INFO (f)->root_window), + f->left_pos, f->top_pos); + unblock_input (); + + fset_parent_frame (f, new_value); + } +} + void android_implicitly_set_name (struct frame *f, Lisp_Object arg, Lisp_Object oldval) @@ -531,9 +562,9 @@ android_default_font_parameter (struct frame *f, Lisp_Object parms) if (! FONTP (font) && ! STRINGP (font)) { const char *names[] = { - "Droid Sans Mono", - "monospace", - "DroidSansMono", + "Droid Sans Mono-12", + "Monospace-12", + "DroidSansMono-12", NULL }; int i; @@ -1119,8 +1150,7 @@ DEFUN ("x-display-pixel-width", Fx_display_pixel_width, error ("Android cross-compilation stub called!"); return Qnil; #else - error ("Not implemented"); - return Qnil; + return make_fixnum (android_get_screen_width ()); #endif } @@ -1133,8 +1163,7 @@ DEFUN ("x-display-pixel-height", Fx_display_pixel_height, error ("Android cross-compilation stub called!"); return Qnil; #else - error ("Not implemented"); - return Qnil; + return make_fixnum (android_get_screen_height ()); #endif } @@ -1185,8 +1214,7 @@ DEFUN ("x-display-mm-width", Fx_display_mm_width, Sx_display_mm_width, error ("Android cross-compilation stub called!"); return Qnil; #else - error ("Not implemented"); - return Qnil; + return make_fixnum (android_get_mm_width ()); #endif } @@ -1198,8 +1226,7 @@ DEFUN ("x-display-mm-height", Fx_display_mm_height, Sx_display_mm_height, error ("Android cross-compilation stub called!"); return Qnil; #else - error ("Not implemented"); - return Qnil; + return make_fixnum (android_get_mm_height ()); #endif } @@ -1225,77 +1252,373 @@ DEFUN ("x-display-visual-class", Fx_display_visual_class, return Qtrue_color; } -DEFUN ("x-display-monitor-attributes-list", Fx_display_monitor_attributes_list, - Sx_display_monitor_attributes_list, +DEFUN ("android-display-monitor-attributes-list", + Fandroid_display_monitor_attributes_list, + Sandroid_display_monitor_attributes_list, 0, 1, 0, - doc: /* SKIP: real doc in xfns.c. */) + doc: /* Return a list of physical monitor attributes on the X display TERMINAL. + +The optional argument TERMINAL specifies which display to ask about. +TERMINAL should be a terminal object, a frame or a display name (a string). +If omitted or nil, that stands for the selected frame's display. + +Internal use only, use `display-monitor-attributes-list' instead. */) (Lisp_Object terminal) { #ifdef ANDROID_STUBIFY error ("Android cross-compilation stub called!"); return Qnil; #else - error ("Not implemented"); - return Qnil; + struct MonitorInfo monitor; + + memset (&monitor, 0, sizeof monitor); + monitor.geom.width = android_get_screen_width (); + monitor.geom.height = android_get_screen_height (); + monitor.mm_width = android_get_mm_width (); + monitor.mm_height = android_get_mm_height (); + monitor.work = monitor.geom; + monitor.name = (char *) "Android device monitor"; + + /* What to do about monitor_frames? */ + return make_monitor_attribute_list (&monitor, 1, + 0, Qnil, NULL); #endif } -DEFUN ("x-frame-geometry", Fx_frame_geometry, Sx_frame_geometry, - 0, 1, 0, doc: /* SKIP: real doc in xfns.c. */) - (Lisp_Object terminal) +#ifndef ANDROID_STUBIFY + +static Lisp_Object +frame_geometry (Lisp_Object frame, Lisp_Object attribute) +{ + struct frame *f = decode_live_frame (frame); + android_window rootw; + unsigned int native_width, native_height, x_border_width = 0; + int x_native = 0, y_native = 0, xptr = 0, yptr = 0; + int left_off = 0, right_off = 0, top_off = 0, bottom_off = 0; + int outer_left, outer_top, outer_right, outer_bottom; + int native_left, native_top, native_right, native_bottom; + int inner_left, inner_top, inner_right, inner_bottom; + int internal_border_width; + bool menu_bar_external = false, tool_bar_external = false; + int menu_bar_height = 0, menu_bar_width = 0; + int tab_bar_height = 0, tab_bar_width = 0; + int tool_bar_height = 0, tool_bar_width = 0; + + if (FRAME_INITIAL_P (f) || !FRAME_ANDROID_P (f) + || !FRAME_ANDROID_WINDOW (f)) + return Qnil; + + block_input (); + android_get_geometry (FRAME_ANDROID_WINDOW (f), + &rootw, &x_native, &y_native, + &native_width, &native_height, &x_border_width); + unblock_input (); + + if (FRAME_PARENT_FRAME (f)) + { + Lisp_Object parent, edges; + + XSETFRAME (parent, FRAME_PARENT_FRAME (f)); + edges = Fandroid_frame_edges (parent, Qnative_edges); + if (!NILP (edges)) + { + x_native += XFIXNUM (Fnth (make_fixnum (0), edges)); + y_native += XFIXNUM (Fnth (make_fixnum (1), edges)); + } + + outer_left = x_native; + outer_top = y_native; + outer_right = outer_left + native_width + 2 * x_border_width; + outer_bottom = outer_top + native_height + 2 * x_border_width; + + native_left = x_native + x_border_width; + native_top = y_native + x_border_width; + native_right = native_left + native_width; + native_bottom = native_top + native_height; + } + else + { + outer_left = xptr; + outer_top = yptr; + outer_right = outer_left + left_off + native_width + right_off; + outer_bottom = outer_top + top_off + native_height + bottom_off; + + native_left = outer_left + left_off; + native_top = outer_top + top_off; + native_right = native_left + native_width; + native_bottom = native_top + native_height; + } + + internal_border_width = FRAME_INTERNAL_BORDER_WIDTH (f); + inner_left = native_left + internal_border_width; + inner_top = native_top + internal_border_width; + inner_right = native_right - internal_border_width; + inner_bottom = native_bottom - internal_border_width; + + menu_bar_height = FRAME_MENU_BAR_HEIGHT (f); + inner_top += menu_bar_height; + menu_bar_width = menu_bar_height ? native_width : 0; + + tab_bar_height = FRAME_TAB_BAR_HEIGHT (f); + tab_bar_width = (tab_bar_height + ? native_width - 2 * internal_border_width + : 0); + inner_top += tab_bar_height; + + tool_bar_height = FRAME_TOOL_BAR_HEIGHT (f); + tool_bar_width = (tool_bar_height + ? native_width - 2 * internal_border_width + : 0); + inner_top += tool_bar_height; + + /* Construct list. */ + if (EQ (attribute, Qouter_edges)) + return list4i (outer_left, outer_top, outer_right, outer_bottom); + else if (EQ (attribute, Qnative_edges)) + return list4i (native_left, native_top, native_right, native_bottom); + else if (EQ (attribute, Qinner_edges)) + return list4i (inner_left, inner_top, inner_right, inner_bottom); + else + return + list (Fcons (Qouter_position, + Fcons (make_fixnum (outer_left), + make_fixnum (outer_top))), + Fcons (Qouter_size, + Fcons (make_fixnum (outer_right - outer_left), + make_fixnum (outer_bottom - outer_top))), + /* Approximate. */ + Fcons (Qexternal_border_size, + Fcons (make_fixnum (right_off), + make_fixnum (bottom_off))), + Fcons (Qouter_border_width, make_fixnum (x_border_width)), + /* Approximate. */ + Fcons (Qtitle_bar_size, + Fcons (make_fixnum (0), + make_fixnum (top_off - bottom_off))), + Fcons (Qmenu_bar_external, menu_bar_external ? Qt : Qnil), + Fcons (Qmenu_bar_size, + Fcons (make_fixnum (menu_bar_width), + make_fixnum (menu_bar_height))), + Fcons (Qtab_bar_size, + Fcons (make_fixnum (tab_bar_width), + make_fixnum (tab_bar_height))), + Fcons (Qtool_bar_external, tool_bar_external ? Qt : Qnil), + Fcons (Qtool_bar_position, FRAME_TOOL_BAR_POSITION (f)), + Fcons (Qtool_bar_size, + Fcons (make_fixnum (tool_bar_width), + make_fixnum (tool_bar_height))), + Fcons (Qinternal_border_width, + make_fixnum (internal_border_width))); +} + +#endif + +DEFUN ("android-frame-geometry", Fandroid_frame_geometry, + Sandroid_frame_geometry, + 0, 1, 0, + doc: /* Return geometric attributes of FRAME. +FRAME must be a live frame and defaults to the selected one. The return +value is an association list of the attributes listed below. All height +and width values are in pixels. + +`outer-position' is a cons of the outer left and top edges of FRAME + relative to the origin - the position (0, 0) - of FRAME's display. + +`outer-size' is a cons of the outer width and height of FRAME. The + outer size includes the title bar and the external borders as well as + any menu and/or tool bar of frame. + +`external-border-size' is a cons of the horizontal and vertical width of + FRAME's external borders as supplied by the window manager. + +`title-bar-size' is a cons of the width and height of the title bar of + FRAME as supplied by the window manager. If both of them are zero, + FRAME has no title bar. If only the width is zero, Emacs was not + able to retrieve the width information. + +`menu-bar-external', if non-nil, means the menu bar is external (never + included in the inner edges of FRAME). + +`menu-bar-size' is a cons of the width and height of the menu bar of + FRAME. + +`tool-bar-external', if non-nil, means the tool bar is external (never + included in the inner edges of FRAME). + +`tool-bar-position' tells on which side the tool bar on FRAME is and can + be one of `left', `top', `right' or `bottom'. If this is nil, FRAME + has no tool bar. + +`tool-bar-size' is a cons of the width and height of the tool bar of + FRAME. + +`internal-border-width' is the width of the internal border of + FRAME. */) + (Lisp_Object frame) { #ifdef ANDROID_STUBIFY error ("Android cross-compilation stub called!"); return Qnil; #else - error ("Not implemented"); + return frame_geometry (frame, Qnil); +#endif +} + +DEFUN ("android-frame-edges", Fandroid_frame_edges, Sandroid_frame_edges, 0, 2, 0, + doc: /* Return edge coordinates of FRAME. +FRAME must be a live frame and defaults to the selected one. The return +value is a list of the form (LEFT, TOP, RIGHT, BOTTOM). All values are +in pixels relative to the origin - the position (0, 0) - of FRAME's +display. + +If optional argument TYPE is the symbol `outer-edges', return the outer +edges of FRAME. The outer edges comprise the decorations of the window +manager (like the title bar or external borders) as well as any external +menu or tool bar of FRAME. If optional argument TYPE is the symbol +`native-edges' or nil, return the native edges of FRAME. The native +edges exclude the decorations of the window manager and any external +menu or tool bar of FRAME. If TYPE is the symbol `inner-edges', return +the inner edges of FRAME. These edges exclude title bar, any borders, +menu bar or tool bar of FRAME. */) + (Lisp_Object frame, Lisp_Object type) +{ +#ifndef ANDROID_STUBIFY + return frame_geometry (frame, ((EQ (type, Qouter_edges) + || EQ (type, Qinner_edges)) + ? type + : Qnative_edges)); +#else return Qnil; #endif } -DEFUN ("x-frame-list-z-order", Fx_frame_list_z_order, - Sx_frame_list_z_order, 0, 1, 0, - doc: /* SKIP: real doc in xfns.c. */) +#ifndef ANDROID_STUBIFY + +static Lisp_Object +android_frame_list_z_order (struct android_display_info *dpyinfo, + android_window window) +{ + android_window root, parent, *children; + unsigned int nchildren; + unsigned long i; + Lisp_Object frames; + + frames = Qnil; + + if (android_query_tree (window, &root, &parent, + &children, &nchildren)) + { + for (i = 0; i < nchildren; i++) + { + Lisp_Object frame, tail; + + FOR_EACH_FRAME (tail, frame) + { + struct frame *cf = XFRAME (frame); + + if (FRAME_ANDROID_P (cf) + && (FRAME_ANDROID_WINDOW (cf) == children[i])) + frames = Fcons (frame, frames); + } + } + + if (children) + xfree (children); + } + + return frames; +} + +#endif + +DEFUN ("android-frame-list-z-order", Fandroid_frame_list_z_order, + Sandroid_frame_list_z_order, 0, 1, 0, + doc: /* Return list of Emacs' frames, in Z (stacking) order. +The optional argument TERMINAL specifies which display to ask about. +TERMINAL should be either a frame or a display name (a string). If +omitted or nil, that stands for the selected frame's display. Return +nil if TERMINAL contains no Emacs frame. + +As a special case, if TERMINAL is non-nil and specifies a live frame, +return the child frames of that frame in Z (stacking) order. + +Frames are listed from topmost (first) to bottommost (last). + +On Android, the order of the frames returned is undefined unless +TERMINAL is a frame. */) (Lisp_Object terminal) { #ifdef ANDROID_STUBIFY error ("Android cross-compilation stub called!"); return Qnil; #else - error ("Not implemented"); - return Qnil; + struct android_display_info *dpyinfo; + android_window window; + + dpyinfo = check_android_display_info (terminal); + + if (FRAMEP (terminal) && FRAME_LIVE_P (XFRAME (terminal))) + window = FRAME_ANDROID_WINDOW (XFRAME (terminal)); + else + window = dpyinfo->root_window; + + return android_frame_list_z_order (dpyinfo, window); #endif } -DEFUN ("x-frame-restack", Fx_frame_restack, Sx_frame_restack, 2, 3, 0, - doc: /* SKIP: real doc in xfns.c. */) +DEFUN ("android-frame-restack", Fandroid_frame_restack, + Sandroid_frame_restack, 2, 3, 0, + doc: /* Restack FRAME1 below FRAME2. +This means that if both frames are visible and the display areas of +these frames overlap, FRAME2 (partially) obscures FRAME1. If optional +third argument ABOVE is non-nil, restack FRAME1 above FRAME2. This +means that if both frames are visible and the display areas of these +frames overlap, FRAME1 (partially) obscures FRAME2. + +This may be thought of as an atomic action performed in two steps: The +first step removes FRAME1's window-step window from the display. The +second step reinserts FRAME1's window below (above if ABOVE is true) +that of FRAME2. Hence the position of FRAME2 in its display's Z +\(stacking) order relative to all other frames excluding FRAME1 remains +unaltered. + +The Android system refuses to restack windows, so this does not +work. */) (Lisp_Object frame1, Lisp_Object frame2, Lisp_Object frame3) { #ifdef ANDROID_STUBIFY error ("Android cross-compilation stub called!"); return Qnil; #else - error ("Not implemented"); + /* This is not supported on Android because of limitations in the + platform that prevent ViewGroups from restacking + SurfaceViews. */ return Qnil; #endif } -DEFUN ("x-mouse-absolute-pixel-position", Fx_mouse_absolute_pixel_position, - Sx_mouse_absolute_pixel_position, 0, 0, 0, - doc: /* SKIP: real doc in xfns.c. */) +DEFUN ("android-mouse-absolute-pixel-position", + Fandroid_mouse_absolute_pixel_position, + Sandroid_mouse_absolute_pixel_position, 0, 0, 0, + doc: /* Return absolute position of mouse cursor in pixels. +The position is returned as a cons cell (X . Y) of the coordinates of +the mouse cursor position in pixels relative to a position (0, 0) of the +selected frame's display. This does not work on Android. */) (void) { - /* TODO: figure out how to implement this. */ + /* This cannot be implemented on Android. */ return Qnil; } -DEFUN ("x-set-mouse-absolute-pixel-position", - Fx_set_mouse_absolute_pixel_position, - Sx_set_mouse_absolute_pixel_position, 2, 2, 0, - doc: /* SKIP: real doc in xfns.c. */) +DEFUN ("android-set-mouse-absolute-pixel-position", + Fandroid_set_mouse_absolute_pixel_position, + Sandroid_set_mouse_absolute_pixel_position, 2, 2, 0, + doc: /* Move mouse pointer to a pixel position at (X, Y). The +coordinates X and Y are interpreted to start from the top-left corner +of the screen. This does not work on Android. */) (Lisp_Object x, Lisp_Object y) { - /* TODO: figure out how to implement this. */ + /* This cannot be implemented on Android. */ return Qnil; } @@ -1364,6 +1687,20 @@ DEFUN ("x-hide-tip", Fx_hide_tip, Sx_hide_tip, 0, 0, 0, #endif } +DEFUN ("android-detect-mouse", Fandroid_detect_mouse, + Sandroid_detect_mouse, 0, 0, 0, + doc: /* Figure out whether or not there is a mouse. +Return non-nil if a mouse is connected to this computer, and nil if +there is no mouse. */) + (void) +{ +#ifndef ANDROID_STUBIFY + return android_detect_mouse () ? Qt : Qnil; +#else + return Qnil; +#endif +} + #ifndef ANDROID_STUBIFY @@ -1579,7 +1916,7 @@ android_set_menu_bar_lines (struct frame *f, Lisp_Object value, y = FRAME_TOP_MARGIN_HEIGHT (f); block_input (); - android_clear_area (FRAME_ANDROID_WINDOW (f), + android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0, y, width, height); unblock_input (); } @@ -1590,7 +1927,7 @@ android_set_menu_bar_lines (struct frame *f, Lisp_Object value, height = nlines * FRAME_LINE_HEIGHT (f) - y; block_input (); - android_clear_area (FRAME_ANDROID_WINDOW (f), 0, y, + android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0, y, width, height); unblock_input (); } @@ -1682,6 +2019,30 @@ android_set_alpha (struct frame *f, Lisp_Object arg, Lisp_Object oldval) } } +static void +android_set_no_focus_on_map (struct frame *f, Lisp_Object new_value, + Lisp_Object old_value) +{ + if (!EQ (new_value, old_value)) + { + android_set_dont_focus_on_map (FRAME_ANDROID_WINDOW (f), + new_value); + FRAME_NO_FOCUS_ON_MAP (f) = !NILP (new_value); + } +} + +static void +android_set_no_accept_focus (struct frame *f, Lisp_Object new_value, + Lisp_Object old_value) +{ + if (!EQ (new_value, old_value)) + { + android_set_dont_accept_focus (FRAME_ANDROID_WINDOW (f), + new_value); + FRAME_NO_ACCEPT_FOCUS (f) = !NILP (new_value); + } +} + frame_parm_handler android_frame_parm_handlers[] = { gui_set_autoraise, @@ -1724,16 +2085,16 @@ frame_parm_handler android_frame_parm_handlers[] = NULL, NULL, NULL, - NULL, /* x_set_undecorated, */ - NULL, /* x_set_parent_frame, */ - NULL, /* x_set_skip_taskbar, */ - NULL, /* x_set_no_focus_on_map, */ - NULL, /* x_set_no_accept_focus, */ - NULL, /* x_set_z_group, */ - NULL, /* x_set_override_redirect, */ + NULL, + android_set_parent_frame, + NULL, + android_set_no_focus_on_map, + android_set_no_accept_focus, + NULL, + NULL, gui_set_no_special_glyphs, - NULL, /* x_set_alpha_background, */ - NULL, /* x_set_use_frame_synchronization, */ + NULL, + NULL, }; #endif @@ -1766,14 +2127,16 @@ syms_of_androidfns (void) defsubr (&Sx_display_mm_height); defsubr (&Sx_display_backing_store); defsubr (&Sx_display_visual_class); - defsubr (&Sx_display_monitor_attributes_list); - defsubr (&Sx_frame_geometry); - defsubr (&Sx_frame_list_z_order); - defsubr (&Sx_frame_restack); - defsubr (&Sx_mouse_absolute_pixel_position); - defsubr (&Sx_set_mouse_absolute_pixel_position); + defsubr (&Sandroid_display_monitor_attributes_list); + defsubr (&Sandroid_frame_geometry); + defsubr (&Sandroid_frame_edges); + defsubr (&Sandroid_frame_list_z_order); + defsubr (&Sandroid_frame_restack); + defsubr (&Sandroid_mouse_absolute_pixel_position); + defsubr (&Sandroid_set_mouse_absolute_pixel_position); defsubr (&Sandroid_get_connection); defsubr (&Sx_display_list); defsubr (&Sx_show_tip); defsubr (&Sx_hide_tip); + defsubr (&Sandroid_detect_mouse); } diff --git a/src/androidfont.c b/src/androidfont.c index b2c83201b06..b0a9f994a85 100644 --- a/src/androidfont.c +++ b/src/androidfont.c @@ -1,4 +1,4 @@ -/* Communication module for Android terminals. +/* Android fallback font driver. Copyright (C) 2023 Free Software Foundation, Inc. @@ -17,6 +17,10 @@ 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 . */ +/* Due to the terrible nature of the Android Typeface subsystems, this + font driver is only used as a fallback when sfntfont-android.c + fails to enumerate any fonts at all. */ + #include #include "lisp.h" @@ -636,7 +640,7 @@ androidfont_draw (struct glyph_string *s, int from, int to, gcontext = android_resolve_handle (s->gc->gcontext, ANDROID_HANDLE_GCONTEXT); - drawable = android_resolve_handle (FRAME_ANDROID_WINDOW (s->f), + drawable = android_resolve_handle (FRAME_ANDROID_DRAWABLE (s->f), ANDROID_HANDLE_WINDOW); chars = (*android_java_env)->NewIntArray (android_java_env, to - from); diff --git a/src/androidgui.h b/src/androidgui.h index 7d045f7b450..422e72408c7 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -222,6 +222,12 @@ enum android_event_type ANDROID_MOTION_NOTIFY, ANDROID_BUTTON_PRESS, ANDROID_BUTTON_RELEASE, + ANDROID_TOUCH_DOWN, + ANDROID_TOUCH_UP, + ANDROID_TOUCH_MOVE, + ANDROID_WHEEL, + ANDROID_ICONIFIED, + ANDROID_DEICONIFIED, }; struct android_any_event @@ -312,6 +318,54 @@ struct android_button_event unsigned int button; }; +struct android_touch_event +{ + /* Type of the event. */ + enum android_event_type type; + + /* Window associated with the event. */ + android_window window; + + /* X and Y coordinates of the event. */ + int x, y; + + /* Time of the event, and the pointer identifier. */ + unsigned long time; + + /* Index of the pointer being tracked. */ + unsigned int pointer_id; +}; + +struct android_wheel_event +{ + /* Type of the event. */ + enum android_event_type type; + + /* Window associated with the event. */ + android_window window; + + /* X and Y coordinates of the event. */ + int x, y; + + /* Time of the event, and the pointer identifier. */ + unsigned long time; + + /* Modifier state at the time of the event. */ + int state; + + /* Motion alongside the X and Y axes. */ + double x_delta, y_delta; +}; + +struct android_iconify_event +{ + /* Type of the event. */ + enum android_event_type type; + + /* Window associated with the event. */ + android_window window; +}; + union android_event { enum android_event_type type; @@ -323,8 +377,26 @@ union android_event struct android_crossing_event xcrossing; struct android_motion_event xmotion; struct android_button_event xbutton; + + /* This has no parallel in X, since the X model of having + monotonically increasing touch IDs can't work on Android. */ + struct android_touch_event touch; + + /* This has no parallel in X outside the X Input Extension, and + emulating the input extension interface would be awfully + complicated. */ + struct android_wheel_event wheel; + + /* This has no parallel in X because Android doesn't have window + properties. */ + struct android_iconify_event iconified; }; +enum + { + ANDROID_CURRENT_TIME = 0L, + }; + extern int android_pending (void); extern void android_next_event (union android_event *); @@ -396,6 +468,17 @@ extern void android_clear_area (android_window, int, int, unsigned int, extern android_pixmap android_create_bitmap_from_data (char *, unsigned int, unsigned int); +extern void android_bell (void); +extern void android_set_input_focus (android_window, unsigned long); +extern void android_raise_window (android_window); +extern void android_lower_window (android_window); +extern int android_query_tree (android_window, android_window *, + android_window *, android_window **, + unsigned int *); +extern void android_get_geometry (android_window, android_window *, + int *, int *, unsigned int *, + unsigned int *, unsigned int *); + #endif diff --git a/src/androidterm.c b/src/androidterm.c index 05fe7f01bf9..220858c0fec 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -19,6 +19,7 @@ along with GNU Emacs. If not, see . */ #include #include +#include #include "lisp.h" #include "androidterm.h" @@ -46,6 +47,11 @@ struct android_display_info *x_display_list; static bool any_help_event_p; +/* Counters for tallying up scroll wheel events if + mwheel_coalesce_scroll_events is true. */ + +static double wheel_event_x, wheel_event_y; + enum { ANDROID_EVENT_NORMAL, @@ -83,19 +89,133 @@ android_clear_frame (struct frame *f) /* Clearing the frame will erase any cursor, so mark them all as no longer visible. */ mark_window_cursors_off (XWINDOW (FRAME_ROOT_WINDOW (f))); - android_clear_window (FRAME_ANDROID_WINDOW (f)); + android_clear_window (FRAME_ANDROID_DRAWABLE (f)); +} + +static void +android_flash (struct frame *f) +{ + struct android_gc *gc; + struct android_gc_values values; + int rc; + fd_set fds; + + block_input (); + + values.function = ANDROID_GC_XOR; + values.foreground = (FRAME_FOREGROUND_PIXEL (f) + ^ FRAME_BACKGROUND_PIXEL (f)); + + gc = android_create_gc ((ANDROID_GC_FUNCTION + | ANDROID_GC_FOREGROUND), + &values); + + /* Get the height not including a menu bar widget. */ + int height = FRAME_PIXEL_HEIGHT (f); + /* Height of each line to flash. */ + int flash_height = FRAME_LINE_HEIGHT (f); + /* These will be the left and right margins of the rectangles. */ + int flash_left = FRAME_INTERNAL_BORDER_WIDTH (f); + int flash_right = FRAME_PIXEL_WIDTH (f) - FRAME_INTERNAL_BORDER_WIDTH (f); + int width = flash_right - flash_left; + + /* If window is tall, flash top and bottom line. */ + if (height > 3 * FRAME_LINE_HEIGHT (f)) + { + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, + flash_left, + (FRAME_INTERNAL_BORDER_WIDTH (f) + + FRAME_TOP_MARGIN_HEIGHT (f)), + width, flash_height); + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, + flash_left, + (height - flash_height + - FRAME_INTERNAL_BORDER_WIDTH (f)), + width, flash_height); + + } + else + /* If it is short, flash it all. */ + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, + flash_left, FRAME_INTERNAL_BORDER_WIDTH (f), + width, (height - 2 + * FRAME_INTERNAL_BORDER_WIDTH (f))); + + flush_frame (f); + + struct timespec delay = make_timespec (0, 150 * 1000 * 1000); + struct timespec wakeup = timespec_add (current_timespec (), delay); + + /* Keep waiting until past the time wakeup or any input gets + available. */ + while (! detect_input_pending ()) + { + struct timespec current = current_timespec (); + struct timespec timeout; + + /* Break if result would not be positive. */ + if (timespec_cmp (wakeup, current) <= 0) + break; + + /* How long `select' should wait. */ + timeout = make_timespec (0, 10 * 1000 * 1000); + + /* Wait for some input to become available on the X + connection. */ + FD_ZERO (&fds); + + /* Try to wait that long--but we might wake up sooner. */ + rc = pselect (0, &fds, NULL, NULL, &timeout, NULL); + + /* Some input is available, exit the visible bell. */ + if (rc >= 0) + break; + } + + /* If window is tall, flash top and bottom line. */ + if (height > 3 * FRAME_LINE_HEIGHT (f)) + { + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, + flash_left, + (FRAME_INTERNAL_BORDER_WIDTH (f) + + FRAME_TOP_MARGIN_HEIGHT (f)), + width, flash_height); + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, + flash_left, + (height - flash_height + - FRAME_INTERNAL_BORDER_WIDTH (f)), + width, flash_height); + } + else + /* If it is short, flash it all. */ + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, + flash_left, FRAME_INTERNAL_BORDER_WIDTH (f), + width, (height - 2 + * FRAME_INTERNAL_BORDER_WIDTH (f))); + + android_free_gc (gc); + flush_frame (f); + + unblock_input (); } static void android_ring_bell (struct frame *f) { - /* TODO */ + if (visible_bell) + android_flash (f); + else + { + block_input (); + android_bell (); + unblock_input (); + } } static void android_toggle_invisible_pointer (struct frame *f, bool invisible) { - /* TODO */ + } /* Start an update of frame F. This function is installed as a hook @@ -127,10 +247,17 @@ show_back_buffer (struct frame *f) { struct android_swap_info swap_info; + /* Somehow Android frames can be swapped while garbaged. */ + if (FRAME_GARBAGED_P (f)) + return; + memset (&swap_info, 0, sizeof (swap_info)); swap_info.swap_window = FRAME_ANDROID_WINDOW (f); swap_info.swap_action = ANDROID_COPIED; android_swap_buffers (&swap_info, 1); + + /* Now the back buffer no longer needs to be flipped. */ + FRAME_ANDROID_NEED_BUFFER_FLIP (f) = false; } /* Flip back buffers on F if it has undrawn content. */ @@ -142,7 +269,8 @@ android_flush_dirty_back_buffer_on (struct frame *f) || buffer_flipping_blocked_p () /* If the frame is not already up to date, do not flush buffers on input, as that will result in flicker. */ - || !FRAME_ANDROID_COMPLETE_P (f)) + || !FRAME_ANDROID_COMPLETE_P (f) + && FRAME_ANDROID_NEED_BUFFER_FLIP (f)) return; show_back_buffer (f); @@ -174,13 +302,13 @@ static void android_frame_rehighlight (struct android_display_info *); static void android_lower_frame (struct frame *f) { - /* TODO. */ + android_lower_window (FRAME_ANDROID_WINDOW (f)); } static void android_raise_frame (struct frame *f) { - /* TODO. */ + android_raise_window (FRAME_ANDROID_WINDOW (f)); } static void @@ -354,6 +482,46 @@ android_construct_mouse_click (struct input_event *result, return Qnil; } +/* Generate a TOUCHSCREEN_UPDATE_EVENT for all pressed tools in FRAME. + Return the event in IE. Do not set IE->timestamp, as that is left + to the caller. */ + +static void +android_update_tools (struct frame *f, struct input_event *ie) +{ + struct android_touch_point *touchpoint; + + ie->kind = TOUCHSCREEN_UPDATE_EVENT; + XSETFRAME (ie->frame_or_window, f); + ie->arg = Qnil; + + /* Build the list of active touches. */ + for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points; + touchpoint; touchpoint = touchpoint->next) + ie->arg = Fcons (list3i (touchpoint->x, + touchpoint->y, + touchpoint->tool_id), + ie->arg); +} + +/* Find and return an existing tool pressed against FRAME, identified + by POINTER_ID. Return NULL if no tool by that ID was found. */ + +static struct android_touch_point * +android_find_tool (struct frame *f, int pointer_id) +{ + struct android_touch_point *touchpoint; + + for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points; + touchpoint; touchpoint = touchpoint->next) + { + if (touchpoint->tool_id == pointer_id) + return touchpoint; + } + + return NULL; +} + static int handle_one_android_event (struct android_display_info *dpyinfo, union android_event *event, int *finish, @@ -364,6 +532,10 @@ handle_one_android_event (struct android_display_info *dpyinfo, Mouse_HLInfo *hlinfo; union buffered_input_event inev; int modifiers, count, do_help; + struct android_touch_point *touchpoint, **last; + Lisp_Object window; + int scroll_height; + double scroll_unit; /* It is okay for this to not resemble handle_one_xevent so much. Differences in event handling code are much less nasty than @@ -633,6 +805,28 @@ handle_one_android_event (struct android_display_info *dpyinfo, f = mouse_or_wdesc_frame (dpyinfo, event->xbutton.window); + if (f && event->xbutton.type == ANDROID_BUTTON_PRESS + && !popup_activated () + /* && !x_window_to_scroll_bar (event->xbutton.display, */ + /* event->xbutton.window, 2) */ + && !FRAME_NO_ACCEPT_FOCUS (f)) + { + /* When clicking into a child frame or when clicking + into a parent frame with the child frame selected and + `no-accept-focus' is not set, select the clicked + frame. */ + struct frame *hf = dpyinfo->highlight_frame; + + if (FRAME_PARENT_FRAME (f) || (hf && frame_ancestor_p (f, hf))) + { + android_set_input_focus (FRAME_ANDROID_WINDOW (f), + event->xbutton.time); + + if (FRAME_PARENT_FRAME (f)) + android_raise_window (FRAME_ANDROID_WINDOW (f)); + } + } + if (f) { /* Is this in the tab-bar? */ @@ -715,6 +909,223 @@ handle_one_android_event (struct android_display_info *dpyinfo, goto OTHER; + /* Touch events. The events here don't parallel X so much. */ + case ANDROID_TOUCH_DOWN: + + if (!any) + goto OTHER; + + /* This event is sent when a tool is put on the screen. X and Y + are the location of the finger, and pointer_id identifies the + tool for as long as it is still held down. First, see if the + touch point already exists and can be reused (this shouldn't + happen, but be safe.) */ + + touchpoint = android_find_tool (any, event->touch.pointer_id); + + if (touchpoint) + { + /* Simply update the tool position and send an update. */ + touchpoint->x = event->touch.x; + touchpoint->y = event->touch.x; + android_update_tools (any, &inev.ie); + inev.ie.timestamp = event->touch.time; + + goto OTHER; + } + + /* Otherwise, link a new touchpoint onto the output's list of + pressed tools. */ + + touchpoint = xmalloc (sizeof *touchpoint); + touchpoint->tool_id = event->touch.pointer_id; + touchpoint->x = event->touch.x; + touchpoint->y = event->touch.x; + touchpoint->next = FRAME_OUTPUT_DATA (any)->touch_points; + FRAME_OUTPUT_DATA (any)->touch_points = touchpoint; + + /* Now generate the Emacs event. */ + inev.ie.kind = TOUCHSCREEN_BEGIN_EVENT; + inev.ie.timestamp = event->touch.time; + XSETFRAME (inev.ie.frame_or_window, any); + XSETINT (inev.ie.x, event->touch.x); + XSETINT (inev.ie.y, event->touch.y); + XSETINT (inev.ie.arg, event->touch.pointer_id); + + goto OTHER; + + case ANDROID_TOUCH_MOVE: + + if (!any) + goto OTHER; + + /* Look for the tool that moved. */ + + touchpoint = android_find_tool (any, event->touch.pointer_id); + + /* If it doesn't exist, skip processing this event. */ + + if (!touchpoint) + goto OTHER; + + /* Otherwise, update the position and send the update event. */ + + touchpoint->x = event->touch.x; + touchpoint->y = event->touch.y; + android_update_tools (any, &inev.ie); + inev.ie.timestamp = event->touch.time; + + goto OTHER; + + case ANDROID_TOUCH_UP: + + if (!any) + goto OTHER; + + /* Now find and unlink the tool in question. */ + + last = &FRAME_OUTPUT_DATA (any)->touch_points; + while ((touchpoint = *last)) + { + if (touchpoint->tool_id == event->touch.pointer_id) + { + *last = touchpoint->next; + + /* The tool was unlinked. Free it and generate the + appropriate Emacs event. */ + xfree (touchpoint); + inev.ie.kind = TOUCHSCREEN_END_EVENT; + inev.ie.timestamp = event->touch.time; + + XSETFRAME (inev.ie.frame_or_window, any); + XSETINT (inev.ie.x, event->touch.x); + XSETINT (inev.ie.y, event->touch.y); + XSETINT (inev.ie.arg, event->touch.pointer_id); + + /* Break out of the loop. */ + goto OTHER; + } + else + last = &touchpoint->next; + } + + /* No touch point was found. This shouldn't happen. */ + goto OTHER; + + /* Wheel motion. The events here don't parallel X because + Android doesn't have scroll valuators. */ + + case ANDROID_WHEEL: + + if (!any) + goto OTHER; + + if (fabs (event->wheel.x_delta) > 0 + || fabs (event->wheel.y_delta) > 0) + { + if (mwheel_coalesce_scroll_events) + { + if (signbit (event->wheel.x_delta) + != signbit (wheel_event_x)) + wheel_event_x = 0.0; + + if (signbit (event->wheel.y_delta) + != signbit (wheel_event_y)) + wheel_event_y = 0.0; + + /* Tally up deltas until one of them exceeds 1.0. */ + wheel_event_x += event->wheel.x_delta; + wheel_event_y += event->wheel.y_delta; + + if (fabs (wheel_event_x) < 1.0 + && fabs (wheel_event_y) < 1.0) + goto OTHER; + } + else + { + /* Use the deltas in the event. */ + wheel_event_x = event->wheel.x_delta; + wheel_event_y = event->wheel.y_delta; + } + + /* Determine what kind of event to send. */ + inev.ie.kind = ((fabs (wheel_event_y) + >= fabs (wheel_event_x)) + ? WHEEL_EVENT : HORIZ_WHEEL_EVENT); + inev.ie.timestamp = event->wheel.time; + + /* Set the event coordinates. */ + XSETINT (inev.ie.x, event->wheel.x); + XSETINT (inev.ie.y, event->wheel.y); + + /* Set the frame. */ + XSETFRAME (inev.ie.frame_or_window, any); + + /* Figure out the scroll direction. */ + inev.ie.modifiers = (signbit ((fabs (wheel_event_x) + >= fabs (wheel_event_y)) + ? wheel_event_x + : wheel_event_y) + ? down_modifier : up_modifier); + + /* Figure out how much to scale the deltas by. */ + window = window_from_coordinates (any, event->wheel.x, + event->wheel.y, NULL, + false, false); + + if (WINDOWP (window)) + scroll_height = XWINDOW (window)->pixel_height; + else + /* EVENT_X and EVENT_Y can be outside the + frame if F holds the input grab, so fall + back to the height of the frame instead. */ + scroll_height = FRAME_PIXEL_HEIGHT (any); + + scroll_unit = pow (scroll_height, 2.0 / 3.0); + + /* Add the keyboard modifiers. */ + inev.ie.modifiers + |= android_android_to_emacs_modifiers (dpyinfo, + event->wheel.state); + + /* Finally include the scroll deltas. */ + inev.ie.arg = list3 (Qnil, + make_float (wheel_event_x + * scroll_unit), + make_float (wheel_event_y + * scroll_unit)); + + wheel_event_x = 0.0; + wheel_event_y = 0.0; + } + + goto OTHER; + + /* Iconification. This is vastly simpler than on X. */ + case ANDROID_ICONIFIED: + + if (FRAME_ICONIFIED_P (any)) + goto OTHER; + + SET_FRAME_VISIBLE (any, false); + SET_FRAME_ICONIFIED (any, true); + + inev.ie.kind = ICONIFY_EVENT; + XSETFRAME (inev.ie.frame_or_window, any); + goto OTHER; + + case ANDROID_DEICONIFIED: + + if (!FRAME_ICONIFIED_P (any)) + goto OTHER; + + SET_FRAME_VISIBLE (any, true); + SET_FRAME_ICONIFIED (any, false); + + inev.ie.kind = DEICONIFY_EVENT; + XSETFRAME (inev.ie.frame_or_window, any); + goto OTHER; + default: goto OTHER; } @@ -781,8 +1192,7 @@ android_read_socket (struct terminal *terminal, now. */ if (dpyinfo->pending_autoraise_frame) { - /* android_raise_frame (dpyinfo->pending_autoraise_frame); - TODO */ + android_raise_frame (dpyinfo->pending_autoraise_frame); dpyinfo->pending_autoraise_frame = NULL; } @@ -796,7 +1206,8 @@ android_frame_up_to_date (struct frame *f) block_input (); FRAME_MOUSE_UPDATE (f); - if (!buffer_flipping_blocked_p ()) + if (!buffer_flipping_blocked_p () + && FRAME_ANDROID_NEED_BUFFER_FLIP (f)) show_back_buffer (f); /* The frame is now complete, as its contents have been drawn. */ @@ -808,7 +1219,10 @@ static void android_buffer_flipping_unblocked_hook (struct frame *f) { block_input (); - show_back_buffer (f); + + if (FRAME_ANDROID_NEED_BUFFER_FLIP (f)) + show_back_buffer (f); + unblock_input (); } @@ -935,7 +1349,25 @@ android_get_focus_frame (struct frame *f) static void android_focus_frame (struct frame *f, bool noactivate) { - /* TODO */ + /* Set the input focus to the frame's window. The system only lets + this work on child frames. */ + android_set_input_focus (FRAME_ANDROID_WINDOW (f), + ANDROID_CURRENT_TIME); +} + +/* The two procedures below only have to update the cursor on Android, + as there are no window borders there. */ + +static void +android_frame_highlight (struct frame *f) +{ + gui_update_cursor (f, true); +} + +static void +android_frame_unhighlight (struct frame *f) +{ + gui_update_cursor (f, true); } static void @@ -963,12 +1395,10 @@ android_frame_rehighlight (struct android_display_info *dpyinfo) 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 } } @@ -1027,7 +1457,8 @@ android_fullscreen_hook (struct frame *f) void android_iconify_frame (struct frame *f) { - /* TODO */ + /* This really doesn't work on Android. */ + error ("Can't notify window manager of iconification"); } static void @@ -1149,7 +1580,7 @@ android_set_offset (struct frame *f, int xoff, int yoff, static void android_set_alpha (struct frame *f) { - /* TODO */ + /* Not supported on Android. */ } static Lisp_Object @@ -1215,6 +1646,7 @@ android_free_frame_resources (struct frame *f) { struct android_display_info *dpyinfo; Mouse_HLInfo *hlinfo; + struct android_touch_point *last, *next; dpyinfo = FRAME_DISPLAY_INFO (f); hlinfo = &dpyinfo->mouse_highlight; @@ -1256,6 +1688,18 @@ android_free_frame_resources (struct frame *f) if (f == dpyinfo->last_mouse_frame) dpyinfo->last_mouse_frame = NULL; + /* Free all tool presses currently active on this frame. */ + next = FRAME_OUTPUT_DATA (f)->touch_points; + while (next) + { + last = next; + next = next->next; + xfree (last); + } + + /* Clear this in case unblock_input reads events. */ + FRAME_OUTPUT_DATA (f)->touch_points = NULL; + unblock_input (); } @@ -1316,8 +1760,8 @@ android_scroll_run (struct window *w, struct run *run) /* Cursor off. Will be switched on again in gui_update_window_end. */ gui_clear_cursor (w); - android_copy_area (FRAME_ANDROID_WINDOW (f), - FRAME_ANDROID_WINDOW (f), + android_copy_area (FRAME_ANDROID_DRAWABLE (f), + FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x, from_y, width, height, x, to_y); @@ -1337,7 +1781,9 @@ static void android_flip_and_flush (struct frame *f) { block_input (); - show_back_buffer (f); + + if (FRAME_ANDROID_NEED_BUFFER_FLIP (f)) + show_back_buffer (f); /* The frame is complete again as its contents were just flushed. */ @@ -1355,7 +1801,7 @@ android_clear_rectangle (struct frame *f, struct android_gc *gc, int x, | ANDROID_GC_FOREGROUND), &xgcv); android_set_foreground (gc, xgcv.background); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x, y, width, height); android_set_foreground (gc, xgcv.foreground); } @@ -1405,7 +1851,7 @@ android_draw_fringe_bitmap (struct window *w, struct glyph_row *row, if (face->stipple) { android_set_fill_style (face->gc, ANDROID_FILL_OPAQUE_STIPPLED); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), face->gc, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), face->gc, p->bx, p->by, p->nx, p->ny); android_set_fill_style (face->gc, ANDROID_FILL_SOLID); @@ -1428,7 +1874,7 @@ android_draw_fringe_bitmap (struct window *w, struct glyph_row *row, unsigned long background, cursor_pixel; int depth; - drawable = FRAME_ANDROID_WINDOW (f); + drawable = FRAME_ANDROID_DRAWABLE (f); clipmask = ANDROID_NONE; background = face->background; cursor_pixel = f->output_data.android->cursor_pixel; @@ -1697,7 +2143,7 @@ android_draw_glyph_string_background (struct glyph_string *s, bool force_p) { /* Fill background with a stipple pattern. */ android_set_fill_style (s->gc, ANDROID_FILL_OPAQUE_STIPPLED); - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, s->x, s->y + box_line_width, s->background_width, s->height - 2 * box_line_width); @@ -1734,7 +2180,7 @@ android_fill_triangle (struct frame *f, struct android_gc *gc, abc[1] = point2; abc[2] = point3; - android_fill_polygon (FRAME_ANDROID_WINDOW (f), + android_fill_polygon (FRAME_ANDROID_DRAWABLE (f), gc, abc, 3, ANDROID_CONVEX, ANDROID_COORD_MODE_ORIGIN); } @@ -1776,7 +2222,7 @@ android_clear_point (struct frame *f, struct android_gc *gc, android_get_gc_values (gc, ANDROID_GC_BACKGROUND | ANDROID_GC_FOREGROUND, &xgcv); android_set_foreground (gc, xgcv.background); - android_draw_point (FRAME_ANDROID_WINDOW (f), gc, x, y); + android_draw_point (FRAME_ANDROID_DRAWABLE (f), gc, x, y); android_set_foreground (gc, xgcv.foreground); } @@ -1798,7 +2244,7 @@ android_draw_relief_rect (struct frame *f, int left_x, int top_y, int right_x, black_gc = f->output_data.android->black_relief.gc; normal_gc = f->output_data.android->normal_gc; - drawable = FRAME_ANDROID_WINDOW (f); + drawable = FRAME_ANDROID_DRAWABLE (f); android_set_clip_rectangles (white_gc, 0, 0, clip_rect, 1); android_set_clip_rectangles (black_gc, 0, 0, clip_rect, 1); @@ -1811,11 +2257,11 @@ android_draw_relief_rect (struct frame *f, int left_x, int top_y, int right_x, /* Draw lines. */ if (top_p) - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, left_x, top_y, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x, top_y, right_x - left_x + 1, hwidth); if (left_p) - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, left_x, top_y, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x, top_y, vwidth, bottom_y - top_y + 1); if (raised_p) @@ -1824,12 +2270,12 @@ android_draw_relief_rect (struct frame *f, int left_x, int top_y, int right_x, gc = white_gc; if (bot_p) - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, left_x, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x, bottom_y - hwidth + 1, right_x - left_x + 1, hwidth); if (right_p) - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, right_x - vwidth + 1, top_y, vwidth, bottom_y - top_y + 1); @@ -1853,7 +2299,7 @@ android_draw_relief_rect (struct frame *f, int left_x, int top_y, int right_x, if (top_p && left_p && bot_p && right_p && hwidth > 1 && vwidth > 1) - android_draw_rectangle (FRAME_ANDROID_WINDOW (f), + android_draw_rectangle (FRAME_ANDROID_DRAWABLE (f), black_gc, left_x, top_y, right_x - left_x, bottom_y - top_y); else @@ -1913,22 +2359,22 @@ android_draw_box_rect (struct glyph_string *s, android_set_clip_rectangles (s->gc, 0, 0, clip_rect, 1); /* Top. */ - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, left_x, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x, left_x, right_x - left_x + 1, hwidth); /* Left. */ if (left_p) - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, left_x, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x, top_y, vwidth, bottom_y - top_y + 1); /* Bottom. */ - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, left_x, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x, bottom_y - hwidth + 1, right_x - left_x + 1, hwidth); /* Right. */ if (right_p) - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, right_x - vwidth + 1, top_y, vwidth, bottom_y - top_y + 1); @@ -2153,7 +2599,7 @@ android_draw_glyph_string_bg_rect (struct glyph_string *s, int x, int y, { /* Fill background with a stipple pattern. */ android_set_fill_style (s->gc, ANDROID_FILL_OPAQUE_STIPPLED); - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, x, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x, y, w, h); android_set_fill_style (s->gc, ANDROID_FILL_SOLID); } @@ -2294,7 +2740,7 @@ android_draw_image_foreground (struct glyph_string *s) if (gui_intersect_rectangles (&clip_rect, &image_rect, &r)) android_copy_area (s->img->pixmap, - FRAME_ANDROID_WINDOW (s->f), + FRAME_ANDROID_DRAWABLE (s->f), s->gc, s->slice.x + r.x - x, s->slice.y + r.y - y, r.width, r.height, r.x, r.y); @@ -2307,7 +2753,7 @@ android_draw_image_foreground (struct glyph_string *s) if (s->hl == DRAW_CURSOR && !s->img->mask) { int relief = eabs (s->img->relief); - android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, + android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x - relief, y - relief, s->slice.width + relief*2 - 1, s->slice.height + relief*2 - 1); @@ -2317,7 +2763,7 @@ android_draw_image_foreground (struct glyph_string *s) } else /* Draw a rectangle if image could not be loaded. */ - android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, x, y, + android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x, y, s->slice.width - 1, s->slice.height - 1); } @@ -2444,7 +2890,7 @@ android_draw_stretch_glyph_string (struct glyph_string *s) { /* Fill background with a stipple pattern. */ android_set_fill_style (gc, ANDROID_FILL_OPAQUE_STIPPLED); - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), gc, x, y, w, h); android_set_fill_style (gc, ANDROID_FILL_SOLID); @@ -2457,7 +2903,7 @@ android_draw_stretch_glyph_string (struct glyph_string *s) | ANDROID_GC_BACKGROUND), &xgcv); android_set_foreground (gc, xgcv.background); - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), gc, x, y, w, h); android_set_foreground (gc, xgcv.foreground); } @@ -2536,7 +2982,7 @@ android_draw_underwave (struct glyph_string *s, int decoration_width) while (x1 <= xmax) { - android_draw_line (FRAME_ANDROID_WINDOW (s->f), s->gc, + android_draw_line (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x1, y1, x2, y2); x1 = x2, y1 = y2; x2 += dx, y2 = y0 + odd*dy; @@ -2567,7 +3013,7 @@ android_draw_glyph_string_foreground (struct glyph_string *s) for (i = 0; i < s->nchars; ++i) { struct glyph *g = s->first_glyph + i; - android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f), + android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x, s->y, g->pixel_width - 1, s->height - 1); @@ -2618,7 +3064,7 @@ android_draw_composite_glyph_string_foreground (struct glyph_string *s) if (s->font_not_found_p) { if (s->cmp_from == 0) - android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f), + android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x, s->y, s->width - 1, s->height - 1); } @@ -2754,7 +3200,7 @@ android_draw_glyphless_glyph_string_foreground (struct glyph_string *s) false); } if (glyph->u.glyphless.method != GLYPHLESS_DISPLAY_THIN_SPACE) - android_draw_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, + android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x, s->ybase - glyph->ascent, glyph->pixel_width - 1, glyph->ascent + glyph->descent - 1); @@ -2987,14 +3433,14 @@ android_draw_glyph_string (struct glyph_string *s) s->underline_position = position; y = s->ybase + position; if (s->face->underline_defaulted_p) - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, s->x, y, decoration_width, thickness); else { struct android_gc_values xgcv; android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv); android_set_foreground (s->gc, s->face->underline_color); - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, s->x, y, decoration_width, thickness); android_set_foreground (s->gc, xgcv.foreground); } @@ -3006,7 +3452,7 @@ android_draw_glyph_string (struct glyph_string *s) unsigned long dy = 0, h = 1; if (s->face->overline_color_defaulted_p) - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, s->x, s->y + dy, decoration_width, h); else @@ -3014,8 +3460,8 @@ android_draw_glyph_string (struct glyph_string *s) struct android_gc_values xgcv; android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv); android_set_foreground (s->gc, s->face->overline_color); - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, s->x, - s->y + dy, decoration_width, h); + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, + s->x, s->y + dy, decoration_width, h); android_set_foreground (s->gc, xgcv.foreground); } } @@ -3044,8 +3490,9 @@ android_draw_glyph_string (struct glyph_string *s) struct android_gc_values xgcv; android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv); android_set_foreground (s->gc, s->face->strike_through_color); - android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f), s->gc, s->x, - glyph_y + dy, decoration_width, h); + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, + s->x, glyph_y + dy, decoration_width, + h); android_set_foreground (s->gc, xgcv.foreground); } } @@ -3125,7 +3572,7 @@ static void android_clear_frame_area (struct frame *f, int x, int y, int width, int height) { - android_clear_area (FRAME_ANDROID_WINDOW (f), + android_clear_area (FRAME_ANDROID_DRAWABLE (f), x, y, width, height); } @@ -3154,25 +3601,25 @@ android_clear_under_internal_border (struct frame *f) struct android_gc *gc = f->output_data.android->normal_gc; android_set_foreground (gc, color); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, 0, margin, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0, margin, width, border); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, 0, 0, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0, 0, border, height); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, width - border, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, width - border, 0, border, height); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, 0, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0, height - border, width, border); android_set_foreground (gc, FRAME_FOREGROUND_PIXEL (f)); } else { - android_clear_area (FRAME_ANDROID_WINDOW (f), 0, 0, + android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0, 0, border, height); - android_clear_area (FRAME_ANDROID_WINDOW (f), 0, + android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0, margin, width, border); - android_clear_area (FRAME_ANDROID_WINDOW (f), width - border, + android_clear_area (FRAME_ANDROID_DRAWABLE (f), width - border, 0, border, height); - android_clear_area (FRAME_ANDROID_WINDOW (f), 0, + android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0, height - border, width, border); } } @@ -3221,7 +3668,7 @@ android_draw_hollow_cursor (struct window *w, struct glyph_row *row) } /* Set clipping, draw the rectangle, and reset clipping again. */ android_clip_to_row (w, row, TEXT_AREA, gc); - android_draw_rectangle (FRAME_ANDROID_WINDOW (f), gc, x, y, wd, h - 1); + android_draw_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x, y, wd, h - 1); android_reset_clip_rectangles (f, gc); } @@ -3295,7 +3742,7 @@ android_draw_bar_cursor (struct window *w, struct glyph_row *row, int width, if ((cursor_glyph->resolved_level & 1) != 0) x += cursor_glyph->pixel_width - width; - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, x, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x, WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y), width, row->height); } @@ -3318,7 +3765,7 @@ android_draw_bar_cursor (struct window *w, struct glyph_row *row, int width, if ((cursor_glyph->resolved_level & 1) != 0 && cursor_glyph->pixel_width > w->phys_cursor_width - 1) x += cursor_glyph->pixel_width - w->phys_cursor_width + 1; - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), gc, x, + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x, cursor_start_y, w->phys_cursor_width - 1, width); } @@ -3387,7 +3834,7 @@ android_draw_vertical_window_border (struct window *w, int x, int y0, int y1) android_set_foreground (f->output_data.android->normal_gc, face->foreground); - android_draw_line (FRAME_ANDROID_WINDOW (f), + android_draw_line (FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x, y0, x, y1); } @@ -3415,17 +3862,17 @@ android_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1) { android_set_foreground (f->output_data.android->normal_gc, color_first); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x0, y0, 1, y1 - y0); android_set_foreground (f->output_data.android->normal_gc, color); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x0 + 1, y0, x1 - x0 - 2, y1 - y0); android_set_foreground (f->output_data.android->normal_gc, color_last); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x1 - 1, y0, 1, y1 - y0); } @@ -3435,16 +3882,16 @@ android_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1) { android_set_foreground (f->output_data.android->normal_gc, color_first); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x0, y0, x1 - x0, 1); android_set_foreground (f->output_data.android->normal_gc, color); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x0, y0 + 1, x1 - x0, y1 - y0 - 2); android_set_foreground (f->output_data.android->normal_gc, color_last); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x0, y1 - 1, x1 - x0, 1); } @@ -3453,7 +3900,7 @@ android_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1) /* In any other case do not draw the first and last pixels differently. */ android_set_foreground (f->output_data.android->normal_gc, color); - android_fill_rectangle (FRAME_ANDROID_WINDOW (f), + android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), f->output_data.android->normal_gc, x0, y0, x1 - x0, y1 - y0); } diff --git a/src/androidterm.h b/src/androidterm.h index 814bcf9f08c..ebde15c40a8 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -134,6 +134,18 @@ struct android_display_info Time last_mouse_movement_time; }; +/* Structure representing a single tool (finger or stylus) pressed + onto a frame. */ + +struct android_touch_point +{ + /* The next tool on this list. */ + struct android_touch_point *next; + + /* The tool ID and the last known X and Y positions. */ + int tool_id, x, y; +}; + struct android_output { /* Graphics contexts for the default font. */ @@ -201,6 +213,10 @@ struct android_output input. */ bool_bf complete : 1; + /* True that indicates whether or not a buffer flip is required + because the frame contents have been dirtied. */ + bool_bf need_buffer_flip : 1; + /* Relief GCs, colors etc. */ struct relief { struct android_gc *gc; @@ -214,6 +230,10 @@ struct android_output /* Focus state. Only present for consistency with X; it is actually a boolean. */ int focus_state; + + /* List of all tools (either styluses or fingers) pressed onto the + frame. */ + struct android_touch_point *touch_points; }; enum @@ -240,6 +260,14 @@ enum #define FRAME_ANDROID_NEED_BUFFER_FLIP(f) \ ((f)->output_data.android->need_buffer_flip) +/* Return the drawable used for rendering to frame F and mark the + frame as needing a buffer flip later. There's no easy way to run + code after any drawing command, but code can be run whenever + someone asks for the handle necessary to draw. */ +#define FRAME_ANDROID_DRAWABLE(f) \ + (((f))->output_data.android->need_buffer_flip = true, \ + FRAME_ANDROID_WINDOW ((f))) + /* Return whether or not the frame F has been completely drawn. Used while handling async input. */ #define FRAME_ANDROID_COMPLETE_P(f) \ diff --git a/src/dired.c b/src/dired.c index 084f3225cb2..57d79b84463 100644 --- a/src/dired.c +++ b/src/dired.c @@ -44,6 +44,21 @@ along with GNU Emacs. If not, see . */ #include "msdos.h" /* for fstatat */ #endif +#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) +typedef DIR emacs_dir; +#define emacs_readdir readdir +#define emacs_closedir closedir +#else + +#include "android.h" + +/* The Android emulation of dirent stuff is required to be able to + list the /assets special directory. */ +typedef struct android_dir emacs_dir; +#define emacs_readdir android_readdir +#define emacs_closedir android_closedir +#endif + #ifdef WINDOWSNT extern int is_slow_fs (const char *); #endif @@ -78,19 +93,30 @@ dirent_type (struct dirent *dp) #endif } -static DIR * +static emacs_dir * open_directory (Lisp_Object dirname, Lisp_Object encoded_dirname, int *fdp) { char *name = SSDATA (encoded_dirname); - DIR *d; + emacs_dir *d; int fd, opendir_errno; -#ifdef DOS_NT - /* Directories cannot be opened. The emulation assumes that any - file descriptor other than AT_FDCWD corresponds to the most - recently opened directory. This hack is good enough for Emacs. */ +#if defined DOS_NT || (defined HAVE_ANDROID && !defined ANDROID_STUBIFY) + /* On DOS_NT, directories cannot be opened. The emulation assumes + that any file descriptor other than AT_FDCWD corresponds to the + most recently opened directory. This hack is good enough for + Emacs. + + This code is also used on Android for a different reason: a + special `assets' directory outside the normal file system is used + to open assets inside the Android application package, and must + be listed using the opendir-like interface provided in + android.h. */ fd = 0; +#ifndef HAVE_ANDROID d = opendir (name); +#else + d = android_opendir (name); +#endif opendir_errno = errno; #else fd = emacs_open (name, O_RDONLY | O_DIRECTORY, 0); @@ -125,7 +151,7 @@ directory_files_internal_w32_unwind (Lisp_Object arg) static void directory_files_internal_unwind (void *d) { - closedir (d); + emacs_closedir (d); } /* Return the next directory entry from DIR; DIR's name is DIRNAME. @@ -133,12 +159,12 @@ directory_files_internal_unwind (void *d) Signal any unrecoverable errors. */ static struct dirent * -read_dirent (DIR *dir, Lisp_Object dirname) +read_dirent (emacs_dir *dir, Lisp_Object dirname) { while (true) { errno = 0; - struct dirent *dp = readdir (dir); + struct dirent *dp = emacs_readdir (dir); if (dp || errno == 0) return dp; if (! (errno == EAGAIN || errno == EINTR)) @@ -190,7 +216,7 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full, Lisp_Object encoded_dirfilename = ENCODE_FILE (dirfilename); int fd; - DIR *d = open_directory (dirfilename, encoded_dirfilename, &fd); + emacs_dir *d = open_directory (dirfilename, encoded_dirfilename, &fd); /* Unfortunately, we can now invoke expand-file-name and file-attributes on filenames, both of which can throw, so we must @@ -300,7 +326,7 @@ directory_files_internal (Lisp_Object directory, Lisp_Object full, list = Fcons (attrs ? Fcons (finalname, fileattrs) : finalname, list); } - closedir (d); + emacs_closedir (d); #ifdef WINDOWSNT if (attrs) Vw32_get_true_file_attributes = w32_save; @@ -514,7 +540,7 @@ file_name_completion (Lisp_Object file, Lisp_Object dirname, bool all_flag, } } int fd; - DIR *d = open_directory (dirname, encoded_dir, &fd); + emacs_dir *d = open_directory (dirname, encoded_dir, &fd); record_unwind_protect_ptr (directory_files_internal_unwind, d); /* Loop reading directory entries. */ diff --git a/src/dispnew.c b/src/dispnew.c index 12814bc0b75..f31e185a84b 100644 --- a/src/dispnew.c +++ b/src/dispnew.c @@ -3175,6 +3175,7 @@ redraw_frame (struct frame *f) its redisplay done. */ mark_window_display_accurate (FRAME_ROOT_WINDOW (f), 0); set_window_update_flags (XWINDOW (FRAME_ROOT_WINDOW (f)), true); + f->garbaged = false; } @@ -6053,7 +6054,7 @@ FILE = nil means just close any termscript file currently open. */) if (tty->termscript != 0) { block_input (); - fclose (tty->termscript); + emacs_fclose (tty->termscript); tty->termscript = 0; unblock_input (); } diff --git a/src/fileio.c b/src/fileio.c index 9d81f0ca545..6fa524b3bb4 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -277,7 +277,7 @@ void fclose_unwind (void *arg) { FILE *stream = arg; - fclose (stream); + emacs_fclose (stream); } /* Restore point, having saved it as a marker. */ @@ -2989,6 +2989,12 @@ If there is no error, returns nil. */) encoded_filename = ENCODE_FILE (absname); +#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY + /* FILE may be some kind of special Android file. */ + if (android_file_access_p (SSDATA (encoded_filename), R_OK)) + return Qnil; +#endif + if (faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK, AT_EACCESS) != 0) report_file_error (SSDATA (string), filename); @@ -3205,7 +3211,11 @@ file_accessible_directory_p (Lisp_Object file) There are three exceptions: "", "/", and "//". Leave "" alone, as it's invalid. Append only "." to the other two exceptions as "/" and "//" are distinct on some platforms, whereas "/", "///", - "////", etc. are all equivalent. */ + "////", etc. are all equivalent. + + Android has a special directory named "/assets". There is no "." + directory there, but appending a "/" is sufficient to check + whether or not it is a directory. */ if (! len) dir = data; else @@ -3215,11 +3225,27 @@ file_accessible_directory_p (Lisp_Object file) special cases "/" and "//", and it's a safe optimization here. After appending '.', append another '/' to work around a macOS bug (Bug#30350). */ - static char const appended[] = "/./"; - char *buf = SAFE_ALLOCA (len + sizeof appended); - memcpy (buf, data, len); - strcpy (buf + len, &appended[data[len - 1] == '/']); - dir = buf; +#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY + if (!strncmp ("/assets/", data, + sizeof "/assets" - 1)) + { + static char const appended[] = "/"; + char *buf = SAFE_ALLOCA (len + sizeof appended); + memcpy (buf, data, len); + strcpy (buf + len, &appended[data[len - 1] == '/']); + dir = buf; + } + else + { +#endif + static char const appended[] = "/./"; + char *buf = SAFE_ALLOCA (len + sizeof appended); + memcpy (buf, data, len); + strcpy (buf + len, &appended[data[len - 1] == '/']); + dir = buf; +#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY + } +#endif } ok = file_access_p (dir, F_OK); @@ -5973,7 +5999,7 @@ do_auto_save_unwind (void *arg) if (stream != NULL) { block_input (); - fclose (stream); + emacs_fclose (stream); unblock_input (); } } diff --git a/src/keyboard.c b/src/keyboard.c index daca964b981..78637ef4f15 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4956,8 +4956,8 @@ const char *const lispy_function_keys[] = [66] = "return", [67] = "backspace", [82] = "menu", - [92] = "page-up", - [93] = "page-down", + [92] = "prior", + [93] = "next", }; #elif defined HAVE_NTGUI @@ -11219,7 +11219,7 @@ This may include sensitive information such as passwords. */) if (dribble) { block_input (); - fclose (dribble); + emacs_fclose (dribble); unblock_input (); dribble = 0; } diff --git a/src/lisp.h b/src/lisp.h index bf8ff5cdc18..f64a27ce113 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -28,6 +28,7 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include #ifdef HAVE_SYS_STAT_H #include @@ -5077,6 +5078,7 @@ extern int emacs_open (const char *, int, int); extern int emacs_open_noquit (const char *, int, int); extern int emacs_pipe (int[2]); extern int emacs_close (int); +extern int emacs_fclose (FILE *); extern ptrdiff_t emacs_read (int, void *, ptrdiff_t); extern ptrdiff_t emacs_read_quit (int, void *, ptrdiff_t); extern ptrdiff_t emacs_write (int, void const *, ptrdiff_t); diff --git a/src/lread.c b/src/lread.c index 2ac3c85ea81..d585dece392 100644 --- a/src/lread.c +++ b/src/lread.c @@ -1141,7 +1141,7 @@ close_infile_unwind (void *arg) { struct infile *prev_infile = arg; eassert (infile && infile != prev_infile); - fclose (infile->stream); + emacs_fclose (infile->stream); infile = prev_infile; } diff --git a/src/sfnt.c b/src/sfnt.c index 20cf2376d85..9b6c421212a 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -3072,8 +3072,8 @@ sfnt_curve_is_flat (struct sfnt_point control0, /* 2.0 is a constant describing the area covered at which point the curve is considered "flat". */ - return (abs (sfnt_mul_fixed (g.x, h.x) - - sfnt_mul_fixed (g.y, h.y)) + return (abs (sfnt_mul_fixed (g.x, h.y) + - sfnt_mul_fixed (g.y, h.x)) <= 0400000); } @@ -3261,9 +3261,11 @@ sfnt_prepare_raster (struct sfnt_raster *raster, struct sfnt_glyph_outline *outline) { raster->width - = sfnt_ceil_fixed (outline->xmax - outline->xmin) >> 16; + = (sfnt_ceil_fixed (outline->xmax) + - sfnt_floor_fixed (outline->xmin)) >> 16; raster->height - = sfnt_ceil_fixed (outline->ymax - outline->ymin) >> 16; + = (sfnt_ceil_fixed (outline->ymax) + - sfnt_floor_fixed (outline->ymin)) >> 16; raster->refcount = 0; /* Align the raster to a SFNT_POLY_ALIGNMENT byte boundary. */ @@ -3292,10 +3294,10 @@ sfnt_step_edge (struct sfnt_edge *edge) } /* Build a list of edges for each contour in OUTLINE, applying - OUTLINE->xmin and OUTLINE->ymin as the offset to each edge. Call - EDGE_PROC with DCONTEXT and the resulting edges as arguments. It - is OK to modify the edges given to EDGE_PROC. Align all edges to - the sub-pixel grid. */ + OUTLINE->xmin and floor (OUTLINE->ymin) as the offset to each edge. + Call EDGE_PROC with DCONTEXT and the resulting edges as arguments. + It is OK to modify the edges given to EDGE_PROC. Align all edges + to the sub-pixel grid. */ static void sfnt_build_outline_edges (struct sfnt_glyph_outline *outline, @@ -3303,13 +3305,18 @@ sfnt_build_outline_edges (struct sfnt_glyph_outline *outline, { struct sfnt_edge *edges; size_t i, edge, next_vertex; - sfnt_fixed dx, dy, bot, step_x; + sfnt_fixed dx, dy, bot, step_x, ymin, xmin; int inc_x; size_t top, bottom, y; edges = alloca (outline->outline_used * sizeof *edges); edge = 0; + /* ymin and xmin must be the same as the offset used to set offy and + offx in rasters. */ + ymin = sfnt_floor_fixed (outline->ymin); + xmin = sfnt_floor_fixed (outline->xmin); + for (i = 0; i < outline->outline_used; ++i) { /* Set NEXT_VERTEX to the next point (vertex) in this contour. @@ -3356,12 +3363,12 @@ sfnt_build_outline_edges (struct sfnt_glyph_outline *outline, top = next_vertex; } - bot = (outline->outline[bottom].y - outline->ymin); - edges[edge].top = (outline->outline[top].y - outline->ymin); + bot = (outline->outline[bottom].y - ymin); + edges[edge].top = (outline->outline[top].y - ymin); /* Record the edge. Rasterization happens from bottom to up, so record the X at the bottom. */ - edges[edge].x = (outline->outline[bottom].x - outline->xmin); + edges[edge].x = (outline->outline[bottom].x - xmin); dx = (outline->outline[top].x - outline->outline[bottom].x); dy = abs (outline->outline[top].y - outline->outline[bottom].y); @@ -4585,7 +4592,7 @@ main (int argc, char **argv) /* Time this important bit. */ clock_gettime (CLOCK_THREAD_CPUTIME_ID, &start); outline = sfnt_build_glyph_outline (glyph, head, - 45, + 12, sfnt_test_get_glyph, sfnt_test_free_glyph, &dcontext); @@ -4652,7 +4659,7 @@ main (int argc, char **argv) if (hmtx && head) { - if (!sfnt_lookup_glyph_metrics (code, 36, + if (!sfnt_lookup_glyph_metrics (code, 12, &metrics, hmtx, hhea, head, maxp)) diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index 47aa27dc113..01bfdbaaf58 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -42,19 +42,6 @@ static Lisp_Object font_cache; -static unsigned int -sfntfont_android_saturate32 (unsigned int a, unsigned int b) -{ - unsigned int c; - - c = a + b; - - if (c < a) - c = -1; - - return c; -} - /* Scale each of the four packed bytes in P in the low 16 bits of P by SCALE. Return the result. @@ -107,8 +94,9 @@ sfntfont_android_blend (unsigned int src, unsigned int dst) src = src & ~0x00ff00ff; src |= (src_rb >> 16 | src_rb << 16); - /* Saturating is unnecessary but helps find bugs. */ - return sfntfont_android_saturate32 (both, src); + /* This addition need not be saturating because both has already + been multiplied by 255 - a. */ + return both + src; } #define U255TO256(x) ((unsigned short) (x) + ((x) >> 7)) @@ -128,8 +116,9 @@ sfntfont_android_blendrgb (unsigned int src, unsigned int dst) both = ag_part | rb_part; - /* Saturating is unnecessary but helps find bugs. */ - return sfntfont_android_saturate32 (both, src); + /* This addition need not be saturating because both has already + been multiplied by 255 - a. */ + return both + src; } /* Composite the bitmap described by BUFFER, STRIDE and TEXT_RECTANGLE @@ -162,6 +151,10 @@ sfntfont_android_composite_bitmap (unsigned char *restrict buffer, src_y = i + (rect->y - text_rectangle->y); + if (src_y > text_rectangle->height) + /* Huh? */ + return; + src_row = (unsigned int *) ((buffer + src_y * stride)); dst_row = (unsigned int *) (dest + ((i + rect->y) * bitmap_info->stride)); @@ -343,7 +336,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, } /* Lock the bitmap. It must be unlocked later. */ - bitmap_data = android_lock_bitmap (FRAME_ANDROID_WINDOW (s->f), + bitmap_data = android_lock_bitmap (FRAME_ANDROID_DRAWABLE (s->f), &bitmap_info, &bitmap); /* If locking the bitmap fails, just discard the data that was @@ -385,7 +378,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, ANDROID_DELETE_LOCAL_REF (bitmap); /* Damage the window by the text rectangle. */ - android_damage_window (FRAME_ANDROID_WINDOW (s->f), + android_damage_window (FRAME_ANDROID_DRAWABLE (s->f), &text_rectangle); /* Release the temporary scanline buffer. */ @@ -495,13 +488,19 @@ init_sfntfont_android (void) version of Android the device is running. */ if (android_get_device_api_level () >= 15) Vsfnt_default_family_alist - = list2 (Fcons (build_string ("Monospace"), + = list3 (Fcons (build_string ("Monospace"), + build_string ("Droid Sans Mono")), + /* Android doesn't come with a Monospace Serif font, so + this will have to do. */ + Fcons (build_string ("Monospace Serif"), build_string ("Droid Sans Mono")), Fcons (build_string ("Sans Serif"), build_string ("Roboto"))); else Vsfnt_default_family_alist - = list2 (Fcons (build_string ("Monospace"), + = list3 (Fcons (build_string ("Monospace"), + build_string ("Droid Sans Mono")), + Fcons (build_string ("Monospace Serif"), build_string ("Droid Sans Mono")), Fcons (build_string ("Sans Serif"), build_string ("Droid Sans"))); diff --git a/src/sfntfont.c b/src/sfntfont.c index 9206fbc6629..25cea59f6a7 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -1982,6 +1982,9 @@ sfntfont_text_extents (struct font *font, const unsigned int *code, total_width = 0; + /* First clear the metrics array. */ + memset (metrics, 0, sizeof *metrics); + /* Get the metrcs one by one, then sum them up. */ for (i = 0; i < nglyphs; ++i) { @@ -2059,7 +2062,7 @@ sfntfont_draw (struct glyph_string *s, int from, int to, struct sfnt_glyph_metrics metrics; length = to - from; - font = s->face->font; + font = s->font; info = (struct sfnt_font_info *) font; rasters = alloca (length * sizeof *rasters); diff --git a/src/sysdep.c b/src/sysdep.c index dd97ae1cb38..4d89d4f25ae 100644 --- a/src/sysdep.c +++ b/src/sysdep.c @@ -2335,7 +2335,8 @@ emacs_backtrace (int backtrace_limit) } } -#ifndef HAVE_NTGUI +#if !defined HAVE_NTGUI && !(defined HAVE_ANDROID \ + && !defined ANDROID_STUBIFY) void emacs_abort (void) { @@ -2568,6 +2569,20 @@ emacs_close (int fd) } } +/* Wrapper around fclose. On Android, this calls `android_fclose' to + clear information associated with the FILE's file descriptor if + necessary. */ + +int +emacs_fclose (FILE *stream) +{ +#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) + return fclose (stream); +#else + return android_fclose (stream); +#endif +} + /* Maximum number of bytes to read or write in a single system call. This works around a serious bug in Linux kernels before 2.6.16; see . diff --git a/src/term.c b/src/term.c index 1a21b6f5764..05163cce533 100644 --- a/src/term.c +++ b/src/term.c @@ -2354,8 +2354,8 @@ A suspended tty may be resumed by calling `resume-tty' on it. */) #ifndef MSDOS if (f != t->display_info.tty->output) - fclose (t->display_info.tty->output); - fclose (f); + emacs_fclose (t->display_info.tty->output); + emacs_fclose (f); #endif t->display_info.tty->input = 0; @@ -4632,12 +4632,12 @@ delete_tty (struct terminal *terminal) { delete_keyboard_wait_descriptor (fileno (tty->input)); if (tty->input != stdin) - fclose (tty->input); + emacs_fclose (tty->input); } if (tty->output && tty->output != stdout && tty->output != tty->input) - fclose (tty->output); + emacs_fclose (tty->output); if (tty->termscript) - fclose (tty->termscript); + emacs_fclose (tty->termscript); xfree (tty->old_tty); xfree (tty->Wcm); diff --git a/src/verbose.mk.in b/src/verbose.mk.in index 25dff990881..f826bf18a0a 100644 --- a/src/verbose.mk.in +++ b/src/verbose.mk.in @@ -83,5 +83,5 @@ AM_V_RC = @$(info $ RC $@) # These are used for the Android port. AM_V_JAVAC = @$(info $ JAVAC $@) -AM_V_DX = @$(info $ DX $@) +AM_V_D8 = @$(info $ D8 $@) endif -- cgit v1.3 From 2b87ab7b27163fbd7b6b64c5a44e26b0e692c00a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 14 Jan 2023 22:12:16 +0800 Subject: Update Android port * java/Makefile.in (clean): Fix distclean and bootstrap-clean rules. * java/debug.sh (jdb_port): (attach_existing): (num_pids): (line): Add new options to upload a gdbserver binary to the device. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): Make focusedActivities public. * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): New class. * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Fix bounds computation. * java/org/gnu/emacs/EmacsGC.java (markDirty): Set stroke width explicitly. * java/org/gnu/emacs/EmacsService.java (EmacsService) (getLocationOnScreen, nameKeysym): New functions. * java/org/gnu/emacs/EmacsView.java (EmacsView): Disable focus highlight. (onCreateContextMenu, popupMenu, cancelPopupMenu): New functions. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): Implement a kind of ``override redirect'' window for tooltips. * src/android.c (struct android_emacs_service): New method `name_keysym'. (android_run_select_thread, android_init_events): (android_select): Release select thread on semaphores instead of signals to avoid one nasty race on SIGUSR2 delivery. (android_init_emacs_service): Initialize new method. (android_create_window): Handle CW_OVERRIDE_REDIRECT. (android_move_resize_window, android_map_raised) (android_translate_coordinates, android_get_keysym_name) (android_build_string, android_exception_check): New functions. * src/android.h: Update prototypes. * src/androidfns.c (android_set_parent_frame, Fx_create_frame) (unwind_create_tip_frame, android_create_tip_frame) (android_hide_tip, compute_tip_xy, Fx_show_tip, Fx_hide_tip) (syms_of_androidfns): Implement tooltips and iconification reporting. * src/androidgui.h (enum android_window_value_mask): Add CWOverrideRedirect. (struct android_set_window_attributes): Add `override_redirect'. (ANDROID_IS_MODIFIER_KEY): Recognize Caps Lock. * src/androidmenu.c (struct android_emacs_context_menu): New struct. (android_init_emacs_context_menu, android_unwind_local_frame) (android_push_local_frame, android_menu_show, init_androidmenu): New functions. * src/androidterm.c (handle_one_android_event): Fix NULL pointer dereference. (android_fullscreen_hook): Handle fullscreen correctly. (android_draw_box_rect): Fix top line. (get_keysym_name): Implement function. (android_create_terminal): Remove scroll bar stubs and add menu hook. * src/androidterm.h: Update prototypes. * src/emacs.c (android_emacs_init): Initialize androidmenu.c. * xcompile/Makefile.in: Fix clean rules. --- java/Makefile.in | 2 +- java/debug.sh | 110 +++-- java/org/gnu/emacs/EmacsActivity.java | 2 +- java/org/gnu/emacs/EmacsContextMenu.java | 213 +++++++++ java/org/gnu/emacs/EmacsDrawRectangle.java | 2 +- java/org/gnu/emacs/EmacsGC.java | 1 + java/org/gnu/emacs/EmacsService.java | 43 +- java/org/gnu/emacs/EmacsView.java | 47 ++ java/org/gnu/emacs/EmacsWindow.java | 161 ++++++- src/android.c | 169 ++++++- src/android.h | 6 + src/androidfns.c | 677 ++++++++++++++++++++++++++++- src/androidgui.h | 14 +- src/androidmenu.c | 293 +++++++++++++ src/androidterm.c | 34 +- src/androidterm.h | 6 + src/emacs.c | 4 + xcompile/Makefile.in | 13 +- 18 files changed, 1685 insertions(+), 112 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsContextMenu.java (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/Makefile.in b/java/Makefile.in index 05e61dede89..c539fb0f1fb 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -168,4 +168,4 @@ clean: rm -rf install-temp find . -name '*.class' -delete -maintainer-clean: clean +maintainer-clean distclean bootstrap-clean: clean diff --git a/java/debug.sh b/java/debug.sh index 3e3e3d9c281..aa80aeeebcd 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -31,6 +31,7 @@ gdb_port=5039 jdb_port=64013 jdb=no attach_existing=no +gdbserver= while [ $# -gt 0 ]; do case "$1" in @@ -41,6 +42,7 @@ while [ $# -gt 0 ]; do echo "You must specify an argument to --device" exit 1 fi + shift ;; "--help" ) echo "Usage: $progname [options] -- [gdb options]" @@ -50,6 +52,7 @@ while [ $# -gt 0 ]; do echo " --jdb-port PORT run the JDB server on a specific port" echo " --jdb run JDB instead of GDB" echo " --attach-existing attach to an existing process" + echo " --gdbserver BINARY upload and use the specified gdbserver binary" echo " --help print this message" echo "" echo "Available devices:" @@ -62,9 +65,18 @@ while [ $# -gt 0 ]; do "--jdb" ) jdb=yes ;; + "--gdbserver" ) + shift + gdbserver=$1 + ;; "--port" ) + shift gdb_port=$1 ;; + "--jdb-port" ) + shift + jdb_port=$1 + ;; "--attach-existing" ) attach_existing=yes ;; @@ -170,46 +182,71 @@ elif [ -z $package_pids ]; then exit 1 fi -# Start JDB to make the wait dialog disappear. -echo "Attaching JDB to unblock the application." -adb -s $device forward --remove-all -adb -s $device forward "tcp:$jdb_port" "jdwp:$pid" +# This isn't necessary when attaching gdb to an existing process. +if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then + # Start JDB to make the wait dialog disappear. + echo "Attaching JDB to unblock the application." + adb -s $device forward --remove-all + adb -s $device forward "tcp:$jdb_port" "jdwp:$pid" -if [ ! $? ]; then - echo "Failed to forward jdwp:$pid to $jdb_port!" - echo "Perhaps you need to specify a different port with --port?" - exit 1; -fi + if [ ! $? ]; then + echo "Failed to forward jdwp:$pid to $jdb_port!" + echo "Perhaps you need to specify a different port with --port?" + exit 1; + fi -jdb_command="jdb -connect \ + jdb_command="jdb -connect \ com.sun.jdi.SocketAttach:hostname=localhost,port=$jdb_port" -if [ $jdb = "yes" ]; then - # Just start JDB and then exit - $jdb_command - exit 1 -fi + if [ $jdb = "yes" ]; then + # Just start JDB and then exit + $jdb_command + exit 1 + fi -exec 4<> /tmp/file-descriptor-stamp + exec 4<> /tmp/file-descriptor-stamp -# Now run JDB with IO redirected to file descriptor 4 in a subprocess. -$jdb_command <&4 >&4 & + # Now run JDB with IO redirected to file descriptor 4 in a subprocess. + $jdb_command <&4 >&4 & -character= -# Next, wait until the prompt is found. -while read -n1 -u 4 character; do - if [ "$character" = ">" ]; then - echo "JDB attached successfully" - break; - fi -done + character= + # Next, wait until the prompt is found. + while read -n1 -u 4 character; do + if [ "$character" = ">" ]; then + echo "JDB attached successfully" + break; + fi + done +fi + +# See if gdbserver has to be uploaded +if [ -z "$gdbserver" ]; then + gdbserver_bin=/system/bin/gdbserver +else + gdbserver_bin=/data/local/tmp/gdbserver + + # Upload the specified gdbserver binary to the device. + adb -s $device push "$gdbserver" "$gdbserver_bin" + adb -s $device shell chmod +x "$gdbserver_bin" +fi # Now start gdbserver on the device asynchronously. echo "Attaching gdbserver to $pid on $device..." exec 5<> /tmp/file-descriptor-stamp -adb -s $device shell run-as $package /system/bin/gdbserver --once \ - "+debug.$package_uid.socket" --attach $pid >&5 & + +if [ -z "$gdbserver" ]; then + adb -s $device shell run-as $package $gdbserver_bin --once \ + "+debug.$package_uid.socket" --attach $pid >&5 & + gdb_socket="localfilesystem:$app_data_dir/debug.$package_uid.socket" +else + # Normally the program cannot access $gdbserver_bin when it is + # placed in /data/local/tmp. + adb -s $device shell $gdbserver_bin --once \ + "+/data/local/tmp/debug.$package_uid.socket" \ + --attach $pid >&5 & + gdb_socket="localfilesystem:/data/local/tmp/debug.$package_uid.socket" +fi # Wait until gdbserver successfully runs. line= @@ -227,16 +264,17 @@ while read -u 5 line; do esac done -# Send EOF to JDB to make it go away. This will also cause Android to -# allow Emacs to continue executing. -echo "Making JDB go away..." -echo "exit" >&4 -read -u 4 line -echo "JDB has gone away with $line" +if [ "$attach_existing" != "yes" ]; then + # Send EOF to JDB to make it go away. This will also cause + # Android to allow Emacs to continue executing. + echo "Making JDB go away..." + echo "exit" >&4 + read -u 4 line + echo "JDB has gone away with $line" +fi # Forward the gdb server port here. -adb -s $device forward "tcp:$gdb_port" \ - "localfilesystem:$app_data_dir/debug.$package_uid.socket" +adb -s $device forward "tcp:$gdb_port" $gdb_socket if [ ! $? ]; then echo "Failed to forward $app_data_dir/debug.$package_uid.socket" echo "to $gdb_port! Perhaps you need to specify a different port" diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 2b661024842..4cd286d1e89 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -43,7 +43,7 @@ public class EmacsActivity extends Activity private FrameLayout layout; /* List of activities with focus. */ - private static List focusedActivities; + public static List focusedActivities; /* The currently focused window. */ public static EmacsWindow focusedWindow; diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java new file mode 100644 index 00000000000..8d7ae08b257 --- /dev/null +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -0,0 +1,213 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +package org.gnu.emacs; + +import java.util.List; +import java.util.ArrayList; + +import android.content.Context; +import android.content.Intent; + +import android.os.Bundle; + +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import android.widget.PopupMenu; + +/* Context menu implementation. This object is built from JNI and + describes a menu hiearchy. Then, `inflate' can turn it into an + Android menu, which can be turned into a popup (or other kind of) + menu. */ + +public class EmacsContextMenu +{ + private class Item + { + public int itemID; + public String itemName; + public EmacsContextMenu subMenu; + public boolean isEnabled; + }; + + public List menuItems; + public String title; + private EmacsContextMenu parent; + + /* Create a context menu with no items inside and the title TITLE, + which may be NULL. */ + + public static EmacsContextMenu + createContextMenu (String title) + { + EmacsContextMenu menu; + + menu = new EmacsContextMenu (); + menu.menuItems = new ArrayList (); + menu.title = title; + + return menu; + } + + /* Add a normal menu item to the context menu with the id ITEMID and + the name ITEMNAME. Enable it if ISENABLED, else keep it + disabled. */ + + public void + addItem (int itemID, String itemName, boolean isEnabled) + { + Item item; + + item = new Item (); + item.itemID = itemID; + item.itemName = itemName; + item.isEnabled = isEnabled; + + menuItems.add (item); + } + + /* Create a disabled menu item with the name ITEMNAME. */ + + public void + addPane (String itemName) + { + Item item; + + item = new Item (); + item.itemName = itemName; + + menuItems.add (item); + } + + /* Add a submenu to the context menu with the specified title and + item name. */ + + public EmacsContextMenu + addSubmenu (String itemName, String title) + { + EmacsContextMenu submenu; + Item item; + + item = new Item (); + item.itemID = 0; + item.itemName = itemName; + item.subMenu = createContextMenu (title); + item.subMenu.parent = this; + + menuItems.add (item); + return item.subMenu; + } + + /* Add the contents of this menu to MENU. */ + + private void + inflateMenuItems (Menu menu) + { + Intent intent; + MenuItem menuItem; + Menu submenu; + + for (Item item : menuItems) + { + if (item.subMenu != null) + { + /* This is a submenu. Create the submenu and add the + contents of the menu to it. */ + submenu = menu.addSubMenu (item.itemName); + inflateMenuItems (submenu); + } + else + { + menuItem = menu.add (item.itemName); + + /* If the item ID is zero, then disable the item. */ + if (item.itemID == 0 || !item.isEnabled) + menuItem.setEnabled (false); + } + } + } + + /* Enter the items in this context menu to MENU. Create each menu + item with an Intent containing a Bundle, where the key + "emacs:menu_item_hi" maps to the high 16 bits of the + corresponding item ID, and the key "emacs:menu_item_low" maps to + the low 16 bits of the item ID. */ + + public void + expandTo (Menu menu) + { + inflateMenuItems (menu); + } + + /* Return the parent or NULL. */ + + public EmacsContextMenu + parent () + { + return parent; + } + + /* Like display, but does the actual work and runs in the main + thread. */ + + private boolean + display1 (EmacsWindow window, int xPosition, int yPosition) + { + return window.view.popupMenu (this, xPosition, yPosition); + } + + /* Display this context menu on WINDOW, at xPosition and + yPosition. */ + + public boolean + display (final EmacsWindow window, final int xPosition, + final int yPosition) + { + Runnable runnable; + final Holder rc; + + rc = new Holder (); + + runnable = new Runnable () { + @Override + public void + run () + { + synchronized (this) + { + rc.thing = display1 (window, xPosition, yPosition); + notify (); + } + } + }; + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + + return rc.thing; + } +}; diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index b42e9556e8c..84ff498847b 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -59,7 +59,7 @@ public class EmacsDrawRectangle } paint = gc.gcPaint; - rect = new Rect (x, y, x + width, y + height); + rect = new Rect (x + 1, y + 1, x + width, y + height); paint.setStyle (Paint.Style.STROKE); diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index caa5c91edd4..c579625f3f7 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -93,6 +93,7 @@ public class EmacsGC extends EmacsHandleObject else real_clip_rects = clip_rects; + gcPaint.setStrokeWidth (1f); gcPaint.setColor (foreground | 0xff000000); gcPaint.setXfermode (function == GC_XOR ? xorAlu : srcInAlu); diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index c008300dd3a..f935b63fa0d 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -29,6 +29,7 @@ import android.graphics.Point; import android.view.View; import android.view.InputDevice; +import android.view.KeyEvent; import android.annotation.TargetApi; import android.app.Service; @@ -150,13 +151,13 @@ public class EmacsService extends Service /* Functions from here on must only be called from the Emacs thread. */ - void + public void runOnUiThread (Runnable runnable) { handler.post (runnable); } - EmacsView + public EmacsView getEmacsView (final EmacsWindow window, final int visibility, final boolean isFocusedByDefault) { @@ -196,6 +197,38 @@ public class EmacsService extends Service return view.thing; } + public void + getLocationOnScreen (final EmacsView view, final int[] coordinates) + { + Runnable runnable; + + runnable = new Runnable () { + public void + run () + { + synchronized (this) + { + view.getLocationOnScreen (coordinates); + notify (); + } + } + }; + + synchronized (runnable) + { + runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + } + public void fillRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) @@ -368,4 +401,10 @@ public class EmacsService extends Service return false; } + + public String + nameKeysym (int keysym) + { + return KeyEvent.keyCodeToString (keysym); + } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 41acabab97b..1391f630be0 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -21,6 +21,7 @@ package org.gnu.emacs; import android.content.res.ColorStateList; +import android.view.ContextMenu; import android.view.View; import android.view.KeyEvent; import android.view.MotionEvent; @@ -73,6 +74,12 @@ public class EmacsView extends ViewGroup next call to getBitmap. */ private Rect bitmapDirty; + /* Whether or not a popup is active. */ + private boolean popupActive; + + /* The current context menu. */ + private EmacsContextMenu contextMenu; + public EmacsView (EmacsWindow window) { @@ -98,6 +105,10 @@ public class EmacsView extends ViewGroup /* Get rid of the foreground and background tint. */ setBackgroundTintList (null); setForegroundTintList (null); + + /* Get rid of the default focus highlight. */ + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) + setDefaultFocusHighlightEnabled (false); } private void @@ -423,4 +434,40 @@ public class EmacsView extends ViewGroup removeView (surfaceView); addView (surfaceView, 0); } + + @Override + protected void + onCreateContextMenu (ContextMenu menu) + { + if (contextMenu == null) + return; + + contextMenu.expandTo (menu); + } + + public boolean + popupMenu (EmacsContextMenu menu, int xPosition, + int yPosition) + { + if (popupActive) + return false; + + contextMenu = menu; + + /* On API 21 or later, use showContextMenu (float, float). */ + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) + return showContextMenu ((float) xPosition, (float) yPosition); + else + return showContextMenu (); + } + + public void + cancelPopupMenu () + { + if (!popupActive) + throw new IllegalStateException ("cancelPopupMenu called without" + + " popupActive set"); + + contextMenu = null; + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 1f8596dba50..6effa79d1a4 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -24,16 +24,22 @@ import java.util.ArrayList; import java.util.List; import java.util.HashMap; +import android.content.Context; + import android.graphics.Rect; import android.graphics.Canvas; import android.graphics.Bitmap; import android.graphics.Point; +import android.graphics.PixelFormat; import android.view.View; +import android.view.ViewManager; import android.view.ViewGroup; +import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.InputDevice; +import android.view.WindowManager; import android.content.Intent; import android.util.Log; @@ -110,9 +116,17 @@ public class EmacsWindow extends EmacsHandleObject not the window should be focusable. */ private boolean dontFocusOnMap, dontAcceptFocus; + /* Whether or not the window is override-redirect. An + override-redirect window always has its own system window. */ + private boolean overrideRedirect; + + /* The window manager that is the parent of this window. NULL if + there is no such window manager. */ + private WindowManager windowManager; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, - int width, int height) + int width, int height, boolean overrideRedirect) { super (handle); @@ -124,6 +138,7 @@ public class EmacsWindow extends EmacsHandleObject view = EmacsService.SERVICE.getEmacsView (this, View.GONE, parent == null); this.parent = parent; + this.overrideRedirect = overrideRedirect; /* Create the list of children. */ children = new ArrayList (); @@ -180,7 +195,7 @@ public class EmacsWindow extends EmacsHandleObject public void run () { - View parent; + ViewManager parent; EmacsWindowAttachmentManager manager; if (EmacsActivity.focusedWindow == EmacsWindow.this) @@ -189,10 +204,15 @@ public class EmacsWindow extends EmacsHandleObject manager = EmacsWindowAttachmentManager.MANAGER; view.setVisibility (View.GONE); - parent = (View) view.getParent (); + /* If the window manager is set, use that instead. */ + if (windowManager != null) + parent = windowManager; + else + parent = (ViewManager) view.getParent (); + windowManager = null; if (parent != null) - ((ViewGroup) parent).removeView (view); + parent.removeView (view); manager.detachWindow (EmacsWindow.this); } @@ -247,6 +267,10 @@ public class EmacsWindow extends EmacsHandleObject public void run () { + if (overrideRedirect) + /* Set the layout parameters again. */ + view.setLayoutParams (getWindowLayoutParams ()); + view.mustReportLayout = true; view.requestLayout (); } @@ -284,6 +308,39 @@ public class EmacsWindow extends EmacsHandleObject } } + private WindowManager.LayoutParams + getWindowLayoutParams () + { + WindowManager.LayoutParams params; + int flags, type; + Rect rect; + + flags = 0; + rect = getGeometry (); + flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + + params + = new WindowManager.LayoutParams (rect.width (), rect.height (), + rect.left, rect.top, + type, flags, + PixelFormat.RGBA_8888); + params.gravity = Gravity.TOP | Gravity.LEFT; + return params; + } + + private Context + findSuitableActivityContext () + { + /* Find a recently focused activity. */ + if (!EmacsActivity.focusedActivities.isEmpty ()) + return EmacsActivity.focusedActivities.get (0); + + /* Return the service context, which probably won't work. */ + return EmacsService.SERVICE; + } + public void mapWindow () { @@ -300,20 +357,60 @@ public class EmacsWindow extends EmacsHandleObject run () { EmacsWindowAttachmentManager manager; + WindowManager windowManager; + Context ctx; + Object tem; + WindowManager.LayoutParams params; /* Make the view visible, first of all. */ view.setVisibility (View.VISIBLE); - 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); - - if (!getDontFocusOnMap ()) - /* Eventually this should check no-focus-on-map. */ - view.requestFocus (); + if (!overrideRedirect) + { + 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); + + if (!getDontFocusOnMap ()) + /* Eventually this should check no-focus-on-map. */ + view.requestFocus (); + } + else + { + /* But if the window is an override-redirect window, + then: + + - Find an activity that is currently active. + + - Map the window as a panel on top of that + activity using the system window manager. */ + + ctx = findSuitableActivityContext (); + tem = ctx.getSystemService (Context.WINDOW_SERVICE); + windowManager = (WindowManager) tem; + + /* Calculate layout parameters. */ + params = getWindowLayoutParams (); + view.setLayoutParams (params); + + /* Attach the view. */ + try + { + windowManager.addView (view, params); + + /* Record the window manager being used in the + EmacsWindow object. */ + EmacsWindow.this.windowManager = windowManager; + } + catch (Exception e) + { + Log.w (TAG, + "failed to attach override-redirect window, " + e); + } + } } }); } @@ -355,6 +452,11 @@ public class EmacsWindow extends EmacsHandleObject view.setVisibility (View.GONE); + /* Detach the view from the window manager if possible. */ + if (windowManager != null) + windowManager.removeView (view); + windowManager = null; + /* Now that the window is unmapped, unregister it as well. */ manager.detachWindow (EmacsWindow.this); @@ -756,17 +858,23 @@ public class EmacsWindow extends EmacsHandleObject run () { EmacsWindowAttachmentManager manager; - View parent; + ViewManager parent; /* First, detach this window if necessary. */ manager = EmacsWindowAttachmentManager.MANAGER; manager.detachWindow (EmacsWindow.this); /* Also unparent this view. */ - parent = (View) view.getParent (); + + /* If the window manager is set, use that instead. */ + if (windowManager != null) + parent = windowManager; + else + parent = (ViewManager) view.getParent (); + windowManager = null; if (parent != null) - ((ViewGroup) parent).removeView (view); + parent.removeView (view); /* Next, either add this window as a child of the new parent's view, or make it available again. */ @@ -899,4 +1007,23 @@ public class EmacsWindow extends EmacsHandleObject { return dontFocusOnMap; } + + public int[] + translateCoordinates (int x, int y) + { + int[] array; + + /* This is supposed to translate coordinates to the root + window. */ + array = new int[2]; + EmacsService.SERVICE.getLocationOnScreen (view, array); + + /* Now, the coordinates of the view should be in array. Offset X + and Y by them. */ + array[0] += x; + array[1] += y; + + /* Return the resulting coordinates. */ + return array; + } }; diff --git a/src/android.c b/src/android.c index fba43129ee3..e4022501f9d 100644 --- a/src/android.c +++ b/src/android.c @@ -87,6 +87,7 @@ struct android_emacs_service jmethodID get_screen_width; jmethodID get_screen_height; jmethodID detect_mouse; + jmethodID name_keysym; }; struct android_emacs_pixmap @@ -229,14 +230,14 @@ static volatile bool android_pselect_completed; /* The global event queue. */ static struct android_event_queue event_queue; -/* Semaphore used to signal select completion. */ -static sem_t android_pselect_sem; +/* Semaphores used to signal select completion and start. */ +static sem_t android_pselect_sem, android_pselect_start_sem; static void * android_run_select_thread (void *data) { sigset_t signals; - int sig, rc; + int rc; sigfillset (&signals); @@ -245,23 +246,10 @@ android_run_select_thread (void *data) "pthread_sigmask: %s", strerror (errno)); - sigemptyset (&signals); - sigaddset (&signals, SIGUSR1); - - if (pthread_sigmask (SIG_UNBLOCK, &signals, NULL)) - __android_log_print (ANDROID_LOG_FATAL, __func__, - "pthread_sigmask: %s", - strerror (errno)); - - sigemptyset (&signals); - sigaddset (&signals, SIGUSR2); - while (true) { - /* Keep waiting for SIGUSR2, ignoring EINTR in the meantime. */ - - while (sigwait (&signals, &sig)) - /* Spin. */; + /* Wait for the thread to be released. */ + sem_wait (&android_pselect_start_sem); /* Get the select lock and call pselect. */ pthread_mutex_lock (&event_queue.select_mutex); @@ -322,6 +310,7 @@ android_init_events (void) strerror (errno)); sem_init (&android_pselect_sem, 0, 0); + sem_init (&android_pselect_start_sem, 0, 0); event_queue.events.next = &event_queue.events; event_queue.events.last = &event_queue.events; @@ -444,7 +433,9 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds, android_pselect_sigset = sigset; pthread_mutex_unlock (&event_queue.select_mutex); - pthread_kill (event_queue.select_thread, SIGUSR2); + /* Release the select thread. */ + sem_post (&android_pselect_start_sem); + pthread_cond_wait (&event_queue.read_var, &event_queue.mutex); /* Interrupt the select thread now, in case it's still in @@ -1058,6 +1049,7 @@ android_init_emacs_service (void) FIND_METHOD (get_screen_width, "getScreenWidth", "(Z)I"); FIND_METHOD (get_screen_height, "getScreenHeight", "(Z)I"); FIND_METHOD (detect_mouse, "detectMouse", "()Z"); + FIND_METHOD (name_keysym, "nameKeysym", "(I)Ljava/lang/String;"); #undef FIND_METHOD } @@ -1678,6 +1670,7 @@ android_create_window (android_window parent, int x, int y, jobject object, parent_object, old; android_window window; android_handle prev_max_handle; + bool override_redirect; parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW); @@ -1695,7 +1688,8 @@ android_create_window (android_window parent, int x, int y, constructor = (*android_java_env)->GetMethodID (android_java_env, class, "", - "(SLorg/gnu/emacs/EmacsWindow;IIII)V"); + "(SLorg/gnu/emacs/EmacsWindow;" + "IIIIZ)V"); assert (constructor != NULL); old = class; @@ -1707,10 +1701,17 @@ android_create_window (android_window parent, int x, int y, memory_full (0); } + /* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window + creation time. */ + override_redirect = ((value_mask + & ANDROID_CW_OVERRIDE_REDIRECT) + && attrs->override_redirect); + object = (*android_java_env)->NewObject (android_java_env, class, constructor, (jshort) window, parent_object, (jint) x, (jint) y, - (jint) width, (jint) height); + (jint) width, (jint) height, + (jboolean) override_redirect); if (!object) { (*android_java_env)->ExceptionClear (android_java_env); @@ -3212,6 +3213,66 @@ android_get_geometry (android_window handle, ANDROID_DELETE_LOCAL_REF (window_geometry); } +void +android_move_resize_window (android_window window, int x, int y, + unsigned int width, unsigned int height) +{ + android_move_window (window, x, y); + android_resize_window (window, width, height); +} + +void +android_map_raised (android_window window) +{ + android_raise_window (window); + android_map_window (window); +} + +void +android_translate_coordinates (android_window src, int x, + int y, int *root_x, int *root_y) +{ + jobject window; + jarray coordinates; + jmethodID method; + jint *ints; + + window = android_resolve_handle (src, ANDROID_HANDLE_WINDOW); + method = android_lookup_method ("org/gnu/emacs/EmacsWindow", + "translateCoordinates", + "(II)[I"); + coordinates + = (*android_java_env)->CallObjectMethod (android_java_env, + window, method, + (jint) x, (jint) y); + + if (!coordinates) + { + (*android_java_env)->ExceptionClear (android_java_env); + memory_full (0); + } + + /* The array must contain two elements: X, Y translated to the root + window. */ + eassert ((*android_java_env)->GetArrayLength (android_java_env, + coordinates) + == 2); + + /* Obtain the coordinates from the array. */ + ints = (*android_java_env)->GetIntArrayElements (android_java_env, + coordinates, NULL); + *root_x = ints[0]; + *root_y = ints[1]; + + /* Release the coordinates. */ + (*android_java_env)->ReleaseIntArrayElements (android_java_env, + coordinates, ints, + JNI_ABORT); + + /* And free the local reference. */ + ANDROID_DELETE_LOCAL_REF (coordinates); +} + /* Low level drawing primitives. */ @@ -3384,6 +3445,30 @@ android_set_dont_accept_focus (android_window handle, (jboolean) no_accept_focus); } +void +android_get_keysym_name (int keysym, char *name_return, size_t size) +{ + jobject string; + const char *buffer; + + string = (*android_java_env)->CallObjectMethod (android_java_env, + emacs_service, + service_class.name_keysym, + (jint) keysym); + android_exception_check (); + + buffer = (*android_java_env)->GetStringUTFChars (android_java_env, + (jstring) string, + NULL); + android_exception_check (); + strncpy (name_return, buffer, size - 1); + + (*android_java_env)->ReleaseStringUTFChars (android_java_env, + (jstring) string, + buffer); + ANDROID_DELETE_LOCAL_REF (string); +} + #undef faccessat @@ -3525,6 +3610,48 @@ emacs_abort (void) abort (); } + + +/* Given a Lisp string TEXT, return a local reference to an equivalent + Java string. */ + +jstring +android_build_string (Lisp_Object text) +{ + Lisp_Object encoded; + jstring string; + + encoded = ENCODE_UTF_8 (text); + + /* Note that Java expects this string to be in ``modified UTF + encoding'', which is actually UTF-8, except with NUL encoded as a + two-byte sequence. The only consequence of passing an actual + UTF-8 string is that NUL bytes cannot be represented, which is + not really of consequence. */ + string = (*android_java_env)->NewStringUTF (android_java_env, + SSDATA (encoded)); + if (!string) + { + (*android_java_env)->ExceptionClear (android_java_env); + memory_full (0); + } + + return string; +} + +/* Check for JNI exceptions and call memory_full in that + situation. */ + +void +android_exception_check (void) +{ + if ((*android_java_env)->ExceptionCheck (android_java_env)) + { + (*android_java_env)->ExceptionClear (android_java_env); + memory_full (0); + } +} + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ diff --git a/src/android.h b/src/android.h index af1e42ec370..98f2494e9a3 100644 --- a/src/android.h +++ b/src/android.h @@ -35,6 +35,7 @@ along with GNU Emacs. If not, see . */ #include #include "androidgui.h" +#include "lisp.h" #endif /* This must be used in every symbol declaration to export it to the @@ -84,6 +85,11 @@ extern bool android_detect_mouse (void); extern void android_set_dont_focus_on_map (android_window, bool); extern void android_set_dont_accept_focus (android_window, bool); +extern jstring android_build_string (Lisp_Object); +extern void android_exception_check (void); + +extern void android_get_keysym_name (int, char *, size_t); + /* Directory listing emulation. */ diff --git a/src/androidfns.c b/src/androidfns.c index 459e407b901..ab136bc2722 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -25,12 +25,36 @@ along with GNU Emacs. If not, see . */ #include "androidterm.h" #include "blockinput.h" #include "keyboard.h" +#include "buffer.h" #ifndef ANDROID_STUBIFY /* Some kind of reference count for the image cache. */ static ptrdiff_t image_cache_refcount; +/* The frame of the currently visible tooltip, or nil if none. */ +static Lisp_Object tip_frame; + +/* The window-system window corresponding to the frame of the + currently visible tooltip. */ +static android_window tip_window; + +/* The X and Y deltas of the last call to `x-show-tip'. */ +static Lisp_Object tip_dx, tip_dy; + +/* A timer that hides or deletes the currently visible tooltip when it + fires. */ +static Lisp_Object tip_timer; + +/* STRING argument of last `x-show-tip' call. */ +static Lisp_Object tip_last_string; + +/* Normalized FRAME argument of last `x-show-tip' call. */ +static Lisp_Object tip_last_frame; + +/* PARMS argument of last `x-show-tip' call. */ +static Lisp_Object tip_last_parms; + #endif static struct android_display_info * @@ -180,6 +204,9 @@ android_set_parent_frame (struct frame *f, Lisp_Object new_value, fset_parent_frame (f, new_value); } + + /* Update the fullscreen frame parameter as well. */ + FRAME_TERMINAL (f)->fullscreen_hook (f); } void @@ -858,13 +885,13 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame, gui_default_parameter (f, parms, Qbottom_divider_width, make_fixnum (0), NULL, NULL, RES_TYPE_NUMBER); - /* gui_default_parameter (f, parms, Qvertical_scroll_bars, */ - /* Qleft, */ - /* "verticalScrollBars", "ScrollBars", */ - /* RES_TYPE_SYMBOL); */ - /* gui_default_parameter (f, parms, Qhorizontal_scroll_bars, Qnil, */ - /* "horizontalScrollBars", "ScrollBars", */ - /* RES_TYPE_SYMBOL); TODO */ + gui_default_parameter (f, parms, Qvertical_scroll_bars, + Qleft, + "verticalScrollBars", "ScrollBars", + RES_TYPE_SYMBOL); + gui_default_parameter (f, parms, Qhorizontal_scroll_bars, Qnil, + "horizontalScrollBars", "ScrollBars", + RES_TYPE_SYMBOL); /* Also do the stuff which must be set before the window exists. */ gui_default_parameter (f, parms, Qforeground_color, build_string ("black"), @@ -893,7 +920,7 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame, android_default_scroll_bar_color_parameter (f, parms, Qscroll_bar_background, "scrollBarBackground", "ScrollBarBackground", false); -#endif /* TODO */ +#endif /* Init faces before gui_default_parameter is called for the scroll-bar-width parameter because otherwise we end up in @@ -974,12 +1001,16 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame, "autoLower", "AutoRaiseLower", RES_TYPE_BOOLEAN); gui_default_parameter (f, parms, Qcursor_type, Qbox, "cursorType", "CursorType", RES_TYPE_SYMBOL); + /* Scroll bars are not supported on Android, as they are near + useless. */ +#if 0 gui_default_parameter (f, parms, Qscroll_bar_width, Qnil, "scrollBarWidth", "ScrollBarWidth", RES_TYPE_NUMBER); gui_default_parameter (f, parms, Qscroll_bar_height, Qnil, "scrollBarHeight", "ScrollBarHeight", RES_TYPE_NUMBER); +#endif gui_default_parameter (f, parms, Qalpha, Qnil, "alpha", "Alpha", RES_TYPE_NUMBER); gui_default_parameter (f, parms, Qalpha_background, Qnil, @@ -1009,8 +1040,9 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame, /* Process fullscreen parameter here in the hope that normalizing a fullheight/fullwidth frame will produce the size set by the last - adjust_frame_size call. */ - gui_default_parameter (f, parms, Qfullscreen, Qnil, + adjust_frame_size call. Note that Android only supports the + `maximized' state. */ + gui_default_parameter (f, parms, Qfullscreen, Qmaximized, "fullscreen", "Fullscreen", RES_TYPE_SYMBOL); /* When called from `x-create-frame-with-faces' visibility is @@ -1661,6 +1693,391 @@ DEFUN ("x-display-list", Fx_display_list, Sx_display_list, 0, 0, 0, return result; } +#ifndef ANDROID_STUBIFY + +static void +unwind_create_tip_frame (Lisp_Object frame) +{ + Lisp_Object deleted; + + deleted = unwind_create_frame (frame); + if (EQ (deleted, Qt)) + { + tip_window = ANDROID_NONE; + tip_frame = Qnil; + } +} + +static Lisp_Object +android_create_tip_frame (struct android_display_info *dpyinfo, + Lisp_Object parms) +{ + struct frame *f; + Lisp_Object frame; + Lisp_Object name; + specpdl_ref count = SPECPDL_INDEX (); + bool face_change_before = face_change; + + if (!dpyinfo->terminal->name) + error ("Terminal is not live, can't create new frames on it"); + + parms = Fcopy_alist (parms); + + /* Get the name of the frame to use for resource lookup. */ + name = gui_display_get_arg (dpyinfo, parms, Qname, "name", "Name", + RES_TYPE_STRING); + if (!STRINGP (name) + && !BASE_EQ (name, Qunbound) + && !NILP (name)) + error ("Invalid frame name--not a string or nil"); + + frame = Qnil; + f = make_frame (false); + f->wants_modeline = false; + XSETFRAME (frame, f); + record_unwind_protect (unwind_create_tip_frame, frame); + + f->terminal = dpyinfo->terminal; + + /* By setting the output method, we're essentially saying that + the frame is live, as per FRAME_LIVE_P. If we get a signal + from this point on, x_destroy_window might screw up reference + counts etc. */ + f->output_method = output_android; + f->output_data.android = xzalloc (sizeof *f->output_data.android); + FRAME_FONTSET (f) = -1; + f->output_data.android->white_relief.pixel = -1; + f->output_data.android->black_relief.pixel = -1; + + f->tooltip = true; + fset_icon_name (f, Qnil); + FRAME_DISPLAY_INFO (f) = dpyinfo; + f->output_data.android->parent_desc = FRAME_DISPLAY_INFO (f)->root_window; + + /* These colors will be set anyway later, but it's important + to get the color reference counts right, so initialize them! */ + { + Lisp_Object black; + + /* Function android_decode_color can signal an error. Make sure + to initialize color slots so that we won't try to free colors + we haven't allocated. */ + FRAME_FOREGROUND_PIXEL (f) = -1; + FRAME_BACKGROUND_PIXEL (f) = -1; + f->output_data.android->cursor_pixel = -1; + f->output_data.android->cursor_foreground_pixel = -1; + + black = build_string ("black"); + FRAME_FOREGROUND_PIXEL (f) + = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); + FRAME_BACKGROUND_PIXEL (f) + = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); + f->output_data.android->cursor_pixel + = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); + f->output_data.android->cursor_foreground_pixel + = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); + } + + /* Set the name; the functions to which we pass f expect the name to + be set. */ + if (BASE_EQ (name, Qunbound) || NILP (name)) + f->explicit_name = false; + else + { + fset_name (f, name); + f->explicit_name = true; + /* use the frame's title when getting resources for this frame. */ + specbind (Qx_resource_name, name); + } + + register_font_driver (&androidfont_driver, f); + register_font_driver (&android_sfntfont_driver, f); + + image_cache_refcount + = FRAME_IMAGE_CACHE (f) ? FRAME_IMAGE_CACHE (f)->refcount : 0; +#ifdef GLYPH_DEBUG + dpyinfo_refcount = dpyinfo->reference_count; +#endif /* GLYPH_DEBUG */ + + gui_default_parameter (f, parms, Qfont_backend, Qnil, + "fontBackend", "FontBackend", RES_TYPE_STRING); + + /* Extract the window parameters from the supplied values that are + needed to determine window geometry. */ + android_default_font_parameter (f, parms); + + gui_default_parameter (f, parms, Qborder_width, make_fixnum (0), + "borderWidth", "BorderWidth", RES_TYPE_NUMBER); + + /* This defaults to 1 in order to match xterm. We recognize either + internalBorderWidth or internalBorder (which is what xterm calls + it). */ + if (NILP (Fassq (Qinternal_border_width, parms))) + { + Lisp_Object value; + + value = gui_display_get_arg (dpyinfo, parms, Qinternal_border_width, + "internalBorder", "internalBorder", + RES_TYPE_NUMBER); + if (! BASE_EQ (value, Qunbound)) + parms = Fcons (Fcons (Qinternal_border_width, value), + parms); + } + + gui_default_parameter (f, parms, Qinternal_border_width, make_fixnum (1), + "internalBorderWidth", "internalBorderWidth", + RES_TYPE_NUMBER); + gui_default_parameter (f, parms, Qright_divider_width, make_fixnum (0), + NULL, NULL, RES_TYPE_NUMBER); + gui_default_parameter (f, parms, Qbottom_divider_width, make_fixnum (0), + NULL, NULL, RES_TYPE_NUMBER); + + /* Also do the stuff which must be set before the window exists. */ + gui_default_parameter (f, parms, Qforeground_color, build_string ("black"), + "foreground", "Foreground", RES_TYPE_STRING); + gui_default_parameter (f, parms, Qbackground_color, build_string ("white"), + "background", "Background", RES_TYPE_STRING); + gui_default_parameter (f, parms, Qmouse_color, build_string ("black"), + "pointerColor", "Foreground", RES_TYPE_STRING); + gui_default_parameter (f, parms, Qcursor_color, build_string ("black"), + "cursorColor", "Foreground", RES_TYPE_STRING); + gui_default_parameter (f, parms, Qborder_color, build_string ("black"), + "borderColor", "BorderColor", RES_TYPE_STRING); + gui_default_parameter (f, parms, Qno_special_glyphs, Qnil, + NULL, NULL, RES_TYPE_BOOLEAN); + + { + struct android_set_window_attributes attrs; + unsigned long mask; + + block_input (); + mask = ANDROID_CW_OVERRIDE_REDIRECT; + + attrs.override_redirect = true; + tip_window + = FRAME_ANDROID_WINDOW (f) + = android_create_window (FRAME_DISPLAY_INFO (f)->root_window, + /* x, y, width, height, value-mask, + attrs. */ + 0, 0, 1, 1, mask, &attrs); + unblock_input (); + } + + /* Init faces before gui_default_parameter is called for the + scroll-bar-width parameter because otherwise we end up in + init_iterator with a null face cache, which should not happen. */ + init_frame_faces (f); + + gui_default_parameter (f, parms, Qinhibit_double_buffering, Qnil, + "inhibitDoubleBuffering", "InhibitDoubleBuffering", + RES_TYPE_BOOLEAN); + + gui_figure_window_size (f, parms, false, false); + + f->output_data.android->parent_desc = FRAME_DISPLAY_INFO (f)->root_window; + + android_make_gc (f); + + gui_default_parameter (f, parms, Qauto_raise, Qnil, + "autoRaise", "AutoRaiseLower", RES_TYPE_BOOLEAN); + gui_default_parameter (f, parms, Qauto_lower, Qnil, + "autoLower", "AutoRaiseLower", RES_TYPE_BOOLEAN); + gui_default_parameter (f, parms, Qcursor_type, Qbox, + "cursorType", "CursorType", RES_TYPE_SYMBOL); + gui_default_parameter (f, parms, Qalpha, Qnil, + "alpha", "Alpha", RES_TYPE_NUMBER); + gui_default_parameter (f, parms, Qalpha_background, Qnil, + "alphaBackground", "AlphaBackground", RES_TYPE_NUMBER); + + /* Add `tooltip' frame parameter's default value. */ + if (NILP (Fframe_parameter (frame, Qtooltip))) + { + AUTO_FRAME_ARG (arg, Qtooltip, Qt); + Fmodify_frame_parameters (frame, arg); + } + + /* FIXME - can this be done in a similar way to normal frames? + https://lists.gnu.org/r/emacs-devel/2007-10/msg00641.html */ + + /* Set the `display-type' frame parameter before setting up faces. */ + { + Lisp_Object disptype; + + disptype = Qcolor; + + if (NILP (Fframe_parameter (frame, Qdisplay_type))) + { + AUTO_FRAME_ARG (arg, Qdisplay_type, disptype); + Fmodify_frame_parameters (frame, arg); + } + } + + /* Set up faces after all frame parameters are known. This call + also merges in face attributes specified for new frames. */ + { + Lisp_Object bg = Fframe_parameter (frame, Qbackground_color); + + call2 (Qface_set_after_frame_default, frame, Qnil); + + if (!EQ (bg, Fframe_parameter (frame, Qbackground_color))) + { + AUTO_FRAME_ARG (arg, Qbackground_color, bg); + Fmodify_frame_parameters (frame, arg); + } + } + + f->no_split = true; + + /* Now that the frame will be official, it counts as a reference to + its display and terminal. */ + f->terminal->reference_count++; + + /* It is now ok to make the frame official even if we get an error + below. And the frame needs to be on Vframe_list or making it + visible won't work. */ + Vframe_list = Fcons (frame, Vframe_list); + f->can_set_window_size = true; + adjust_frame_size (f, FRAME_TEXT_WIDTH (f), FRAME_TEXT_HEIGHT (f), + 0, true, Qtip_frame); + + /* Setting attributes of faces of the tooltip frame from resources + and similar will set face_change, which leads to the clearing of + all current matrices. Since this isn't necessary here, avoid it + by resetting face_change to the value it had before we created + the tip frame. */ + face_change = face_change_before; + + /* Discard the unwind_protect. */ + return unbind_to (count, frame); +} + +static Lisp_Object +android_hide_tip (bool delete) +{ + if (!NILP (tip_timer)) + { + call1 (Qcancel_timer, tip_timer); + tip_timer = Qnil; + } + + if (NILP (tip_frame) + || (!delete + && !NILP (tip_frame) + && FRAME_LIVE_P (XFRAME (tip_frame)) + && !FRAME_VISIBLE_P (XFRAME (tip_frame)))) + return Qnil; + else + { + Lisp_Object was_open = Qnil; + + specpdl_ref count = SPECPDL_INDEX (); + specbind (Qinhibit_redisplay, Qt); + specbind (Qinhibit_quit, Qt); + + if (!NILP (tip_frame)) + { + struct frame *f = XFRAME (tip_frame); + + if (FRAME_LIVE_P (f)) + { + if (delete) + { + delete_frame (tip_frame, Qnil); + tip_frame = Qnil; + } + else + android_make_frame_invisible (XFRAME (tip_frame)); + + was_open = Qt; + } + else + tip_frame = Qnil; + } + else + tip_frame = Qnil; + + return unbind_to (count, was_open); + } +} + +static void +compute_tip_xy (struct frame *f, Lisp_Object parms, Lisp_Object dx, + Lisp_Object dy, int width, int height, int *root_x, + int *root_y) +{ + Lisp_Object left, top, right, bottom; + int min_x, min_y, max_x, max_y = -1; + android_window window; + struct frame *mouse_frame; + + /* Initialize these values in case there is no mouse frame. */ + *root_x = 0; + *root_y = 0; + + /* User-specified position? */ + left = CDR (Fassq (Qleft, parms)); + top = CDR (Fassq (Qtop, parms)); + right = CDR (Fassq (Qright, parms)); + bottom = CDR (Fassq (Qbottom, parms)); + + /* Move the tooltip window where the mouse pointer was last seen. + Resize and show it. */ + if ((!FIXNUMP (left) && !FIXNUMP (right)) + || (!FIXNUMP (top) && !FIXNUMP (bottom))) + { + if (x_display_list->last_mouse_motion_frame) + { + *root_x = x_display_list->last_mouse_motion_x; + *root_y = x_display_list->last_mouse_motion_y; + mouse_frame = x_display_list->last_mouse_motion_frame; + window = FRAME_ANDROID_WINDOW (mouse_frame); + + /* Translate the coordinates to the screen. */ + android_translate_coordinates (window, *root_x, *root_y, + root_x, root_y); + } + } + + min_x = 0; + min_y = 0; + max_x = android_get_screen_width (); + max_y = android_get_screen_height (); + + if (FIXNUMP (top)) + *root_y = XFIXNUM (top); + else if (FIXNUMP (bottom)) + *root_y = XFIXNUM (bottom) - height; + else if (*root_y + XFIXNUM (dy) <= min_y) + *root_y = min_y; /* Can happen for negative dy */ + else if (*root_y + XFIXNUM (dy) + height <= max_y) + /* It fits below the pointer */ + *root_y += XFIXNUM (dy); + else if (height + XFIXNUM (dy) + min_y <= *root_y) + /* It fits above the pointer. */ + *root_y -= height + XFIXNUM (dy); + else + /* Put it on the top. */ + *root_y = min_y; + + if (FIXNUMP (left)) + *root_x = XFIXNUM (left); + else if (FIXNUMP (right)) + *root_x = XFIXNUM (right) - width; + else if (*root_x + XFIXNUM (dx) <= min_x) + *root_x = 0; /* Can happen for negative dx */ + else if (*root_x + XFIXNUM (dx) + width <= max_x) + /* It fits to the right of the pointer. */ + *root_x += XFIXNUM (dx); + else if (width + XFIXNUM (dx) + min_x <= *root_x) + /* It fits to the left of the pointer. */ + *root_x -= width + XFIXNUM (dx); + else + /* Put it left justified on the screen -- it ought to fit that way. */ + *root_x = min_x; +} + +#endif + DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, doc: /* SKIP: real doc in xfns.c. */) (Lisp_Object string, Lisp_Object frame, Lisp_Object parms, @@ -1670,8 +2087,214 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, error ("Android cross-compilation stub called!"); return Qnil; #else - /* TODO tooltips */ - return Qnil; + struct frame *f, *tip_f; + struct window *w; + int root_x, root_y; + struct buffer *old_buffer; + struct text_pos pos; + int width, height; + int old_windows_or_buffers_changed = windows_or_buffers_changed; + specpdl_ref count = SPECPDL_INDEX (); + Lisp_Object window, size, tip_buf; + bool displayed; +#ifdef ENABLE_CHECKING + struct glyph_row *row, *end; +#endif + AUTO_STRING (tip, " *tip*"); + + specbind (Qinhibit_redisplay, Qt); + + CHECK_STRING (string); + if (SCHARS (string) == 0) + string = make_unibyte_string (" ", 1); + + if (NILP (frame)) + frame = selected_frame; + f = decode_window_system_frame (frame); + + if (NILP (timeout)) + timeout = Vx_show_tooltip_timeout; + CHECK_FIXNAT (timeout); + + if (NILP (dx)) + dx = make_fixnum (5); + else + CHECK_FIXNUM (dx); + + if (NILP (dy)) + dy = make_fixnum (-10); + else + CHECK_FIXNUM (dy); + + tip_dx = dx; + tip_dy = dy; + + if (!NILP (tip_frame) && FRAME_LIVE_P (XFRAME (tip_frame))) + { + if (FRAME_VISIBLE_P (XFRAME (tip_frame)) + && !NILP (Fequal_including_properties (tip_last_string, + string)) + && !NILP (Fequal (tip_last_parms, parms))) + { + /* Only DX and DY have changed. */ + tip_f = XFRAME (tip_frame); + if (!NILP (tip_timer)) + { + call1 (Qcancel_timer, tip_timer); + tip_timer = Qnil; + } + + block_input (); + compute_tip_xy (tip_f, parms, dx, dy, FRAME_PIXEL_WIDTH (tip_f), + FRAME_PIXEL_HEIGHT (tip_f), &root_x, &root_y); + android_move_window (FRAME_ANDROID_WINDOW (tip_f), + root_x, root_y); + unblock_input (); + + goto start_timer; + } + else + android_hide_tip (true); + } + else + android_hide_tip (true); + + tip_last_frame = frame; + tip_last_string = string; + tip_last_parms = parms; + + if (NILP (tip_frame) || !FRAME_LIVE_P (XFRAME (tip_frame))) + { + /* Add default values to frame parameters. */ + if (NILP (Fassq (Qname, parms))) + parms = Fcons (Fcons (Qname, build_string ("tooltip")), parms); + if (NILP (Fassq (Qinternal_border_width, parms))) + parms = Fcons (Fcons (Qinternal_border_width, make_fixnum (3)), + parms); + if (NILP (Fassq (Qborder_width, parms))) + parms = Fcons (Fcons (Qborder_width, make_fixnum (1)), parms); + if (NILP (Fassq (Qborder_color, parms))) + parms = Fcons (Fcons (Qborder_color, build_string ("lightyellow")), + parms); + if (NILP (Fassq (Qbackground_color, parms))) + parms = Fcons (Fcons (Qbackground_color, + build_string ("lightyellow")), + parms); + + /* Create a frame for the tooltip, and record it in the global + variable tip_frame. */ + if (NILP (tip_frame = android_create_tip_frame (FRAME_DISPLAY_INFO (f), + parms))) + /* Creating the tip frame failed. */ + return unbind_to (count, Qnil); + } + + tip_f = XFRAME (tip_frame); + window = FRAME_ROOT_WINDOW (tip_f); + tip_buf = Fget_buffer_create (tip, Qnil); + /* We will mark the tip window a "pseudo-window" below, and such + windows cannot have display margins. */ + bset_left_margin_cols (XBUFFER (tip_buf), make_fixnum (0)); + bset_right_margin_cols (XBUFFER (tip_buf), make_fixnum (0)); + set_window_buffer (window, tip_buf, false, false); + w = XWINDOW (window); + w->pseudo_window_p = true; + /* Try to avoid that `other-window' select us (Bug#47207). */ + Fset_window_parameter (window, Qno_other_window, Qt); + + /* Set up the frame's root window. Note: The following code does not + try to size the window or its frame correctly. Its only purpose is + to make the subsequent text size calculations work. The right + sizes should get installed when the toolkit gets back to us. */ + w->left_col = 0; + w->top_line = 0; + w->pixel_left = 0; + w->pixel_top = 0; + + if (CONSP (Vx_max_tooltip_size) + && RANGED_FIXNUMP (1, XCAR (Vx_max_tooltip_size), INT_MAX) + && RANGED_FIXNUMP (1, XCDR (Vx_max_tooltip_size), INT_MAX)) + { + w->total_cols = XFIXNAT (XCAR (Vx_max_tooltip_size)); + w->total_lines = XFIXNAT (XCDR (Vx_max_tooltip_size)); + } + else + { + w->total_cols = 80; + w->total_lines = 40; + } + + w->pixel_width = w->total_cols * FRAME_COLUMN_WIDTH (tip_f); + w->pixel_height = w->total_lines * FRAME_LINE_HEIGHT (tip_f); + FRAME_TOTAL_COLS (tip_f) = w->total_cols; + adjust_frame_glyphs (tip_f); + + /* Insert STRING into root window's buffer and fit the frame to the + buffer. */ + specpdl_ref count_1 = SPECPDL_INDEX (); + old_buffer = current_buffer; + set_buffer_internal_1 (XBUFFER (w->contents)); + bset_truncate_lines (current_buffer, Qnil); + specbind (Qinhibit_read_only, Qt); + specbind (Qinhibit_modification_hooks, Qt); + specbind (Qinhibit_point_motion_hooks, Qt); + Ferase_buffer (); + Finsert (1, &string); + clear_glyph_matrix (w->desired_matrix); + clear_glyph_matrix (w->current_matrix); + SET_TEXT_POS (pos, BEGV, BEGV_BYTE); + displayed = try_window (window, pos, TRY_WINDOW_IGNORE_FONTS_CHANGE); + + if (!displayed && NILP (Vx_max_tooltip_size)) + { +#ifdef ENABLE_CHECKING + row = w->desired_matrix->rows; + end = w->desired_matrix->rows + w->desired_matrix->nrows; + + while (row < end) + { + if (!row->displays_text_p + || row->ends_at_zv_p) + break; + ++row; + } + + eassert (row < end && row->ends_at_zv_p); +#endif + } + + /* Calculate size of tooltip window. */ + size = Fwindow_text_pixel_size (window, Qnil, Qnil, Qnil, + make_fixnum (w->pixel_height), Qnil, + Qnil); + /* Add the frame's internal border to calculated size. */ + width = XFIXNUM (CAR (size)) + 2 * FRAME_INTERNAL_BORDER_WIDTH (tip_f); + height = XFIXNUM (CDR (size)) + 2 * FRAME_INTERNAL_BORDER_WIDTH (tip_f); + + /* Calculate position of tooltip frame. */ + compute_tip_xy (tip_f, parms, dx, dy, width, height, &root_x, &root_y); + + /* Show tooltip frame. */ + block_input (); + android_move_resize_window (FRAME_ANDROID_WINDOW (tip_f), + root_x, root_y, width, + height); + android_map_raised (FRAME_ANDROID_WINDOW (tip_f)); + unblock_input (); + + w->must_be_updated_p = true; + update_single_window (w); + flush_frame (tip_f); + set_buffer_internal_1 (old_buffer); + unbind_to (count_1, Qnil); + windows_or_buffers_changed = old_windows_or_buffers_changed; + + start_timer: + /* Let the tip disappear after timeout seconds. */ + tip_timer = call3 (Qrun_at_time, timeout, Qnil, + Qx_hide_tip); + + return unbind_to (count, Qnil); #endif } @@ -1683,7 +2306,7 @@ DEFUN ("x-hide-tip", Fx_hide_tip, Sx_hide_tip, 0, 0, 0, error ("Android cross-compilation stub called!"); return Qnil; #else - return Qnil; + return android_hide_tip (true); #endif } @@ -2112,6 +2735,17 @@ syms_of_androidfns (void) doc: /* SKIP: real doc in xfns.c. */); Vx_cursor_fore_pixel = Qnil; + /* Used by Fx_show_tip. */ + DEFSYM (Qrun_at_time, "run-at-time"); + DEFSYM (Qx_hide_tip, "x-hide-tip"); + DEFSYM (Qcancel_timer, "cancel-timer"); + DEFSYM (Qassq_delete_all, "assq-delete-all"); + DEFSYM (Qcolor, "color"); + + DEFVAR_LISP ("x-max-tooltip-size", Vx_max_tooltip_size, + doc: /* SKIP: real doc in xfns.c. */); + Vx_max_tooltip_size = Qnil; + /* Functions defined. */ defsubr (&Sx_create_frame); defsubr (&Sxw_color_defined_p); @@ -2139,4 +2773,21 @@ syms_of_androidfns (void) defsubr (&Sx_show_tip); defsubr (&Sx_hide_tip); defsubr (&Sandroid_detect_mouse); + +#ifndef ANDROID_STUBIFY + tip_timer = Qnil; + staticpro (&tip_timer); + tip_frame = Qnil; + staticpro (&tip_frame); + tip_last_frame = Qnil; + staticpro (&tip_last_frame); + tip_last_string = Qnil; + staticpro (&tip_last_string); + tip_last_parms = Qnil; + staticpro (&tip_last_parms); + tip_dx = Qnil; + staticpro (&tip_dx); + tip_dy = Qnil; + staticpro (&tip_dy); +#endif } diff --git a/src/androidgui.h b/src/androidgui.h index 422e72408c7..8450a1f637b 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -80,13 +80,18 @@ enum android_fill_style enum android_window_value_mask { - ANDROID_CW_BACK_PIXEL = (1 << 1), + ANDROID_CW_BACK_PIXEL = (1 << 1), + ANDROID_CW_OVERRIDE_REDIRECT = (1 << 2), }; struct android_set_window_attributes { /* The background pixel. */ unsigned long background_pixel; + + /* Whether or not the window is override redirect. This cannot be + set after creation on Android. */ + bool override_redirect; }; struct android_gc_values @@ -260,7 +265,7 @@ struct android_key_event ((key) == 57 || (key) == 58 || (key) == 113 || (key) == 114 \ || (key) == 119 || (key) == 117 || (key) == 118 || (key) == 78 \ || (key) == 94 || (key) == 59 || (key) == 60 || (key) == 95 \ - || (key) == 63) + || (key) == 63 || (key) == 115) struct android_configure_event { @@ -478,6 +483,11 @@ extern int android_query_tree (android_window, android_window *, extern void android_get_geometry (android_window, android_window *, int *, int *, unsigned int *, unsigned int *, unsigned int *); +extern void android_move_resize_window (android_window, int, int, + unsigned int, unsigned int); +extern void android_map_raised (android_window); +extern void android_translate_coordinates (android_window, int, + int, int *, int *); #endif diff --git a/src/androidmenu.c b/src/androidmenu.c index 6f6e4ca8de9..0f0c6f4ef1f 100644 --- a/src/androidmenu.c +++ b/src/androidmenu.c @@ -21,6 +21,10 @@ along with GNU Emacs. If not, see . */ #include "lisp.h" #include "androidterm.h" +#include "android.h" +#include "blockinput.h" +#include "keyboard.h" +#include "menu.h" #ifndef ANDROID_STUBIFY @@ -35,4 +39,293 @@ popup_activated (void) return popup_activated_flag; } + + +/* Toolkit menu implementation. */ + +/* Structure describing the EmacsContextMenu class. */ + +struct android_emacs_context_menu +{ + jclass class; + jmethodID create_context_menu; + jmethodID add_item; + jmethodID add_submenu; + jmethodID add_pane; + jmethodID parent; + jmethodID display; +}; + +/* Identifiers associated with the EmacsContextMenu class. */ +static struct android_emacs_context_menu menu_class; + +static void +android_init_emacs_context_menu (void) +{ + jclass old; + + menu_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsContextMenu"); + eassert (menu_class.class); + + old = menu_class.class; + menu_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!menu_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + menu_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + menu_class.class, \ + name, signature); \ + eassert (menu_class.c_name); + +#define FIND_METHOD_STATIC(c_name, name, signature) \ + menu_class.c_name \ + = (*android_java_env)->GetStaticMethodID (android_java_env, \ + menu_class.class, \ + name, signature); \ + eassert (menu_class.c_name); + + FIND_METHOD_STATIC (create_context_menu, "createContextMenu", + "(Ljava/lang/String;)Lorg/gnu/emacs/EmacsContextMenu;"); + + FIND_METHOD (add_item, "addItem", "(ILjava/lang/String;Z)V"); + FIND_METHOD (add_submenu, "addSubmenu", "(Ljava/lang/String;" + "Ljava/lang/String;)Lorg/gnu/emacs/EmacsContextMenu;"); + FIND_METHOD (add_pane, "addPane", "(Ljava/lang/String;)V"); + FIND_METHOD (parent, "parent", "()Lorg/gnu/emacs/EmacsContextMenu;"); + FIND_METHOD (display, "display", "(Lorg/gnu/emacs/EmacsWindow;II)Z"); + +#undef FIND_METHOD +#undef FIND_METHOD_STATIC +} + +static void +android_unwind_local_frame (void) +{ + (*android_java_env)->PopLocalFrame (android_java_env, NULL); +} + +/* Push a local reference frame to the JVM stack and record it on the + specpdl. Release local references created within that frame when + the specpdl is unwound past where it is after returning. */ + +static void +android_push_local_frame (void) +{ + int rc; + + rc = (*android_java_env)->PushLocalFrame (android_java_env, 30); + + /* This means the JVM ran out of memory. */ + if (rc < 1) + android_exception_check (); + + record_unwind_protect_void (android_unwind_local_frame); +} + +Lisp_Object +android_menu_show (struct frame *f, int x, int y, int menuflags, + Lisp_Object title, const char **error_name) +{ + jobject context_menu, current_context_menu; + jobject title_string, temp; + size_t i; + Lisp_Object pane_name, prefix; + const char *pane_string; + specpdl_ref count, count1; + Lisp_Object item_name, enable, def; + jmethodID method; + jobject store; + bool rc; + jobject window; + + count = SPECPDL_INDEX (); + + block_input (); + + /* Push the first local frame. */ + android_push_local_frame (); + + /* Push the first local frame for the context menu. */ + title_string = (!NILP (title) + ? (jobject) android_build_string (title) + : NULL); + method = menu_class.create_context_menu; + current_context_menu = context_menu + = (*android_java_env)->CallStaticObjectMethod (android_java_env, + menu_class.class, + method, + title_string); + + if (title_string) + ANDROID_DELETE_LOCAL_REF (title_string); + + /* Push the second local frame for temporaries. */ + count1 = SPECPDL_INDEX (); + android_push_local_frame (); + + /* Iterate over the menu. */ + i = 0; + + while (i < menu_items_used) + { + if (NILP (AREF (menu_items, i))) + { + /* This is the start of a new submenu. However, it can be + ignored here. */ + i += 1; + } + else if (EQ (AREF (menu_items, i), Qlambda)) + { + /* This is the end of a submenu. Go back to the previous + context menu. */ + store = current_context_menu; + current_context_menu + = (*android_java_env)->CallObjectMethod (android_java_env, + current_context_menu, + menu_class.parent); + android_exception_check (); + + if (store != context_menu) + ANDROID_DELETE_LOCAL_REF (store); + i += 1; + + eassert (current_context_menu); + } + else if (EQ (AREF (menu_items, i), Qquote)) + i += 1; + else if (EQ (AREF (menu_items, i), Qt)) + { + /* This is a new pane. Switch back to the topmost context + menu. */ + if (current_context_menu != context_menu) + ANDROID_DELETE_LOCAL_REF (current_context_menu); + current_context_menu = context_menu; + + /* Now figure out the title of this pane. */ + pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME); + prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX); + pane_string = (NILP (pane_name) + ? "" : SSDATA (pane_name)); + if ((menuflags & MENU_KEYMAPS) && !NILP (prefix)) + pane_string++; + + /* Add the pane. */ + temp = (*android_java_env)->NewStringUTF (android_java_env, + pane_string); + android_exception_check (); + + (*android_java_env)->CallVoidMethod (android_java_env, + current_context_menu, + menu_class.add_pane, + temp); + android_exception_check (); + ANDROID_DELETE_LOCAL_REF (temp); + + i += MENU_ITEMS_PANE_LENGTH; + } + else + { + item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME); + enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE); + def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION); + + /* This is an actual menu item (or submenu). Add it to the + menu. */ + + if (i + MENU_ITEMS_ITEM_LENGTH < menu_items_used && + NILP (AREF (menu_items, i + MENU_ITEMS_ITEM_LENGTH))) + { + /* This is a submenu. Add it. */ + title_string = (!NILP (item_name) + ? android_build_string (item_name) + : NULL); + store = current_context_menu; + current_context_menu + = (*android_java_env)->CallObjectMethod (android_java_env, + current_context_menu, + menu_class.add_submenu, + title_string); + android_exception_check (); + + if (store != context_menu) + ANDROID_DELETE_LOCAL_REF (store); + + if (title_string) + ANDROID_DELETE_LOCAL_REF (title_string); + } + else if (NILP (def) && menu_separator_name_p (SSDATA (item_name))) + /* Ignore this separator item. */ + ; + else + { + /* Add this menu item with the appropriate state. */ + + title_string = (!NILP (item_name) + ? android_build_string (item_name) + : NULL); + (*android_java_env)->CallVoidMethod (android_java_env, + current_context_menu, + menu_class.add_item, + (jint) 1, + title_string, + (jboolean) !NILP (enable)); + android_exception_check (); + + if (title_string) + ANDROID_DELETE_LOCAL_REF (title_string); + } + + i += MENU_ITEMS_ITEM_LENGTH; + } + } + + /* The menu has now been built. Pop the second local frame. */ + unbind_to (count1, Qnil); + + /* Now, display the context menu. */ + window = android_resolve_handle (FRAME_ANDROID_WINDOW (f), + ANDROID_HANDLE_WINDOW); + rc = (*android_java_env)->CallBooleanMethod (android_java_env, + context_menu, + window, (jint) x, + (jint) y); + android_exception_check (); + + if (!rc) + /* This means displaying the menu failed. */ + goto finish; + +#if 0 + record_unwind_protect_ptr (android_dismiss_menu, &context_menu); + + /* Otherwise, loop waiting for the menu event to arrive. */ + android_process_events_for_menu (&id); + + if (!id) + /* This means no menu item was selected. */ + goto finish; + +#endif + + finish: + unblock_input (); + return unbind_to (count, Qnil); +} + +#endif + +void +init_androidmenu (void) +{ +#ifndef ANDROID_STUBIFY + android_init_emacs_context_menu (); #endif +} diff --git a/src/androidterm.c b/src/androidterm.c index a8325312498..002d39af707 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -559,6 +559,9 @@ handle_one_android_event (struct android_display_info *dpyinfo, f = android_window_to_frame (dpyinfo, configureEvent.xconfigure.window); + if (!f) + goto OTHER; + int width = configureEvent.xconfigure.width; int height = configureEvent.xconfigure.height; @@ -884,10 +887,6 @@ handle_one_android_event (struct android_display_info *dpyinfo, inev.ie.arg = tab_bar_arg; } } - else - { - /* TODO: scroll bars */ - } if (event->type == ANDROID_BUTTON_PRESS) { @@ -1451,7 +1450,12 @@ android_make_frame_visible_invisible (struct frame *f, bool visible) static void android_fullscreen_hook (struct frame *f) { - /* TODO */ + /* Explicitly setting fullscreen is not supported on Android. */ + + if (!FRAME_PARENT_FRAME (f)) + store_frame_param (f, Qfullscreen, Qmaximized); + else + store_frame_param (f, Qfullscreen, Qnil); } void @@ -2360,7 +2364,7 @@ android_draw_box_rect (struct glyph_string *s, /* Top. */ android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x, - left_x, right_x - left_x + 1, hwidth); + top_y, right_x - left_x + 1, hwidth); /* Left. */ if (left_p) @@ -3958,7 +3962,14 @@ frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y) char * get_keysym_name (int keysym) { - return (char *) "UNKNOWN KEYSYM"; + static char buffer[64]; + +#ifndef ANDROID_STUBIFY + android_get_keysym_name (keysym, buffer, 64); +#else + emacs_abort (); +#endif + return buffer; } @@ -4009,20 +4020,13 @@ android_create_terminal (struct android_display_info *dpyinfo) terminal->set_new_font_hook = android_new_font; terminal->set_bitmap_icon_hook = android_bitmap_icon; terminal->implicit_set_name_hook = android_implicitly_set_name; - /* terminal->menu_show_hook = android_menu_show; XXX */ + terminal->menu_show_hook = android_menu_show; terminal->change_tab_bar_height_hook = android_change_tab_bar_height; terminal->change_tool_bar_height_hook = android_change_tool_bar_height; - /* terminal->set_vertical_scroll_bar_hook */ - /* = android_set_vertical_scroll_bar; */ - /* terminal->set_horizontal_scroll_bar_hook */ - /* = android_set_horizontal_scroll_bar; */ terminal->set_scroll_bar_default_width_hook = android_set_scroll_bar_default_width; terminal->set_scroll_bar_default_height_hook = android_set_scroll_bar_default_height; - /* terminal->condemn_scroll_bars_hook = android_condemn_scroll_bars; */ - /* terminal->redeem_scroll_bars_hook = android_redeem_scroll_bars; */ - /* terminal->judge_scroll_bars_hook = android_judge_scroll_bars; */ terminal->free_pixmap = android_free_pixmap_hook; terminal->delete_frame_hook = android_delete_frame; terminal->delete_terminal_hook = android_delete_terminal; diff --git a/src/androidterm.h b/src/androidterm.h index ebde15c40a8..e83e32a5854 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -402,6 +402,12 @@ extern void syms_of_androidfont (void); extern void android_finalize_font_entity (struct font_entity *); +/* Defined in androidmenu.c. */ + +extern Lisp_Object android_menu_show (struct frame *, int, int, int, + Lisp_Object, const char **); +extern void init_androidmenu (void); + /* Defined in sfntfont-android.c. */ extern const struct font_driver android_sfntfont_driver; diff --git a/src/emacs.c b/src/emacs.c index e3f6c7d66f7..8f5be53aad9 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -2499,6 +2499,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem init_window (); init_font (); +#ifdef HAVE_ANDROID + init_androidmenu (); +#endif + #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY init_androidfont (); init_sfntfont (); diff --git a/xcompile/Makefile.in b/xcompile/Makefile.in index ed2b10b91a7..9f817bb4c53 100644 --- a/xcompile/Makefile.in +++ b/xcompile/Makefile.in @@ -110,7 +110,7 @@ lib/libgnu.a: src/verbose.mk config.status $(LIB_DEPS) $(PRE_BUILD_DEPS) +make -C lib libgnu.a src/Makefile src/config.h &: $(top_builddir)/src/config.h.android \ - $(top_builddir)/src/Makefile.android $(PRE_BUILD_DEPS) + $(top_builddir)/src/Makefile.android mkdir -p src src/deps # Copy config.h to src/ cp -f -p $(top_builddir)/src/config.h.android src/config.h @@ -155,13 +155,20 @@ $(LIBSRC_BINARIES) &: src/verbose.mk $(top_builddir)/$@ lib/libgnu.a \ # Finally, go into lib-src and make everything being built +make -C lib-src $(foreach bin,$(LIBSRC_BINARIES),$(notdir $(bin))) -.PHONY: clean maintainer-clean +.PHONY: clean maintainer-clean distclean clean: rm -rf $(CLEAN_SUBDIRS) *.bak sys if [ -e lib/Makefile ]; then \ make -C lib clean; \ fi - rm -rf lib/gnulib.mk lib/Makefile lib/config.h + rm -rf lib/config.h + +distclean bootstrap-clean: clean + if [ -e lib/Makefile ]; then \ + make -C lib distclean; \ + fi +# Just in case. + rm -rf lib/Makefile lib/gnulib.mk maintainer-clean: clean if [ -e lib/Makefile ]; then \ -- cgit v1.3 From ad59d8986aee4498c0427449e024341d1a195a2d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 16 Jan 2023 19:50:02 +0800 Subject: Update Android port * doc/emacs/android.texi (Android, Android Environment): Improve documentation. * doc/lispref/commands.texi (Touchscreen Events): Document changes to touchscreen support. * doc/lispref/display.texi (Defining Faces, Window Systems): * doc/lispref/frames.texi (Frame Layout, Font and Color Parameters): * doc/lispref/os.texi (System Environment): Document Android in various places. * java/org/gnu/emacs/EmacsWindow.java (figureChange): Fix crash. * lisp/loadup.el: ("touch-screen"): Load touch-screen.el. * lisp/pixel-scroll.el: Autoload two functions. * lisp/term/android-win.el: Add require 'touch-screen. * lisp/touch-screen.el (touch-screen-current-tool) (touch-screen-current-timer, touch-screen-delay) (touch-screen-relative-xy, touch-screen-handle-scroll) (touch-screen-handle-timeout, touch-screen-handle-point-update) (touch-screen-handle-point-up, touch-screen-handle-touch) (global-map, touch-screen): New file. * src/android.c (android_run_debug_thread): Fix build on 64 bit systems. (JNICALL, android_put_pixel): Likewise. (android_transform_coordinates, android_four_corners_bilinear) (android_fetch_pixel_bilinear, android_project_image_bilinear) (android_fetch_pixel_nearest_24, android_fetch_pixel_nearest_1) (android_project_image_nearest): New functions. * src/androidgui.h (struct android_transform): New structure. * src/androidterm.c (android_note_mouse_movement): Remove obsolete TODO. (android_get_scale_factor): New function. (android_draw_underwave): Scale underwave correctly. * src/dispextern.h: Support native image transforms on Android. * src/image.c (matrix_identity, matrix_rotate) (matrix_mirror_horizontal, matrix_translate): New functions. (image_set_transform): Implement native image transforms on Android. (Fimage_transforms_p): Implement on Android. * src/keyboard.c (make_lispy_event, syms_of_keyboard): Handle touch screen- menu bar events. * src/sfnt.c: Fix typo in comment. * src/sfntfont-android.c (sfntfont_android_blend, U255TO256) (sfntfont_android_put_glyphs): Avoid redundant swizzling. * src/sfntfont.c (sfntfont_lookup_char): Fix build on 64 bit systems. --- doc/emacs/android.texi | 56 ++++++- doc/lispref/commands.texi | 5 + doc/lispref/display.texi | 7 +- doc/lispref/frames.texi | 3 +- doc/lispref/os.texi | 3 + java/org/gnu/emacs/EmacsWindow.java | 4 +- lisp/loadup.el | 4 + lisp/pixel-scroll.el | 2 + lisp/term/android-win.el | 1 + lisp/touch-screen.el | 322 ++++++++++++++++++++++++++++++++++++ src/android.c | 282 ++++++++++++++++++++++++++++++- src/androidgui.h | 19 +++ src/androidterm.c | 28 +++- src/dispextern.h | 5 +- src/image.c | 310 ++++++++++++++++++++++++++++++++-- src/keyboard.c | 70 ++++++++ src/sfnt.c | 2 +- src/sfntfont-android.c | 55 +++--- src/sfntfont.c | 2 +- 19 files changed, 1115 insertions(+), 65 deletions(-) create mode 100644 lisp/touch-screen.el (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 57ef10de9f3..b5a91b0f98f 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -14,6 +14,7 @@ an Android device running Android 2.2 or later. * Android Startup:: Starting up Emacs on Android. * Android File System:: The Android file system. * Android Environment:: Running Emacs under Android. +* Android Fonts:: Font selection under Android. @end menu @node What is Android? @@ -293,5 +294,56 @@ This strategy works as long as one window is in the foreground. Otherwise, Emacs can only run in the background for a limited amount of time before the process is killed completely. -@c TODO: write more documentation here about what is supported and -@c what is not, and fonts. +@cindex windowing limitations, android +@cindex frame parameters, android +Due to the unusual nature of the Android windowing environment, Emacs +only supports a limited subset of GUI features. Here is a list of +known limitations, and features which are not implemented: + +@itemize @bullet +@item +The functions @code{raise-frame} and @code{lower-frame} are +non-functional, because of bugs in the window system. + +@item +Scroll bars are not supported, as they are close to useless on Android +devices. + +@item +The @code{alpha}, @code{alpha-background}, @code{z-group}, +@code{override-redirect}, @code{mouse-color}, @code{cursor-color}, +@code{cursor-type}, @code{title}, @code{wait-for-wm}, @code{sticky}, +@code{undecorated} and @code{tool-bar-position} frame parameters +(@pxref{Frame Parameters,,, elisp, the Emacs Lisp Reference Manual}) +are unsupported. + +@item +The @code{fullscreen} frame parameter is always @code{maximized} for +top-level frames. +@end itemize + +@node Android Fonts +@section Font backends and selection under Android +@cindex fonts, android + + Emacs supports two font backends under Android: they are respectively +named @code{sfnt-android} and @code{android}. + +Upon startup, Emacs enumerates all the TrueType format fonts in the +directory @file{/system/fonts}; this is where the Android system +places fonts. Emacs assumes there will always be a font named ``Droid +Sans Mono'', and then defaults to using this font. These fonts are +then rendered by the @code{sfnt-android} font driver. + +When running on Android, Emacs currently lacks support for TrueType +Container and OpenType fonts. This means that only a subset of the +fonts installed on the system are currently available to Emacs. If +you are interested in raising this limitation, please contact +@email{emacs-devel@@gnu.org}. + +If the @code{sfnt-android} font driver fails to find any fonts at all, +Emacs falls back to the @code{android} font driver. This is a very +lousy font driver, because of limitations and inaccuracies in the font +metrics provided by the Android platform. In that case, Emacs uses +the ``Monospace'' typeface configured on your system; this should +always be Droid Sans Mono. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index dc78adc4520..14702ce6efa 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2018,6 +2018,11 @@ display, because another program took the grab, or because the user raised the finger from the touchscreen. @end table +If a touchpoint is pressed against the menu bar, then Emacs will not +generate any corresponding @code{touchscreen-begin} or +@code{touchscreen-end} events; instead, the menu bar may be displayed +when @code{touchscreen-end} should have been delivered. + @node Focus Events @subsection Focus Events @cindex focus event diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 5a9a9f95f7b..097ce8991ab 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -2846,8 +2846,9 @@ apply to. Here are the possible values of @var{characteristic}: The kind of window system the terminal uses---either @code{graphic} (any graphics-capable display), @code{x}, @code{pc} (for the MS-DOS console), @code{w32} (for MS Windows 9X/NT/2K/XP), @code{haiku} (for -Haiku), @code{pgtk} (for pure GTK), or @code{tty} (a non-graphics-capable -display). @xref{Window Systems, window-system}. +Haiku), @code{pgtk} (for pure GTK), @code{android} (for Android), or +@code{tty} (a non-graphics-capable display). @xref{Window Systems, +window-system}. @item class What kinds of colors the terminal supports---either @code{color}, @@ -8734,6 +8735,8 @@ Emacs is displaying the frame using MS-DOS direct screen writes. Emacs is displaying the frame using the Application Kit on Haiku. @item pgtk Emacs is displaying the frame using pure GTK facilities. +@item android +Emacs is displaying the frame on Android. @item nil Emacs is displaying the frame on a character-based terminal. @end table diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 68f31e500bb..fb96b96ec8c 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -695,7 +695,7 @@ The position of the top left corner of the native frame specifies the indicate that position for the various builds: @itemize @w{} -@item (1) non-toolkit, Haiku, and terminal frames +@item (1) non-toolkit, Android, Haiku, and terminal frames @item (2) Lucid, Motif, and MS-Windows frames @@ -2389,6 +2389,7 @@ engine), and @code{harfbuzz} (font driver for OTF and TTF fonts with HarfBuzz text shaping) (@pxref{Windows Fonts,,, emacs, The GNU Emacs Manual}). The @code{harfbuzz} driver is similarly recommended. On Haiku, there can be several font drivers (@pxref{Haiku Fonts,,, emacs, +The GNU Emacs Manual}), as on Android (@pxref{Android Fonts,,, emacs, The GNU Emacs Manual}). On other systems, there is only one available font backend, so it does diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index 3be7036f637..094e954e82b 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -970,6 +970,9 @@ Hewlett-Packard HPUX operating system. @item nacl Google Native Client (@acronym{NaCl}) sandboxing system. +@item android +The Open Handset Alliance's Android operating system. + @item ms-dos Microsoft's DOS@. Emacs compiled with DJGPP for MS-DOS binds @code{system-type} to @code{ms-dos} even when you run it on MS-Windows. diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 6effa79d1a4..7181bc89fea 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -637,8 +637,8 @@ public class EmacsWindow extends EmacsHandleObject pointerIndex = event.getActionIndex (); pointerID = event.getPointerId (pointerIndex); pointerMap.put (pointerID, - new Coordinate ((int) event.getX (pointerID), - (int) event.getY (pointerID))); + new Coordinate ((int) event.getX (pointerIndex), + (int) event.getY (pointerIndex))); break; case MotionEvent.ACTION_POINTER_UP: diff --git a/lisp/loadup.el b/lisp/loadup.el index c4044a3bb9d..3b48d5fe1b5 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -295,6 +295,10 @@ (if (featurep 'dynamic-setting) (load "dynamic-setting")) +;; touch-screen.el is tiny and is used liberally throughout the button +;; code etc, so it may as well be preloaded everywhere. +(load "touch-screen") + (if (featurep 'x) (progn (load "x-dnd") diff --git a/lisp/pixel-scroll.el b/lisp/pixel-scroll.el index 487144144f5..d3287d936bb 100644 --- a/lisp/pixel-scroll.el +++ b/lisp/pixel-scroll.el @@ -500,6 +500,7 @@ Otherwise, redisplay will reset the window's vscroll." (set-window-start nil (pixel-point-at-unseen-line) t) (set-window-vscroll nil vscroll t)) +;;;###autoload (defun pixel-scroll-precision-scroll-down-page (delta) "Scroll the current window down by DELTA pixels. Note that this function doesn't work if DELTA is larger than @@ -556,6 +557,7 @@ the height of the current window." (setq delta (- delta max-height))) (pixel-scroll-precision-scroll-down-page delta))) +;;;###autoload (defun pixel-scroll-precision-scroll-up-page (delta) "Scroll the current window up by DELTA pixels. Note that this function doesn't work if DELTA is larger than diff --git a/lisp/term/android-win.el b/lisp/term/android-win.el index 8aeabee567d..43b75d39f50 100644 --- a/lisp/term/android-win.el +++ b/lisp/term/android-win.el @@ -37,6 +37,7 @@ (require 'mouse) (require 'fontset) (require 'dnd) +(require 'touch-screen) (add-to-list 'display-format-alist '(".*" . android)) diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el new file mode 100644 index 00000000000..192a09b3a29 --- /dev/null +++ b/lisp/touch-screen.el @@ -0,0 +1,322 @@ +;;; touch-screen.el --- touch screen support for X and Android -*- lexical-binding: t; -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; Maintainer: emacs-devel@gnu.org +;; Package: emacs + +;; 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 . + +;;; Commentary: + +;; This file provides code to recognize simple touch screen gestures. +;; It is used on X and Android, where the platform cannot recognize +;; them for us. + +;;; Code: + +(defvar touch-screen-current-tool nil + "The touch point currently being tracked, or nil. +If non-nil, this is a list of five elements: the ID of the touch +point being tracked, the window where the touch began, a cons +containing the last known position of the touch point, relative +to that window, a field used to store data while tracking the +touch point, and the initial position of the touchpoint. See +`touch-screen-handle-point-update' for the meanings of the fourth +element.") + +(defvar touch-screen-current-timer nil + "Timer used to track long-presses. +This is always cleared upon any significant state change.") + +(defcustom touch-screen-delay 0.7 + "Delay in seconds before Emacs considers a touch to be a long-press." + :type 'number + :group 'mouse + :version "30.1") + +(defun touch-screen-relative-xy (posn window) + "Return the coordinates of POSN, a mouse position list. +However, return the coordinates relative to WINDOW. + +If (posn-window posn) is the same as window, simply return the +coordinates in POSN. Otherwise, convert them to the frame, and +then back again." + (if (eq (posn-window posn) window) + (posn-x-y posn) + (let ((xy (posn-x-y posn)) + (edges (window-inside-pixel-edges window))) + ;; Make the X and Y positions frame relative. + (when (windowp (posn-window posn)) + (let ((edges (window-inside-pixel-edges + (posn-window posn)))) + (setq xy (cons (+ (car xy) (car edges)) + (+ (cdr xy) (cadr edges)))))) + ;; Make the X and Y positions window relative again. + (cons (- (car xy) (car edges)) + (- (cdr xy) (cadr edges)))))) + +(defun touch-screen-handle-scroll (dx dy) + "Scroll the display assuming that a touch point has moved by DX and DY." + (ignore dx) + ;; This only looks good with precision pixel scrolling. + (if (> dy 0) + (pixel-scroll-precision-scroll-down-page dy) + (pixel-scroll-precision-scroll-up-page (- dy)))) + +(defun touch-screen-handle-timeout (arg) + "Start the touch screen timeout or handle it depending on ARG. +When ARG is nil, start the `touch-screen-current-timer' to go off +in `touch-screen-delay' seconds, and call this function with ARG +t. + +When ARG is t, beep. Then, set the fourth element of +touch-screen-current-tool to `held', and the mark to the last +known position of the tool." + (if (not arg) + ;; Cancel the touch screen long-press timer, if it is still + ;; there by any chance. + (progn + (when touch-screen-current-timer + (cancel-timer touch-screen-current-timer)) + (setq touch-screen-current-timer + (run-at-time touch-screen-delay nil + #'touch-screen-handle-timeout + t))) + ;; Beep. + (beep) + ;; Set touch-screen-current-timer to nil. + (setq touch-screen-current-timer nil) + (when touch-screen-current-tool + ;; Set the state to `held'. + (setcar (nthcdr 3 touch-screen-current-tool) 'held) + ;; Go to the initial position of the touchpoint and activate the + ;; mark. + (with-selected-window (cadr touch-screen-current-tool) + (set-mark (posn-point (nth 4 touch-screen-current-tool))) + (goto-char (mark)) + (activate-mark))))) + +(defun touch-screen-handle-point-update (point) + "Notice that the touch point POINT has changed position. +POINT must be the touch point currently being tracked as +`touch-screen-current-tool'. + +If the fourth element of `touch-screen-current-tool' is nil, then +the touch has just begun. Determine how much POINT has moved. +If POINT has moved upwards or downwards by a significant amount, +then set the fourth element to `scroll'. Then, call +`touch-screen-handle-scroll' to scroll the display by that +amount. + +If the fourth element of `touch-screen-current-tool' is `scroll', +then scroll the display by how much POINT has moved in the Y +axis. + +If the fourth element of `touch-screen-current-tool' is `held', +then the touch has been held down for some time. If motion +happens, cancel `touch-screen-current-timer', and set the field +to `drag'. Then, activate the mark and start dragging. + +If the fourth element of `touch-screen-current-tool' is `drag', +then move point to the position of POINT. + +Set `touch-screen-current-tool' to nil should any error occur." + (let ((window (nth 1 touch-screen-current-tool)) + (what (nth 3 touch-screen-current-tool))) + (cond ((null what) + (let* ((posn (cdr point)) + (last-posn (nth 2 touch-screen-current-tool)) + ;; Now get the position of X and Y relative to + ;; WINDOW. + (relative-xy + (touch-screen-relative-xy posn window)) + (diff-x (- (car last-posn) (car relative-xy))) + (diff-y (- (cdr last-posn) (cdr relative-xy)))) + ;; Decide whether or not to start scrolling. + (when (or (> diff-y 10) (> diff-x 10) + (< diff-y -10) (< diff-x -10)) + (setcar (nthcdr 3 touch-screen-current-tool) + 'scroll) + (setcar (nthcdr 2 touch-screen-current-tool) + relative-xy) + (with-selected-window window + (touch-screen-handle-scroll diff-x diff-y)) + ;; Cancel the touch screen long-press timer, if it is + ;; still there by any chance. + (when touch-screen-current-timer + (cancel-timer touch-screen-current-timer) + (setq touch-screen-current-timer nil))))) + ((eq what 'scroll) + ;; Cancel the touch screen long-press timer, if it is still + ;; there by any chance. + (when touch-screen-current-timer + (cancel-timer touch-screen-current-timer) + (setq touch-screen-current-timer nil)) + (let* ((posn (cdr point)) + (last-posn (nth 2 touch-screen-current-tool)) + ;; Now get the position of X and Y relative to + ;; WINDOW. + (relative-xy + (touch-screen-relative-xy posn window)) + (diff-x (- (car last-posn) (car relative-xy))) + (diff-y (- (cdr last-posn) (cdr relative-xy)))) + (setcar (nthcdr 3 touch-screen-current-tool) + 'scroll) + (setcar (nthcdr 2 touch-screen-current-tool) + relative-xy) + (unless (and (zerop diff-x) (zerop diff-y)) + (with-selected-window window + (touch-screen-handle-scroll diff-x diff-y))))) + ((eq what 'held) + (let* ((posn (cdr point)) + (relative-xy + (touch-screen-relative-xy posn window))) + (when touch-screen-current-timer + (cancel-timer touch-screen-current-timer) + (setq touch-screen-current-timer nil)) + ;; Now start dragging. + (setcar (nthcdr 3 touch-screen-current-tool) + 'drag) + (setcar (nthcdr 2 touch-screen-current-tool) + relative-xy) + (with-selected-window window + ;; Activate the mark. It should have been set by the + ;; time `touch-screen-timeout' was called. + (activate-mark) + + ;; Figure out what character to go to. If this posn is + ;; in the window, go to (posn-point posn). If not, + ;; then go to the line before either window start or + ;; window end. + (if (and (eq (posn-window posn) window) + (posn-point posn)) + (goto-char (posn-point posn)) + (let ((relative-xy + (touch-screen-relative-xy posn window))) + (let ((scroll-conservatively 101)) + (cond + ((< (cdr relative-xy) 0) + (ignore-errors + (goto-char (1- (window-start)))) + (redisplay)) + ((> (cdr relative-xy) + (let ((edges (window-inside-pixel-edges))) + (- (nth 3 edges) (cadr edges)))) + (ignore-errors + (goto-char (1+ (window-end nil t)))) + (redisplay))))))))) + ((eq what 'drag) + (let* ((posn (cdr point))) + ;; Keep dragging. + (with-selected-window window + ;; Figure out what character to go to. If this posn is + ;; in the window, go to (posn-point posn). If not, + ;; then go to the line before either window start or + ;; window end. + (if (and (eq (posn-window posn) window) + (posn-point posn)) + (goto-char (posn-point posn)) + (let ((relative-xy + (touch-screen-relative-xy posn window))) + (let ((scroll-conservatively 101)) + (cond + ((< (cdr relative-xy) 0) + (ignore-errors + (goto-char (1- (window-start)))) + (redisplay)) + ((> (cdr relative-xy) + (let ((edges (window-inside-pixel-edges))) + (- (nth 3 edges) (cadr edges)))) + (ignore-errors + (goto-char (1+ (window-end nil t)))) + (redisplay)))))))))))) + +(defun touch-screen-handle-point-up (point) + "Notice that POINT has been removed from the screen. +POINT should be the point currently tracked as +`touch-screen-current-tool'. + +If the fourth argument of `touch-screen-current-tool' is nil, +move point to the position of POINT, selecting the window under +POINT as well; if there is a button at POINT, then activate the +button there. Otherwise, deactivate the mark. Then, display the +on-screen keyboard." + (let ((what (nth 3 touch-screen-current-tool))) + (cond ((null what) + (when (windowp (posn-window (cdr point))) + ;; Select the window that was tapped. + (select-window (posn-window (cdr point))) + (let ((button (button-at (posn-point (cdr point))))) + (when button + (button-activate button t)) + (goto-char (posn-point (cdr point))) + (deactivate-mark))))))) + +(defun touch-screen-handle-touch (event) + "Handle a single touch EVENT, and perform associated actions. +EVENT can either be a touchscreen-begin, touchscreen-update or +touchscreen-end event." + (interactive "e") + (cond + ((eq (car event) 'touchscreen-begin) + ;; A tool was just pressed against the screen. Figure out the + ;; window where it is and make it the tool being tracked on the + ;; window. + (let ((touchpoint (caadr event)) + (position (cdadr event))) + ;; Cancel the touch screen timer, if it is still there by any + ;; chance. + (when touch-screen-current-timer + (cancel-timer touch-screen-current-timer) + (setq touch-screen-current-timer nil)) + ;; Replace any previously ongoing gesture. If POSITION has no + ;; window or position, make it nil instead. + (setq touch-screen-current-tool (and (windowp (posn-window position)) + (posn-point position) + (list touchpoint + (posn-window position) + (posn-x-y position) + nil position))) + ;; Start the long-press timer. + (touch-screen-handle-timeout nil))) + ((eq (car event) 'touchscreen-update) + ;; The positions of tools currently pressed against the screen + ;; have changed. If there is a tool being tracked as part of a + ;; gesture, look it up in the list of tools. + (let ((new-point (assq (car touch-screen-current-tool) + (cadr event)))) + (when new-point + (touch-screen-handle-point-update new-point)))) + ((eq (car event) 'touchscreen-end) + ;; A tool has been removed from the screen. If it is the tool + ;; currently being tracked, clear `touch-screen-current-tool'. + (when (eq (caadr event) (car touch-screen-current-tool)) + ;; Cancel the touch screen long-press timer, if it is still there + ;; by any chance. + (when touch-screen-current-timer + (cancel-timer touch-screen-current-timer) + (setq touch-screen-current-timer nil)) + (touch-screen-handle-point-up (cadr event)) + (setq touch-screen-current-tool nil))))) + +(define-key global-map [touchscreen-begin] #'touch-screen-handle-touch) +(define-key global-map [touchscreen-update] #'touch-screen-handle-touch) +(define-key global-map [touchscreen-end] #'touch-screen-handle-touch) + +(provide 'touch-screen) + +;;; touch-screen ends here diff --git a/src/android.c b/src/android.c index 3a965286460..9b15ea9f15a 100644 --- a/src/android.c +++ b/src/android.c @@ -25,9 +25,11 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include #include #include +#include #include @@ -513,7 +515,7 @@ android_run_debug_thread (void *data) char *line; size_t n; - fd = (int) data; + fd = (int) (intptr_t) data; file = fdopen (fd, "r"); if (!file) @@ -958,7 +960,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, close (pipefd[1]); if (pthread_create (&thread, NULL, android_run_debug_thread, - (void *) pipefd[0])) + (void *) (intptr_t) pipefd[0])) emacs_abort (); /* Now set the path to the site load directory. */ @@ -2829,6 +2831,7 @@ android_put_pixel (struct android_image *ximg, int x, int y, { char *byte, *word; unsigned int r, g, b; + unsigned int pixel_int; /* Ignore out-of-bounds accesses. */ @@ -2859,7 +2862,8 @@ android_put_pixel (struct android_image *ximg, int x, int y, b = pixel & 0x000000ff; pixel = (r >> 16) | g | (b << 16) | 0xff000000; - memcpy (word, &pixel, sizeof pixel); + pixel_int = pixel; + memcpy (word, &pixel_int, sizeof pixel_int); break; } } @@ -3734,6 +3738,262 @@ android_exception_check (void) } } + + +/* Native image transforms. */ + +/* Transform the coordinates X and Y by the specified affine + transformation MATRIX. Place the result in *XOUT and *YOUT. */ + +static void +android_transform_coordinates (int x, int y, + struct android_transform *transform, + float *xout, float *yout) +{ + /* Apply the specified affine transformation. + A transform looks like: + + M1 M2 M3 X + M4 M5 M6 * Y + + = + + M1*X + M2*Y + M3*1 = X1 + M4*X + M5*Y + M6*1 = Y1 + + (In most transforms, there is another row at the bottom for + mathematical reasons. Since Z1 is always 1.0, the row is simply + implied to be 0 0 1, because 0 * x + 0 * y + 1 * 1 = 1.0. See + the definition of matrix3x3 in image.c for some more explanations + about this.) */ + + *xout = transform->m1 * x + transform->m2 * y + transform->m3; + *yout = transform->m4 * x + transform->m5 * y + transform->m6; +} + +/* Return the interpolation of the four pixels TL, TR, BL, and BR, + according to the weights DISTX and DISTY. */ + +static unsigned int +android_four_corners_bilinear (unsigned int tl, unsigned int tr, + unsigned int bl, unsigned int br, + int distx, int disty) +{ + int distxy, distxiy, distixy, distixiy; + uint32_t f, r; + + distxy = distx * disty; + distxiy = (distx << 8) - distxy; + distixy = (disty << 8) - distxy; + distixiy = (256 * 256 - (disty << 8) + - (distx << 8) + distxy); + + /* Red */ + r = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy + + (bl & 0x000000ff) * distixy + (br & 0x000000ff) * distxy); + + /* Green */ + f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy + + (bl & 0x0000ff00) * distixy + (br & 0x0000ff00) * distxy); + r |= f & 0xff000000; + + /* Now do the upper two components. */ + tl >>= 16; + tr >>= 16; + bl >>= 16; + br >>= 16; + r >>= 16; + + /* Blue */ + f = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy + + (bl & 0x000000ff) * distixy + (br & 0x000000ff) * distxy); + r |= f & 0x00ff0000; + + /* Alpha */ + f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy + + (bl & 0x0000ff00) * distixy + (br & 0x0000ff00) * distxy); + r |= f & 0xff000000; + + return r; +} + +/* Return the interpolation of the four pixels closest to at X, Y in + IMAGE, according to weights in both axes computed from X and Y. + IMAGE must be depth 24, or the behavior is undefined. */ + +static unsigned int +android_fetch_pixel_bilinear (struct android_image *image, + float x, float y) +{ + int x1, y1, x2, y2; + float distx, disty; + unsigned int top_left, top_right; + unsigned int bottom_left, bottom_right; + char *word; + + /* Compute the four closest corners to X and Y. */ + x1 = (int) x; + x2 = x1 + 1; + y1 = (int) y; + y2 = y1 + 1; + + /* Make sure all four corners are within range. */ + x1 = MAX (0, MIN (image->width - 1, x1)); + y1 = MAX (0, MIN (image->height - 1, y1)); + x2 = MAX (0, MIN (image->width - 1, x2)); + y2 = MAX (0, MIN (image->height - 1, y2)); + + /* Compute the X and Y biases. These are numbers between 0f and + 1f. */ + distx = x - x1; + disty = y - y1; + + /* Fetch the four closest pixels. */ + word = image->data + y1 * image->bytes_per_line + x1 * 4; + memcpy (&top_left, word, sizeof top_left); + word = image->data + y1 * image->bytes_per_line + x2 * 4; + memcpy (&top_right, word, sizeof top_right); + word = image->data + y2 * image->bytes_per_line + x1 * 4; + memcpy (&bottom_left, word, sizeof bottom_left); + word = image->data + y2 * image->bytes_per_line + x2 * 4; + memcpy (&bottom_right, word, sizeof bottom_right); + + /* Do the interpolation. */ + return android_four_corners_bilinear (top_left, top_right, bottom_left, + bottom_right, distx * 256, + disty * 256); +} + +/* Transform the depth 24 image IMAGE by the 3x2 affine transformation + matrix MATRIX utilizing a bilinear filter. Place the result in + OUT. The matrix maps from the coordinate space of OUT to + IMAGE. */ + +void +android_project_image_bilinear (struct android_image *image, + struct android_image *out, + struct android_transform *transform) +{ + int x, y; + unsigned int pixel; + float xout, yout; + char *word; + + /* Loop through each pixel in OUT. Transform it by TRANSFORM, then + interpolate it to IMAGE, and place the result back in OUT. */ + + for (y = 0; y < out->height; ++y) + { + for (x = 0; x < out->width; ++x) + { + /* Transform the coordinates by TRANSFORM. */ + android_transform_coordinates (x, y, transform, + &xout, &yout); + + /* Interpolate back to IMAGE. */ + pixel = android_fetch_pixel_bilinear (image, xout, yout); + + /* Put the pixel back in OUT. */ + word = out->data + y * out->bytes_per_line + x * 4; + memcpy (word, &pixel, sizeof pixel); + } + } +} + +/* Return the interpolation of X, Y to IMAGE, a depth 24 image. */ + +static unsigned int +android_fetch_pixel_nearest_24 (struct android_image *image, float x, + float y) +{ + int x1, y1; + char *word; + unsigned int pixel; + + x1 = MAX (0, MIN (image->width - 1, (int) roundf (x))); + y1 = MAX (0, MIN (image->height - 1, (int) roundf (y))); + + word = image->data + y1 * image->bytes_per_line + x1 * 4; + memcpy (&pixel, word, sizeof pixel); + + return pixel; +} + +/* Return the interpolation of X, Y to IMAGE, a depth 1 image. */ + +static unsigned int +android_fetch_pixel_nearest_1 (struct android_image *image, float x, + float y) +{ + int x1, y1; + char *byte; + + x1 = MAX (0, MIN (image->width - 1, (int) roundf (x))); + y1 = MAX (0, MIN (image->height - 1, (int) roundf (y))); + + byte = image->data + y1 * image->bytes_per_line; + return (byte[x1 / 8] & (1 << x1 % 8)) ? 1 : 0; +} + +/* Transform the depth 24 or 1 image IMAGE by the 3x2 affine + transformation matrix MATRIX. Place the result in OUT. The matrix + maps from the coordinate space of OUT to IMAGE. Use a + nearest-neighbor filter. */ + +void +android_project_image_nearest (struct android_image *image, + struct android_image *out, + struct android_transform *transform) +{ + int x, y; + unsigned int pixel; + float xout, yout; + char *word, *byte; + + if (image->depth == 1) + { + for (y = 0; y < out->height; ++y) + { + for (x = 0; x < out->width; ++x) + { + /* Transform the coordinates by TRANSFORM. */ + android_transform_coordinates (x, y, transform, + &xout, &yout); + + /* Interpolate back to IMAGE. */ + pixel = android_fetch_pixel_nearest_1 (image, xout, yout); + + /* Put the pixel back in OUT. */ + byte = out->data + y * out->bytes_per_line + x / 8; + + if (pixel) + *byte |= (1 << x % 8); + else + *byte &= ~(1 << x % 8); + } + } + + return; + } + + for (y = 0; y < out->height; ++y) + { + for (x = 0; x < out->width; ++x) + { + /* Transform the coordinates by TRANSFORM. */ + android_transform_coordinates (x, y, transform, + &xout, &yout); + + /* Interpolate back to IMAGE. */ + pixel = android_fetch_pixel_nearest_24 (image, xout, yout); + + /* Put the pixel back in OUT. */ + word = out->data + y * out->bytes_per_line + x * 4; + memcpy (word, &pixel, sizeof pixel); + } + } +} + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ @@ -3793,4 +4053,20 @@ android_put_image (android_pixmap pixmap, emacs_abort (); } +void +android_project_image_bilinear (struct android_image *image, + struct android_image *out, + struct android_transform *transform) +{ + emacs_abort (); +} + +void +android_project_image_nearest (struct android_image *image, + struct android_image *out, + struct android_transform *transform) +{ + emacs_abort (); +} + #endif diff --git a/src/androidgui.h b/src/androidgui.h index 9df5b073a7c..1f28c18ff34 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -542,6 +542,25 @@ extern struct android_image *android_get_image (android_drawable, enum android_image_format); extern void android_put_image (android_pixmap, struct android_image *); + +/* Native image transforms. */ + +/* 3x2 matrix describing a projective transform. See + android_transform_coordinates for details. */ + +struct android_transform +{ + float m1, m2, m3; + float m4, m5, m6; +}; + +extern void android_project_image_bilinear (struct android_image *, + struct android_image *, + struct android_transform *); +extern void android_project_image_nearest (struct android_image *, + struct android_image *, + struct android_transform *); + /* X emulation stuff also needed while building stubs. */ diff --git a/src/androidterm.c b/src/androidterm.c index 6f452a52d85..cc2da279bb3 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -417,8 +417,6 @@ android_note_mouse_movement (struct frame *frame, || event->y < r->y || event->y >= r->y + r->height) { frame->mouse_moved = true; - /* TODO - dpyinfo->last_mouse_scroll_bar = NULL; */ note_mouse_highlight (frame, event->x, event->y); /* Remember which glyph we're now on. */ remember_mouse_glyph (frame, event->x, event->y, r); @@ -2959,10 +2957,34 @@ android_draw_stretch_glyph_string (struct glyph_string *s) s->background_filled_p = true; } +static void +android_get_scale_factor (int *scale_x, int *scale_y) +{ + /* This is 96 everywhere else, but 160 on Android. */ + const int base_res = 160; + struct android_display_info *dpyinfo; + + dpyinfo = x_display_list; + *scale_x = *scale_y = 1; + + if (dpyinfo) + { + if (dpyinfo->resx > base_res) + *scale_x = floor (dpyinfo->resx / base_res); + if (dpyinfo->resy > base_res) + *scale_y = floor (dpyinfo->resy / base_res); + } +} + static void android_draw_underwave (struct glyph_string *s, int decoration_width) { - int wave_height = 3, wave_length = 2; + int scale_x, scale_y; + + android_get_scale_factor (&scale_x, &scale_y); + + int wave_height = 3 * scale_y, wave_length = 2 * scale_x; + int dx, dy, x0, y0, width, x1, y1, x2, y2, xmax; bool odd; struct android_rectangle wave_clip, string_clip, final_clip; diff --git a/src/dispextern.h b/src/dispextern.h index 2ceed53813e..bc3556114c5 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -3098,8 +3098,9 @@ struct redisplay_interface #ifdef HAVE_WINDOW_SYSTEM -# if (defined USE_CAIRO || defined HAVE_XRENDER \ - || defined HAVE_NS || defined HAVE_NTGUI || defined HAVE_HAIKU) +# if (defined USE_CAIRO || defined HAVE_XRENDER \ + || defined HAVE_NS || defined HAVE_NTGUI || defined HAVE_HAIKU \ + || defined HAVE_ANDROID) # define HAVE_NATIVE_TRANSFORMS # endif diff --git a/src/image.c b/src/image.c index 364ddd96e31..16618f49f1e 100644 --- a/src/image.c +++ b/src/image.c @@ -2631,11 +2631,11 @@ compute_image_size (double width, double height, finally move the origin back to the top left of the image, which may now be a different corner. - Note that different GUI backends (X, Cairo, w32, NS, Haiku) want - the transform matrix defined as transform from the original image - to the transformed image, while others want the matrix to describe - the transform of the space, which boils down to inverting the - matrix. + Note that different GUI backends (X, Cairo, w32, NS, Haiku, + Android) want the transform matrix defined as transform from the + original image to the transformed image, while others want the + matrix to describe the transform of the space, which boils down to + inverting the matrix. It's possible to pre-calculate the matrix multiplications and just generate one transform matrix that will do everything we need in a @@ -2677,6 +2677,96 @@ compute_image_rotation (struct image *img, double *rotation) *rotation = XFIXNUM (reduced_angle); } +#ifdef HAVE_ANDROID + +static void +matrix_identity (matrix3x3 matrix) +{ + memset (matrix, 0, sizeof (matrix3x3)); + + matrix[0][0] = 1.0; + matrix[1][1] = 1.0; + matrix[2][2] = 1.0; +} + +/* Translate the matrix TRANSFORM to X, Y, and then perform clockwise + rotation by the given angle THETA in radians and translate back. + As the transform is being performed in a coordinate system where Y + grows downwards, the given angle describes a clockwise + rotation. */ + +static void +matrix_rotate (matrix3x3 transform, double theta, double x, double y) +{ + matrix3x3 temp, copy; + + /* 1. Translate the matrix so X and Y are in the center. */ + + matrix_identity (temp); + memcpy (copy, transform, sizeof copy); + + temp[0][2] = x; + temp[1][2] = y; + + matrix3x3_mult (copy, temp, transform); + matrix_identity (temp); + memcpy (copy, transform, sizeof copy); + + /* 2. Rotate the matrix counter-clockwise, assuming a coordinate + system where Y grows downwards. */ + + temp[0][0] = cos (theta); + temp[0][1] = -sin (theta); + temp[1][0] = sinf (theta); + temp[1][1] = cosf (theta); + + matrix3x3_mult (copy, temp, transform); + matrix_identity (temp); + memcpy (copy, transform, sizeof copy); + + /* 3. Translate back. */ + + temp[0][2] = -x; + temp[1][2] = -y; + + matrix3x3_mult (copy, temp, transform); +} + +/* Scale the matrix TRANSFORM by -1, and then apply a TX of width, in + effect flipping the image horizontally. */ + +static void +matrix_mirror_horizontal (matrix3x3 transform, double width) +{ + matrix3x3 temp, copy; + + matrix_identity (temp); + memcpy (copy, transform, sizeof copy); + + temp[0][0] = -1.0f; + temp[0][2] = width; + + matrix3x3_mult (copy, temp, transform); +} + +static void +matrix_translate (matrix3x3 transform, float tx, float ty) +{ + matrix3x3 temp, copy; + + matrix_identity (temp); + memcpy (copy, transform, sizeof copy); + + /* Set the tx and ty. */ + temp[0][2] = tx; + temp[1][2] = ty; + + /* Multiply it with the transform. */ + matrix3x3_mult (copy, temp, transform); +} + +#endif + static void image_set_transform (struct frame *f, struct image *img) { @@ -2696,6 +2786,14 @@ image_set_transform (struct frame *f, struct image *img) memcpy (&img->transform, identity, sizeof identity); #endif +#if defined HAVE_ANDROID + matrix3x3 identity = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + }; +#endif + # if (defined HAVE_IMAGEMAGICK \ && !defined DONT_CREATE_TRANSFORMED_IMAGEMAGICK_IMAGE) /* ImageMagick images already have the correct transform. */ @@ -2733,7 +2831,8 @@ image_set_transform (struct frame *f, struct image *img) /* Determine flipping. */ flip = !NILP (image_spec_value (img->spec, QCflip, NULL)); -# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined HAVE_HAIKU +# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined HAVE_HAIKU \ + || defined HAVE_ANDROID /* We want scale up operations to use a nearest neighbor filter to show real pixels instead of munging them, but scale down operations to use a blended filter, to avoid aliasing and the like. @@ -2755,7 +2854,7 @@ image_set_transform (struct frame *f, struct image *img) matrix3x3 matrix = { -# if defined USE_CAIRO || defined HAVE_XRENDER +# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_ANDROID [0][0] = (!IEEE_FLOATING_POINT && width == 0 ? DBL_MAX : img->width / (double) width), [1][1] = (!IEEE_FLOATING_POINT && height == 0 ? DBL_MAX @@ -2778,7 +2877,7 @@ image_set_transform (struct frame *f, struct image *img) /* Haiku needs this, since the transformation is done on the basis of the view, and not the image. */ -#ifdef HAVE_HAIKU +#if defined HAVE_HAIKU int extra_tx, extra_ty; extra_tx = 0; @@ -2789,8 +2888,9 @@ image_set_transform (struct frame *f, struct image *img) rotate_flag = 0; else { -# if (defined USE_CAIRO || defined HAVE_XRENDER \ - || defined HAVE_NTGUI || defined HAVE_NS \ +#ifndef HAVE_ANDROID +# if (defined USE_CAIRO || defined HAVE_XRENDER \ + || defined HAVE_NTGUI || defined HAVE_NS \ || defined HAVE_HAIKU) int cos_r, sin_r; if (rotation == 0) @@ -2817,7 +2917,7 @@ image_set_transform (struct frame *f, struct image *img) sin_r = 1; rotate_flag = 1; -#ifdef HAVE_HAIKU +#if defined HAVE_HAIKU if (!flip) extra_ty = height; extra_tx = 0; @@ -2853,7 +2953,7 @@ image_set_transform (struct frame *f, struct image *img) if (0 < rotate_flag) { -# if defined USE_CAIRO || defined HAVE_XRENDER +# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_ANDROID /* 1. Translate so (0, 0) is in the center of the image. */ matrix3x3 t = { [0][0] = 1, @@ -2904,6 +3004,93 @@ image_set_transform (struct frame *f, struct image *img) img->height = height; } # endif +#else + /* Calculate the inverse transform from the destination to the + source. The matrix is currently identity with scale + applied. + + This code makes more sense to me than what lies above. But + I'm not touching what works. */ + + if (rotation != 0 && rotation != 90 + && rotation != 180 && rotation != 270) + { + rotate_flag = 0; + goto bail; + } + + rotate_flag = 1; + + switch ((int) rotation + (flip ? 1 : 0)) + { + case 0: + break; + + case 90: + /* Rotate the image 90 degrees clockwise. IOW, rotate the + destination by 90 degrees counterclockwise, which is 270 + degrees clockwise. */ + matrix_rotate (matrix, M_PI * 1.5, 0, 0); + matrix_translate (matrix, -height, 0); + break; + + case 180: + /* Apply clockwise 180 degree rotation around the + center. */ + matrix_rotate (matrix, M_PI, width / 2.0, height / 2.0); + break; + + case 270: + /* Apply 270 degree counterclockwise rotation to the + destination, which is 90 degrees clockwise. */ + matrix_rotate (matrix, M_PI * 0.5, 0, 0); + matrix_translate (matrix, 0, -width); + break; + + case 1: + /* Flipped. Apply horizontal flip. */ + matrix_mirror_horizontal (matrix, width); + break; + + case 91: + /* Apply a flip but otherwise treat this the same as 90. */ + matrix_rotate (matrix, M_PI * 1.5, 0, 0); + matrix_translate (matrix, -height, 0); + matrix_mirror_horizontal (matrix, height); + break; + + case 181: + /* Flipped 180 degrees. Apply a flip and treat this the + same as 180. */ + matrix_rotate (matrix, M_PI, width / 2.0, height / 2.0); + matrix_mirror_horizontal (matrix, width); + break; + + case 271: + /* Flipped 270 degrees. Apply a flip and treat this the + same as 270. */ + matrix_rotate (matrix, M_PI * 0.5, 0, 0); + matrix_translate (matrix, 0, -width); + matrix_mirror_horizontal (matrix, height); + break; + } + + /* Now set img->width and img->height. Flip them if the + rotation being applied requires so. */ + + if (rotation != 270 && rotation != 90) + { + img->width = width; + img->height = height; + } + else + { + img->height = width; + img->width = height; + } + bail: + ; +#endif } if (rotate_flag < 0) @@ -2968,6 +3155,103 @@ image_set_transform (struct frame *f, struct image *img) img->transform[0][2] = extra_tx; img->transform[1][2] = extra_ty; } +# elif defined HAVE_ANDROID + /* Create a new image of the right size, then turn it into a pixmap + and set that as img->pixmap. Destroy img->mask for now (this is + not right.) */ + + struct android_image *transformed_image, *image; + struct android_transform transform; + + /* If there is no transform, simply return. */ + if (!memcmp (&matrix, &identity, sizeof matrix)) + return; + + /* First, get the source image. */ + image = image_get_x_image (f, img, false); + + /* Make the transformed image. */ + transformed_image = android_create_image (image->depth, + ANDROID_Z_PIXMAP, + NULL, img->width, + img->height); + + /* Allocate memory for that image. */ + transformed_image->data + = xmalloc (transformed_image->bytes_per_line + * transformed_image->height); + + /* Do the transform. */ + transform.m1 = matrix[0][0]; + transform.m2 = matrix[0][1]; + transform.m3 = matrix[0][2]; + transform.m4 = matrix[1][0]; + transform.m5 = matrix[1][1]; + transform.m6 = matrix[1][2]; + + if (image->depth == 24 && smoothing) + android_project_image_bilinear (image, transformed_image, + &transform); + else + android_project_image_nearest (image, transformed_image, + &transform); + + image_unget_x_image (img, false, image); + + /* Now replace the image. */ + + if (img->ximg) + image_destroy_x_image (img->ximg); + + img->ximg = transformed_image; + +#ifndef ANDROID_STUBIFY + /* Then replace the pixmap. */ + android_free_pixmap (img->pixmap); + + /* In case android_create_pixmap signals. */ + img->pixmap = ANDROID_NONE; + img->pixmap = android_create_pixmap (img->width, img->height, + transformed_image->depth); + android_put_image (img->pixmap, transformed_image); +#else + emacs_abort (); +#endif + + /* Now, transform the mask. The mask should be depth 1, and is + always transformed using a nearest neighbor filter. */ + + if (img->mask_img || img->mask) + { + image = image_get_x_image (f, img, true); + transformed_image = android_create_image (1, ANDROID_Z_PIXMAP, + NULL, img->width, + img->height); + transformed_image->data + = xmalloc (transformed_image->bytes_per_line + * transformed_image->height); + android_project_image_nearest (image, transformed_image, + &transform); + image_unget_x_image (img, false, image); + + /* Now replace the image. */ + + if (img->mask_img) + image_destroy_x_image (img->mask_img); + + img->mask_img = transformed_image; + +#ifndef ANDROID_STUBIFY + if (img->mask) + android_free_pixmap (img->mask); + + img->mask = ANDROID_NONE; + img->mask = android_create_pixmap (img->width, img->height, 1); + android_put_image (img->mask, transformed_image); +#endif + } + + /* Done! */ #endif } @@ -12087,7 +12371,7 @@ The list of capabilities can include one or more of the following: { #ifdef HAVE_NATIVE_TRANSFORMS # if defined HAVE_IMAGEMAGICK || defined (USE_CAIRO) || defined (HAVE_NS) \ - || defined (HAVE_HAIKU) + || defined (HAVE_HAIKU) | defined HAVE_ANDROID return list2 (Qscale, Qrotate90); # elif defined (HAVE_X_WINDOWS) && defined (HAVE_XRENDER) if (FRAME_DISPLAY_INFO (f)->xrender_supported_p) diff --git a/src/keyboard.c b/src/keyboard.c index 78637ef4f15..306fea354e2 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -339,6 +339,10 @@ static struct timespec timer_last_idleness_start_time; static Lisp_Object virtual_core_pointer_name; static Lisp_Object virtual_core_keyboard_name; +/* If not nil, ID of the last TOUCHSCREEN_END_EVENT to land on the + menu bar. */ +static Lisp_Object menu_bar_touch_id; + /* Global variable declarations. */ @@ -6445,11 +6449,74 @@ make_lispy_event (struct input_event *event) { Lisp_Object x, y, id, position; struct frame *f = XFRAME (event->frame_or_window); +#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR + int column, row, dummy; +#endif id = event->arg; x = event->x; y = event->y; +#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR + if (event->kind == TOUCHSCREEN_BEGIN_EVENT + && coords_in_menu_bar_window (f, XFIXNUM (x), XFIXNUM (y))) + { + /* If the tap began in the menu bar window, then save the + id. */ + menu_bar_touch_id = id; + return Qnil; + } + else if (event->kind == TOUCHSCREEN_END_EVENT + && EQ (menu_bar_touch_id, id)) + { + /* This touch should activate the menu bar. Generate the + menu bar event. */ + menu_bar_touch_id = Qnil; + + if (f->menu_bar_window) + { + x_y_to_hpos_vpos (XWINDOW (f->menu_bar_window), XFIXNUM (x), + XFIXNUM (y), &column, &row, NULL, NULL, + &dummy); + + if (row >= 0 && row < FRAME_MENU_BAR_LINES (f)) + { + Lisp_Object items, item; + + /* Find the menu bar item under `column'. */ + item = Qnil; + items = FRAME_MENU_BAR_ITEMS (f); + for (i = 0; i < ASIZE (items); i += 4) + { + Lisp_Object pos, string; + string = AREF (items, i + 1); + pos = AREF (items, i + 3); + if (NILP (string)) + break; + if (column >= XFIXNUM (pos) + && column < XFIXNUM (pos) + SCHARS (string)) + { + item = AREF (items, i); + break; + } + } + + /* ELisp manual 2.4b says (x y) are window + relative but code says they are + frame-relative. */ + position = list4 (event->frame_or_window, + Qmenu_bar, + Fcons (event->x, event->y), + INT_TO_INTEGER (event->timestamp)); + + return list2 (item, position); + } + } + + return Qnil; + } +#endif + position = make_lispy_position (f, x, y, event->timestamp); return list2 (((event->kind @@ -12462,6 +12529,9 @@ syms_of_keyboard (void) virtual_core_keyboard_name = Qnil; staticpro (&virtual_core_keyboard_name); + menu_bar_touch_id = Qnil; + staticpro (&menu_bar_touch_id); + defsubr (&Scurrent_idle_time); defsubr (&Sevent_symbol_parse_modifiers); defsubr (&Sevent_convert_list); diff --git a/src/sfnt.c b/src/sfnt.c index 9b6c421212a..09fda82382b 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -3436,7 +3436,7 @@ sfnt_compare_edges (const void *a, const void *b) that now overlap with Y, keeping them sorted by X. Poly those edges through SPAN_FUNC. Then, move upwards by SFNT_POLY_STEP, remove edges that no longer apply, and interpolate the remaining - edge's X coordinates. Repeat until all the edges have been polyed. + edges' X coordinates. Repeat until all the edges have been polyed. Or alternatively, think of this as such: each edge is actually a vector from its bottom position towards its top most position. diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index 01bfdbaaf58..cddb3fd40f3 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -73,48 +73,22 @@ sfntfont_android_mul8x2 (unsigned int a8, unsigned int b32) return (i + ((i >> 8) & 0xff00ff)) >> 8 & 0xff00ff; } -/* Blend two pixels SRC and DST without utilizing any control flow. - SRC must be in premultiplied ARGB8888 format, and DST must be in - premultiplied ABGR8888 format. Value is in premultiplied ABGR8888 - format. */ - -static unsigned int -sfntfont_android_blend (unsigned int src, unsigned int dst) -{ - unsigned int a, br_part, ag_part, src_rb, both; - - a = (src >> 24); - br_part = sfntfont_android_mul8x2 (255 - a, dst); - ag_part = sfntfont_android_mul8x2 (255 - a, dst >> 8) << 8; - - both = ag_part | br_part; - - /* Swizzle src. */ - src_rb = src & 0x00ff00ff; - src = src & ~0x00ff00ff; - src |= (src_rb >> 16 | src_rb << 16); - - /* This addition need not be saturating because both has already - been multiplied by 255 - a. */ - return both + src; -} - #define U255TO256(x) ((unsigned short) (x) + ((x) >> 7)) /* Blend two pixels SRC and DST without utilizing any control flow. - Both SRC and DST are expected to be in premultiplied ARGB8888 + Both SRC and DST are expected to be in premultiplied ABGB8888 format. Value is returned in premultiplied ARGB8888 format. */ static unsigned int -sfntfont_android_blendrgb (unsigned int src, unsigned int dst) +sfntfont_android_blend (unsigned int src, unsigned int dst) { - unsigned int a, rb_part, ag_part, both; + unsigned int a, br_part, ag_part, both; a = (src >> 24); - rb_part = sfntfont_android_mul8x2 (255 - a, dst); + br_part = sfntfont_android_mul8x2 (255 - a, dst); ag_part = sfntfont_android_mul8x2 (255 - a, dst >> 8) << 8; - both = ag_part | rb_part; + both = ag_part | br_part; /* This addition need not be saturating because both has already been multiplied by 255 - a. */ @@ -210,6 +184,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, jobject bitmap; int left, top, temp_y; unsigned int prod, raster_y; + unsigned long foreground, back_pixel, rb; if (!s->gc->num_clip_rects) /* Clip region is empty. */ @@ -219,6 +194,17 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, /* Nothing to draw. */ return; + /* Swizzle the foreground and background in s->gc into BGR, then add + an alpha channel. */ + foreground = s->gc->foreground; + back_pixel = s->gc->background; + rb = foreground & 0x00ff00ff; + foreground &= ~0x00ff00ff; + foreground |= rb >> 16 | rb << 16 | 0xff000000; + rb = back_pixel & 0x00ff00ff; + back_pixel &= ~0x00ff00ff; + back_pixel |= rb >> 16 | rb << 16 | 0xff000000; + USE_SAFE_ALLOCA; prepare_face_for_display (s->f, s->face); @@ -294,7 +280,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, + stride * temp_y); for (x = background.x; x < background.x + background.width; ++x) - row[x] = s->gc->background | 0xff000000; + row[x] = back_pixel; } } @@ -327,10 +313,9 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, { prod = sfntfont_android_scale32 (U255TO256 (raster_row[x]), - (s->gc->foreground - | 0xff000000)); + foreground); row[left + x] - = sfntfont_android_blendrgb (prod, row[left + x]); + = sfntfont_android_blend (prod, row[left + x]); } } } diff --git a/src/sfntfont.c b/src/sfntfont.c index 25cea59f6a7..56977622211 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -860,7 +860,7 @@ sfntfont_lookup_char (struct sfnt_font_desc *desc, Lisp_Object character, /* Emacs missing charsets? */ return false; - font_character = ENCODE_CHAR (charset, XFIXNUM (character)); + font_character = ENCODE_CHAR (charset, (int) XFIXNUM (character)); if (font_character == CHARSET_INVALID_CODE (charset)) return false; -- cgit v1.3 From 1b8258a1f2b6a080a4f0e819aa4a86c1ec2da89f Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 17 Jan 2023 22:10:43 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Fonts): Document that TTC format fonts are now supported. * doc/emacs/emacs.texi (Top): Fix menus. * doc/lispref/commands.texi (Touchscreen Events) (Key Sequence Input): Document changes to touchscreen events. * etc/DEBUG: Describe how to debug 64 bit binaries on Android. * java/org/gnu/emacs/EmacsCopyArea.java (perform): Explicitly recycle copy bitmap. * java/org/gnu/emacs/EmacsDialog.java (EmacsDialog): New class. * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Use 5 point PolyLine like X, because Android behaves like Postscript on some devices and X elsewhere. * java/org/gnu/emacs/EmacsFillRectangle.java (perform): Explicitly recycle copy bitmap. * java/org/gnu/emacs/EmacsPixmap.java (destroyHandle): Explicitly recycle bitmap and GC if it is big. * java/org/gnu/emacs/EmacsView.java (EmacsView): Make `bitmapDirty' a boolean. (handleDirtyBitmap): Reimplement in terms of that boolean. Explicitly recycle old bitmap and GC. (onLayout): Fix lock up. (onDetachedFromWindow): Recycle bitmap and GC. * java/org/gnu/emacs/EmacsWindow.java (requestViewLayout): Update call to explicitlyDirtyBitmap. * src/android.c (android_run_select_thread, android_select): Really fix android_select. (android_build_jstring): New function. * src/android.h: Update prototypes. * src/androidmenu.c (android_process_events_for_menu): Totally unblock input before process_pending_signals. (android_menu_show): Remove redundant unblock_input and debugging code. (struct android_emacs_dialog, android_init_emacs_dialog) (android_dialog_show, android_popup_dialog, init_androidmenu): Implement popup dialogs on Android. * src/androidterm.c (android_update_tools) (handle_one_android_event, android_frame_up_to_date): Allow tapping tool bar items. (android_create_terminal): Add dialog hook. (android_wait_for_event): Adjust call to android_select. * src/androidterm.h (struct android_touch_point): New field `tool_bar_p'. * src/keyboard.c (read_key_sequence, head_table) (syms_of_keyboard): Prefix touchscreen events with posn. * src/keyboard.h (EVENT_HEAD): Handle touchscreen events. * src/process.c (wait_reading_process_output): Adjust call to android_select. * src/sfnt.c (sfnt_read_table_directory): If the first long turns out to be ttcf, return -1. (sfnt_read_ttc_header): New function. (main): Test TTC support. * src/sfnt.h (struct sfnt_ttc_header): New structure. (enum sfnt_ttc_tag): New enum. * src/sfntfont-android.c (struct sfntfont_android_scanline_buffer): New structure. (GET_SCANLINE_BUFFER): New macro. Try to avoid so much malloc upon accessing the scanline buffer. (sfntfont_android_put_glyphs): Do not use SAFE_ALLOCA to allocate the scaline buffer. (Fandroid_enumerate_fonts): Enumerate ttc fonts too. * src/sfntfont.c (struct sfnt_font_desc): New field `offset'. (sfnt_enum_font_1): Split out enumeration code from sfnt_enum_font. (sfnt_enum_font): Read TTC tables and enumerate each font therein. (sfntfont_open): Seek to the offset specified. * xcompile/Makefile.in (maintainer-clean): Fix depends here. --- doc/emacs/android.texi | 9 +- doc/emacs/emacs.texi | 2 + doc/lispref/commands.texi | 31 ++- etc/DEBUG | 9 + java/org/gnu/emacs/EmacsCopyArea.java | 4 + java/org/gnu/emacs/EmacsDialog.java | 333 +++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsDrawRectangle.java | 32 ++- java/org/gnu/emacs/EmacsFillRectangle.java | 3 + java/org/gnu/emacs/EmacsPixmap.java | 23 ++ java/org/gnu/emacs/EmacsView.java | 84 ++++++-- java/org/gnu/emacs/EmacsWindow.java | 2 +- src/android.c | 62 ++++-- src/android.h | 3 +- src/androidmenu.c | 240 ++++++++++++++++++++- src/androidterm.c | 100 ++++++++- src/androidterm.h | 7 + src/keyboard.c | 7 +- src/keyboard.h | 13 +- src/process.c | 2 +- src/sfnt.c | 167 ++++++++++++++- src/sfnt.h | 40 ++++ src/sfntfont-android.c | 99 +++++++-- src/sfntfont.c | 113 ++++++++-- xcompile/Makefile.in | 2 +- 24 files changed, 1259 insertions(+), 128 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsDialog.java (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index b5a91b0f98f..8806a2a2bf6 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -335,11 +335,10 @@ places fonts. Emacs assumes there will always be a font named ``Droid Sans Mono'', and then defaults to using this font. These fonts are then rendered by the @code{sfnt-android} font driver. -When running on Android, Emacs currently lacks support for TrueType -Container and OpenType fonts. This means that only a subset of the -fonts installed on the system are currently available to Emacs. If -you are interested in raising this limitation, please contact -@email{emacs-devel@@gnu.org}. +When running on Android, Emacs currently lacks support for OpenType +fonts. This means that only a subset of the fonts installed on the +system are currently available to Emacs. If you are interested in +lifting this limitation, please contact @email{emacs-devel@@gnu.org}. If the @code{sfnt-android} font driver fails to find any fonts at all, Emacs falls back to the @code{android} font driver. This is a very diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index 19a99d1deb2..d21d09bf920 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -223,6 +223,7 @@ Appendices * Antinews:: Information about Emacs version 28. * Mac OS / GNUstep:: Using Emacs under macOS and GNUstep. * Haiku:: Using Emacs on Haiku. +* Android:: Using Emacs on Android. * Microsoft Windows:: Using Emacs on Microsoft Windows and MS-DOS. * Manifesto:: What's GNU? Gnu's Not Unix! @@ -1262,6 +1263,7 @@ Emacs and Android * Android Startup:: Starting up Emacs on Android. * Android File System:: The Android file system. * Android Environment:: Running Emacs under Android. +* Android Fonts:: Font selection under Android. Emacs and Microsoft Windows/MS-DOS diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 14702ce6efa..6d1ce145fbf 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -1999,6 +1999,13 @@ each point is represented by a cons of an arbitrary number identifying the point and a mouse position list (@pxref{Click Events}) specifying the position of the finger when the event occurred. +In addition, @code{touchscreen-begin} events also have imaginary +prefixes keys added by @code{read-key-sequence} when they originate on +top of a special part of a frame or window. @xref{Key Sequence +Input}. The reason the other touch screen events do not undergo this +treatment is that they are rarely useful without being used in tandem +from their corresponding @code{touchscreen-begin} events. + @table @code @cindex @code{touchscreen-begin} event @item (touchscreen-begin @var{point}) @@ -3024,19 +3031,21 @@ with any other events. @cindex @code{right-divider}, prefix key @cindex @code{bottom-divider}, prefix key @cindex mouse events, in special parts of window or frame -When mouse events occur in special parts of a window or frame, such as a mode -line or a scroll bar, the event type shows nothing special---it is the -same symbol that would normally represent that combination of mouse -button and modifier keys. The information about the window part is kept -elsewhere in the event---in the coordinates. But -@code{read-key-sequence} translates this information into imaginary -prefix keys, all of which are symbols: @code{tab-line}, @code{header-line}, -@code{horizontal-scroll-bar}, @code{menu-bar}, @code{tab-bar}, @code{mode-line}, +@cindex touch screen events, in special parts of window or frame +When mouse or @code{touch-screen-begin} events occur in special parts +of a window or frame, such as a mode line or a scroll bar, the event +type shows nothing special---it is the same symbol that would normally +represent that combination of mouse button and modifier keys. The +information about the window part is kept elsewhere in the event---in +the coordinates. But @code{read-key-sequence} translates this +information into imaginary prefix keys, all of which are symbols: +@code{tab-line}, @code{header-line}, @code{horizontal-scroll-bar}, +@code{menu-bar}, @code{tab-bar}, @code{mode-line}, @code{vertical-line}, @code{vertical-scroll-bar}, @code{left-margin}, @code{right-margin}, @code{left-fringe}, @code{right-fringe}, -@code{right-divider}, and @code{bottom-divider}. You can define meanings for -mouse clicks in special window parts by defining key sequences using these -imaginary prefix keys. +@code{right-divider}, and @code{bottom-divider}. You can define +meanings for mouse clicks in special window parts by defining key +sequences using these imaginary prefix keys. For example, if you call @code{read-key-sequence} and then click the mouse on the window's mode line, you get two events, like this: diff --git a/etc/DEBUG b/etc/DEBUG index 124453939b1..1cc575598bf 100644 --- a/etc/DEBUG +++ b/etc/DEBUG @@ -1123,6 +1123,15 @@ then placing a breakpoint on: will let you find the source of the crash. +If there is no `gdbserver' binary present on the device, then you can +specify one to upload, like so: + + ../java/debug.sh --gdbserver /path/to/gdbserver + +In addition, when Emacs runs as a 64-bit process on a system +supporting both 64 and 32-bit binaries, you must specify the path to a +64-bit gdbserver binary. + This file is part of GNU Emacs. diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 00e817bb97d..5d72a7860c8 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -116,6 +116,7 @@ public class EmacsCopyArea src_x, src_y, width, height); canvas.drawBitmap (bitmap, null, rect, paint); + bitmap.recycle (); } else { @@ -183,6 +184,9 @@ public class EmacsCopyArea paint.setXfermode (overAlu); canvas.drawBitmap (maskBitmap, null, maskRect, paint); gc.resetXfermode (); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java new file mode 100644 index 00000000000..5bc8efa5978 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -0,0 +1,333 @@ +/* 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 . */ + +package org.gnu.emacs; + +import java.util.List; +import java.util.ArrayList; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Context; +import android.util.Log; + +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.FrameLayout; + +import android.view.View; +import android.view.ViewGroup; + +/* Toolkit dialog implementation. This object is built from JNI and + describes a single alert dialog. Then, `inflate' turns it into + AlertDialog. */ + +public class EmacsDialog implements DialogInterface.OnDismissListener +{ + private static final String TAG = "EmacsDialog"; + + /* List of buttons in this dialog. */ + private List buttons; + + /* Dialog title. */ + private String title; + + /* Dialog text. */ + private String text; + + /* Whether or not a selection has already been made. */ + private boolean wasButtonClicked; + + /* Dialog to dismiss after click. */ + private AlertDialog dismissDialog; + + private class EmacsButton implements View.OnClickListener, + DialogInterface.OnClickListener + { + /* Name of this button. */ + public String name; + + /* ID of this button. */ + public int id; + + /* Whether or not the button is enabled. */ + public boolean enabled; + + @Override + public void + onClick (View view) + { + Log.d (TAG, "onClicked " + this); + + wasButtonClicked = true; + EmacsNative.sendContextMenu ((short) 0, id); + dismissDialog.dismiss (); + } + + @Override + public void + onClick (DialogInterface dialog, int which) + { + Log.d (TAG, "onClicked " + this); + + wasButtonClicked = true; + EmacsNative.sendContextMenu ((short) 0, id); + } + }; + + /* Create a popup dialog with the title TITLE and the text TEXT. + TITLE may be NULL. */ + + public static EmacsDialog + createDialog (String title, String text) + { + EmacsDialog dialog; + + dialog = new EmacsDialog (); + dialog.buttons = new ArrayList (); + dialog.title = title; + dialog.text = text; + + return dialog; + } + + /* Add a button named NAME, with the identifier ID. If DISABLE, + disable the button. */ + + public void + addButton (String name, int id, boolean disable) + { + EmacsButton button; + + button = new EmacsButton (); + button.name = name; + button.id = id; + button.enabled = !disable; + buttons.add (button); + } + + /* Turn this dialog into an AlertDialog for the specified + CONTEXT. + + Upon a button being selected, the dialog will send an + ANDROID_CONTEXT_MENU event with the id of that button. + + Upon the dialog being dismissed, an ANDROID_CONTEXT_MENU event + will be sent with an id of 0. */ + + public AlertDialog + toAlertDialog (Context context) + { + AlertDialog dialog; + int size; + EmacsButton button; + LinearLayout layout; + Button buttonView; + ViewGroup.LayoutParams layoutParams; + + size = buttons.size (); + + if (size <= 3) + { + dialog = new AlertDialog.Builder (context).create (); + dialog.setMessage (text); + dialog.setCancelable (true); + dialog.setOnDismissListener (this); + + if (title != null) + dialog.setTitle (title); + + /* There are less than 4 buttons. Add the buttons the way + Android intends them to be added. */ + + if (size >= 1) + { + button = buttons.get (0); + dialog.setButton (DialogInterface.BUTTON_POSITIVE, + button.name, button); + } + + if (size >= 2) + { + button = buttons.get (1); + dialog.setButton (DialogInterface.BUTTON_NEUTRAL, + button.name, button); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); + buttonView.setEnabled (button.enabled); + } + + if (size >= 3) + { + button = buttons.get (2); + dialog.setButton (DialogInterface.BUTTON_NEGATIVE, + button.name, button); + } + } + else + { + /* There are more than 4 buttons. Add them all to a + LinearLayout. */ + layout = new LinearLayout (context); + layoutParams + = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + for (EmacsButton emacsButton : buttons) + { + buttonView = new Button (context); + buttonView.setText (emacsButton.name); + buttonView.setOnClickListener (emacsButton); + buttonView.setLayoutParams (layoutParams); + buttonView.setEnabled (emacsButton.enabled); + layout.addView (buttonView); + } + + layoutParams + = new FrameLayout.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + layout.setLayoutParams (layoutParams); + + /* Add that layout to the dialog's custom view. + + android.R.id.custom is documented to work. But looking it + up returns NULL, so setView must be used instead. */ + + dialog = new AlertDialog.Builder (context).setView (layout).create (); + dialog.setMessage (text); + dialog.setCancelable (true); + dialog.setOnDismissListener (this); + + if (title != null) + dialog.setTitle (title); + } + + return dialog; + } + + /* Internal helper for display run on the main thread. */ + + private boolean + display1 () + { + EmacsActivity activity; + int size; + Button buttonView; + EmacsButton button; + AlertDialog dialog; + + if (EmacsActivity.focusedActivities.isEmpty ()) + return false; + + activity = EmacsActivity.focusedActivities.get (0); + dialog = dismissDialog = toAlertDialog (activity); + dismissDialog.show (); + + /* If there are less than four buttons, then they must be + individually enabled or disabled after the dialog is + displayed. */ + size = buttons.size (); + + if (size <= 3) + { + if (size >= 1) + { + button = buttons.get (0); + buttonView + = dialog.getButton (DialogInterface.BUTTON_POSITIVE); + buttonView.setEnabled (button.enabled); + } + + if (size >= 2) + { + button = buttons.get (1); + dialog.setButton (DialogInterface.BUTTON_NEUTRAL, + button.name, button); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); + buttonView.setEnabled (button.enabled); + } + + if (size >= 3) + { + button = buttons.get (2); + buttonView + = dialog.getButton (DialogInterface.BUTTON_NEGATIVE); + buttonView.setEnabled (button.enabled); + } + } + + return true; + } + + /* Display this dialog for a suitable activity. + Value is false if the dialog could not be displayed, + and true otherwise. */ + + public boolean + display () + { + Runnable runnable; + final Holder rc; + + rc = new Holder (); + runnable = new Runnable () { + @Override + public void + run () + { + synchronized (this) + { + rc.thing = display1 (); + notify (); + } + } + }; + + synchronized (runnable) + { + EmacsService.SERVICE.runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + + return rc.thing; + } + + + + @Override + public void + onDismiss (DialogInterface dialog) + { + Log.d (TAG, "onDismiss: " + this); + + if (wasButtonClicked) + return; + + EmacsNative.sendContextMenu ((short) 0, 0); + } +}; diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index b42e9556e8c..c29d413f66e 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -36,10 +36,10 @@ public class EmacsDrawRectangle Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; - Rect rect; Rect maskRect, dstRect; Canvas canvas; Bitmap clipBitmap; + Rect clipRect; /* TODO implement stippling. */ if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) @@ -58,13 +58,29 @@ public class EmacsDrawRectangle canvas.clipRect (gc.real_clip_rects[i]); } - paint = gc.gcPaint; - rect = new Rect (x, y, x + width, y + height); + /* Clip to the clipRect because some versions of Android draw an + overly wide line. */ + clipRect = new Rect (x, y, x + width + 1, + y + height + 1); + canvas.clipRect (clipRect); - paint.setStyle (Paint.Style.STROKE); + paint = gc.gcPaint; if (gc.clip_mask == null) - canvas.drawRect (rect, paint); + { + /* canvas.drawRect just doesn't work on Android, producing + different results on various devices. Do a 5 point + PolyLine instead. */ + canvas.drawLine ((float) x, (float) y, (float) x + width, + (float) y, paint); + canvas.drawLine ((float) x + width, (float) y, + (float) x + width, (float) y + height, + paint); + canvas.drawLine ((float) x + width, (float) y + height, + (float) x, (float) y + height, paint); + canvas.drawLine ((float) x, (float) y + height, + (float) x, (float) y, paint); + } else { /* Drawing with a clip mask involves calculating the @@ -116,10 +132,12 @@ public class EmacsDrawRectangle /* Finally, draw the mask bitmap to the destination. */ paint.setXfermode (null); canvas.drawBitmap (maskBitmap, null, maskRect, paint); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); - drawable.damageRect (new Rect (x, y, x + width + 1, - y + height + 1)); + drawable.damageRect (clipRect); } } diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index b733b417d6b..7cc55d3db96 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -115,6 +115,9 @@ public class EmacsFillRectangle /* Finally, draw the mask bitmap to the destination. */ paint.setXfermode (null); canvas.drawBitmap (maskBitmap, null, maskRect, paint); + + /* Recycle this unused bitmap. */ + maskBitmap.recycle (); } canvas.restore (); diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index 15452f007c4..85931c2abd4 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -25,6 +25,8 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.Build; + /* Drawable backed by bitmap. */ public class EmacsPixmap extends EmacsHandleObject @@ -123,4 +125,25 @@ public class EmacsPixmap extends EmacsHandleObject { return bitmap; } + + @Override + public void + destroyHandle () + { + boolean needCollect; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + needCollect = (bitmap.getByteCount () + >= 1024 * 512); + else + needCollect = (bitmap.getAllocationByteCount () + >= 1024 * 512); + + bitmap.recycle (); + bitmap = null; + + /* Collect the bitmap storage if the bitmap is big. */ + if (needCollect) + Runtime.getRuntime ().gc (); + } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 445d8ffa023..6137fd74a7f 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -70,9 +70,9 @@ public class EmacsView extends ViewGroup event regardless of what changed. */ public boolean mustReportLayout; - /* If non-null, whether or not bitmaps must be recreated upon the - next call to getBitmap. */ - private Rect bitmapDirty; + /* Whether or not bitmaps must be recreated upon the next call to + getBitmap. */ + private boolean bitmapDirty; /* Whether or not a popup is active. */ private boolean popupActive; @@ -80,6 +80,9 @@ public class EmacsView extends ViewGroup /* The current context menu. */ private EmacsContextMenu contextMenu; + /* The last measured width and height. */ + private int measuredWidth, measuredHeight; + public EmacsView (EmacsWindow window) { @@ -116,13 +119,27 @@ public class EmacsView extends ViewGroup { Bitmap oldBitmap; + if (measuredWidth == 0 || measuredHeight == 0) + return; + + /* If bitmap is the same width and height as the measured width + and height, there is no need to do anything. Avoid allocating + the extra bitmap. */ + if (bitmap != null + && (bitmap.getWidth () == measuredWidth + && bitmap.getHeight () == measuredHeight)) + { + bitmapDirty = false; + return; + } + /* Save the old bitmap. */ oldBitmap = bitmap; /* Recreate the front and back buffer bitmaps. */ bitmap - = Bitmap.createBitmap (bitmapDirty.width (), - bitmapDirty.height (), + = Bitmap.createBitmap (measuredWidth, + measuredHeight, Bitmap.Config.ARGB_8888); bitmap.eraseColor (0xffffffff); @@ -133,23 +150,27 @@ public class EmacsView extends ViewGroup if (oldBitmap != null) canvas.drawBitmap (oldBitmap, 0f, 0f, new Paint ()); - bitmapDirty = null; + bitmapDirty = false; + + /* Explicitly free the old bitmap's memory. */ + if (oldBitmap != null) + oldBitmap.recycle (); + + /* Some Android versions still don't free the bitmap until the + next GC. */ + Runtime.getRuntime ().gc (); } public synchronized void - explicitlyDirtyBitmap (Rect rect) + explicitlyDirtyBitmap () { - if (bitmapDirty == null - && (bitmap == null - || rect.width () != bitmap.getWidth () - || rect.height () != bitmap.getHeight ())) - bitmapDirty = rect; + bitmapDirty = true; } public synchronized Bitmap getBitmap () { - if (bitmapDirty != null) + if (bitmapDirty || bitmap == null) handleDirtyBitmap (); return bitmap; @@ -158,7 +179,7 @@ public class EmacsView extends ViewGroup public synchronized Canvas getCanvas () { - if (bitmapDirty != null) + if (bitmapDirty || bitmap == null) handleDirtyBitmap (); return canvas; @@ -196,8 +217,12 @@ public class EmacsView extends ViewGroup super.setMeasuredDimension (width, height); } + /* Note that the monitor lock for the window must never be held from + within the lock for the view, because the window also locks the + other way around. */ + @Override - protected synchronized void + protected void onLayout (boolean changed, int left, int top, int right, int bottom) { @@ -213,12 +238,13 @@ public class EmacsView extends ViewGroup window.viewLayout (left, top, right, bottom); } - if (changed - /* Check that a change has really happened. */ - && (bitmapDirty == null - || bitmapDirty.width () != right - left - || bitmapDirty.height () != bottom - top)) - bitmapDirty = new Rect (left, top, right, bottom); + measuredWidth = right - left; + measuredHeight = bottom - top; + + /* Dirty the back buffer. */ + + if (changed) + explicitlyDirtyBitmap (); for (i = 0; i < count; ++i) { @@ -472,4 +498,20 @@ public class EmacsView extends ViewGroup contextMenu = null; popupActive = false; } + + @Override + public synchronized void + onDetachedFromWindow () + { + synchronized (this) + { + /* Recycle the bitmap and call GC. */ + bitmap.recycle (); + bitmap = null; + canvas = null; + + /* Collect the bitmap storage; it could be large. */ + Runtime.getRuntime ().gc (); + } + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 7181bc89fea..f5b50f11f14 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -260,7 +260,7 @@ public class EmacsWindow extends EmacsHandleObject { /* This is necessary because otherwise subsequent drawing on the Emacs thread may be lost. */ - view.explicitlyDirtyBitmap (rect); + view.explicitlyDirtyBitmap (); EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override diff --git a/src/android.c b/src/android.c index 9b15ea9f15a..cfb79045c0b 100644 --- a/src/android.c +++ b/src/android.c @@ -26,6 +26,7 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include #include #include @@ -225,7 +226,6 @@ static fd_set *volatile android_pselect_readfds; static fd_set *volatile android_pselect_writefds; static fd_set *volatile android_pselect_exceptfds; static struct timespec *volatile android_pselect_timeout; -static const sigset_t *volatile android_pselect_sigset; /* Value of pselect. */ static int android_pselect_rc; @@ -242,8 +242,8 @@ static sem_t android_pselect_sem, android_pselect_start_sem; static void * android_run_select_thread (void *data) { - sigset_t signals, sigset; - int rc; + sigset_t signals, waitset; + int rc, sig; sigfillset (&signals); @@ -253,6 +253,8 @@ android_run_select_thread (void *data) strerror (errno)); sigdelset (&signals, SIGUSR1); + sigemptyset (&waitset); + sigaddset (&waitset, SIGUSR1); while (true) { @@ -262,35 +264,33 @@ android_run_select_thread (void *data) /* Get the select lock and call pselect. */ pthread_mutex_lock (&event_queue.select_mutex); - - /* Make sure SIGUSR1 can always wake pselect up. */ - if (android_pselect_sigset) - { - sigset = *android_pselect_sigset; - sigdelset (&sigset, SIGUSR1); - android_pselect_sigset = &sigset; - } - else - android_pselect_sigset = &signals; - rc = pselect (android_pselect_nfds, android_pselect_readfds, android_pselect_writefds, android_pselect_exceptfds, android_pselect_timeout, - android_pselect_sigset); + &signals); android_pselect_rc = rc; pthread_mutex_unlock (&event_queue.select_mutex); + /* Signal the main thread that there is now data to read. + It is ok to signal this condition variable without holding + the event queue lock, because android_select will always + wait for this to complete before returning. */ + android_pselect_completed = true; + pthread_cond_signal (&event_queue.read_var); + + if (rc != -1 || errno != EINTR) + /* Now, wait for SIGUSR1, unless pselect was interrupted and + the signal was already delivered. The Emacs thread will + always send this signal after read_var is triggered or the + UI thread has sent an event. */ + sigwait (&waitset, &sig); + /* Signal the Emacs thread that pselect is done. If read_var was signaled by android_write_event, event_queue.mutex could still be locked, so this must come before. */ sem_post (&android_pselect_sem); - - pthread_mutex_lock (&event_queue.mutex); - android_pselect_completed = true; - pthread_cond_signal (&event_queue.read_var); - pthread_mutex_unlock (&event_queue.mutex); } } @@ -445,8 +445,7 @@ android_write_event (union android_event *event) int android_select (int nfds, fd_set *readfds, fd_set *writefds, - fd_set *exceptfds, struct timespec *timeout, - const sigset_t *sigset) + fd_set *exceptfds, struct timespec *timeout) { int nfds_return; @@ -467,7 +466,6 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds, android_pselect_writefds = writefds; android_pselect_exceptfds = exceptfds; android_pselect_timeout = timeout; - android_pselect_sigset = sigset; pthread_mutex_unlock (&event_queue.select_mutex); /* Release the select thread. */ @@ -3725,6 +3723,24 @@ android_build_string (Lisp_Object text) return string; } +/* Do the same, except TEXT is constant string data. */ + +jstring +android_build_jstring (const char *text) +{ + jstring string; + + string = (*android_java_env)->NewStringUTF (android_java_env, + text); + if (!string) + { + (*android_java_env)->ExceptionClear (android_java_env); + memory_full (0); + } + + return string; +} + /* Check for JNI exceptions and call memory_full in that situation. */ diff --git a/src/android.h b/src/android.h index e68e0a51fbf..036e6d266fd 100644 --- a/src/android.h +++ b/src/android.h @@ -48,7 +48,7 @@ extern int ANDROID_EXPORT android_emacs_init (int, char **); #ifndef ANDROID_STUBIFY extern int android_select (int, fd_set *, fd_set *, fd_set *, - struct timespec *, const sigset_t *); + struct timespec *); extern bool android_file_access_p (const char *, int); extern int android_open (const char *, int, int); @@ -86,6 +86,7 @@ extern void android_set_dont_focus_on_map (android_window, bool); extern void android_set_dont_accept_focus (android_window, bool); extern jstring android_build_string (Lisp_Object); +extern jstring android_build_jstring (const char *); extern void android_exception_check (void); extern void android_get_keysym_name (int, char *, size_t); diff --git a/src/androidmenu.c b/src/androidmenu.c index 6fb4963174b..f65b5d3ffd1 100644 --- a/src/androidmenu.c +++ b/src/androidmenu.c @@ -168,11 +168,17 @@ android_dismiss_menu (void *pointer) static void android_process_events_for_menu (int *id) { + int blocked; + /* Set menu_event_id to -1; handle_one_android_event will set it to the event ID upon receiving a context menu event. This can cause a non-local exit. */ x_display_list->menu_event_id = -1; + /* Unblock input completely. */ + blocked = interrupt_input_blocked; + totally_unblock_input (); + /* Now wait for the menu event ID to change. */ while (x_display_list->menu_event_id == -1) { @@ -181,11 +187,11 @@ android_process_events_for_menu (int *id) /* Process pending signals. */ process_pending_signals (); - - /* Maybe quit. */ - maybe_quit (); } + /* Restore the input block. */ + interrupt_input_blocked = blocked; + /* Return the ID. */ *id = x_display_list->menu_event_id; } @@ -420,9 +426,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, /* Next, process events waiting for something to be selected. */ popup_activated_flag = 1; - unblock_input (); android_process_events_for_menu (&id); - block_input (); if (!id) /* This means no menu item was selected. */ @@ -498,8 +502,6 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, } } - Fprint (tem, Qexternal_debugging_output); - unblock_input (); return unbind_to (count, tem); @@ -508,6 +510,229 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, return unbind_to (count, Qnil); } + + +/* Toolkit dialog implementation. */ + +/* Structure describing the EmacsDialog class. */ + +struct android_emacs_dialog +{ + jclass class; + jmethodID create_dialog; + jmethodID add_button; + jmethodID display; +}; + +/* Identifiers associated with the EmacsDialog class. */ +static struct android_emacs_dialog dialog_class; + +static void +android_init_emacs_dialog (void) +{ + jclass old; + + dialog_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsDialog"); + eassert (dialog_class.class); + + old = dialog_class.class; + dialog_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!dialog_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + dialog_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + dialog_class.class, \ + name, signature); \ + eassert (dialog_class.c_name); + +#define FIND_METHOD_STATIC(c_name, name, signature) \ + dialog_class.c_name \ + = (*android_java_env)->GetStaticMethodID (android_java_env, \ + dialog_class.class, \ + name, signature); \ + + FIND_METHOD_STATIC (create_dialog, "createDialog", "(Ljava/lang/String;" + "Ljava/lang/String;)Lorg/gnu/emacs/EmacsDialog;"); + FIND_METHOD (add_button, "addButton", "(Ljava/lang/String;IZ)V"); + FIND_METHOD (display, "display", "()Z"); + +#undef FIND_METHOD +#undef FIND_METHOD_STATIC +} + +static Lisp_Object +android_dialog_show (struct frame *f, Lisp_Object title, + Lisp_Object header, const char **error_name) +{ + specpdl_ref count; + jobject dialog, java_header, java_title, temp; + size_t i; + Lisp_Object item_name, enable, entry; + bool rc; + int id; + jmethodID method; + + if (menu_items_n_panes > 1) + { + *error_name = "Multiple panes in dialog box"; + return Qnil; + } + + /* Do the initial setup. */ + count = SPECPDL_INDEX (); + *error_name = NULL; + + android_push_local_frame (); + + /* Figure out what header to use. */ + java_header = (!NILP (header) + ? android_build_jstring ("Information") + : android_build_jstring ("Question")); + + /* And the title. */ + java_title = android_build_string (title); + + /* Now create the dialog. */ + method = dialog_class.create_dialog; + dialog = (*android_java_env)->CallStaticObjectMethod (android_java_env, + dialog_class.class, + method, java_header, + java_title); + android_exception_check (); + + /* Delete now unused local references. */ + if (java_header) + ANDROID_DELETE_LOCAL_REF (java_header); + ANDROID_DELETE_LOCAL_REF (java_title); + + /* Create the buttons. */ + i = MENU_ITEMS_PANE_LENGTH; + while (i < menu_items_used) + { + item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME); + enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE); + + /* Verify that there is no submenu here. */ + + if (NILP (item_name)) + { + *error_name = "Submenu in dialog items"; + return unbind_to (count, Qnil); + } + + /* Skip past boundaries between buttons on different sides. The + Android toolkit is too silly to understand this + distinction. */ + + if (EQ (item_name, Qquote)) + ++i; + else + { + /* Make sure i is within bounds. */ + if (i > TYPE_MAXIMUM (jint)) + { + *error_name = "Dialog box too big"; + return unbind_to (count, Qnil); + } + + /* Add the button. */ + temp = android_build_string (item_name); + (*android_java_env)->CallVoidMethod (android_java_env, + dialog, + dialog_class.add_button, + temp, (jint) i, + (jboolean) NILP (enable)); + android_exception_check (); + ANDROID_DELETE_LOCAL_REF (temp); + i += MENU_ITEMS_ITEM_LENGTH; + } + } + + /* The dialog is now built. Run it. */ + rc = (*android_java_env)->CallBooleanMethod (android_java_env, + dialog, + dialog_class.display); + android_exception_check (); + + if (!rc) + quit (); + + /* Wait for the menu ID to arrive. */ + android_process_events_for_menu (&id); + + if (!id) + quit (); + + /* Find the selected item, and its pane, to return + the proper value. */ + i = 0; + while (i < menu_items_used) + { + if (EQ (AREF (menu_items, i), Qt)) + i += MENU_ITEMS_PANE_LENGTH; + else if (EQ (AREF (menu_items, i), Qquote)) + /* This is the boundary between left-side elts and right-side + elts. */ + ++i; + else + { + entry = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE); + + if (id == i) + return entry; + + i += MENU_ITEMS_ITEM_LENGTH; + } + } + + return Qnil; +} + +Lisp_Object +android_popup_dialog (struct frame *f, Lisp_Object header, + Lisp_Object contents) +{ + Lisp_Object title; + const char *error_name; + Lisp_Object selection; + specpdl_ref specpdl_count = SPECPDL_INDEX (); + + check_window_system (f); + + /* Decode the dialog items from what was specified. */ + title = Fcar (contents); + CHECK_STRING (title); + record_unwind_protect_void (unuse_menu_items); + + if (NILP (Fcar (Fcdr (contents)))) + /* No buttons specified, add an "Ok" button so users can pop down + the dialog. */ + contents = list2 (title, Fcons (build_string ("Ok"), Qt)); + + list_of_panes (list1 (contents)); + + /* Display them in a dialog box. */ + block_input (); + selection = android_dialog_show (f, title, header, &error_name); + unblock_input (); + + unbind_to (specpdl_count, Qnil); + discard_menu_items (); + + if (error_name) + error ("%s", error_name); + + return selection; +} + #else int @@ -531,6 +756,7 @@ init_androidmenu (void) { #ifndef ANDROID_STUBIFY android_init_emacs_context_menu (); + android_init_emacs_dialog (); #endif } diff --git a/src/androidterm.c b/src/androidterm.c index cc2da279bb3..f19cee5b11b 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -496,10 +496,17 @@ android_update_tools (struct frame *f, struct input_event *ie) /* Build the list of active touches. */ for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points; touchpoint; touchpoint = touchpoint->next) - ie->arg = Fcons (list3i (touchpoint->x, - touchpoint->y, - touchpoint->tool_id), - ie->arg); + { + /* Skip touch points which originated on the tool bar. */ + + if (touchpoint->tool_bar_p) + continue; + + ie->arg = Fcons (list3i (touchpoint->x, + touchpoint->y, + touchpoint->tool_id), + ie->arg); + } } /* Find and return an existing tool pressed against FRAME, identified @@ -951,6 +958,59 @@ handle_one_android_event (struct android_display_info *dpyinfo, touchpoint->next = FRAME_OUTPUT_DATA (any)->touch_points; FRAME_OUTPUT_DATA (any)->touch_points = touchpoint; + /* Figure out whether or not the tool was pressed on the tool + bar. Note that the code which runs when it was is more or + less an abuse of the mouse highlight machinery, but it works + well enough in practice. */ + + if (WINDOWP (any->tool_bar_window) + && WINDOW_TOTAL_LINES (XWINDOW (any->tool_bar_window))) + { + Lisp_Object window; + int x = event->touch.x; + int y = event->touch.y; + + window = window_from_coordinates (any, x, y, 0, true, + true); + + /* If this touch has started in the tool bar, do not + send it to Lisp. Instead, simulate a tool bar + click, releasing it once it goes away. */ + + if (EQ (window, any->tool_bar_window)) + { + /* Call note_mouse_highlight on the tool bar + item. Otherwise, get_tool_bar_item will + return 1. + + This is not necessary when mouse-highlight is + nil. */ + + if (!NILP (Vmouse_highlight)) + { + note_mouse_highlight (any, x, y); + + /* Always allow future mouse motion to + update the mouse highlight, no matter + where it is. */ + memset (&dpyinfo->last_mouse_glyph, 0, + sizeof dpyinfo->last_mouse_glyph); + dpyinfo->last_mouse_glyph_frame = any; + } + + handle_tool_bar_click (any, x, y, true, 0); + + /* Flush any changes made by that to the front + buffer. */ + android_flush_dirty_back_buffer_on (any); + + /* Mark the touch point as being grabbed by the tool + bar. */ + touchpoint->tool_bar_p = true; + goto OTHER; + } + } + /* Now generate the Emacs event. */ inev.ie.kind = TOUCHSCREEN_BEGIN_EVENT; inev.ie.timestamp = event->touch.time; @@ -970,9 +1030,10 @@ handle_one_android_event (struct android_display_info *dpyinfo, touchpoint = android_find_tool (any, event->touch.pointer_id); - /* If it doesn't exist, skip processing this event. */ + /* If it doesn't exist or has been grabbed by the tool bar, skip + processing this event. */ - if (!touchpoint) + if (!touchpoint || touchpoint->tool_bar_p) goto OTHER; /* Otherwise, update the position and send the update event. */ @@ -999,8 +1060,27 @@ handle_one_android_event (struct android_display_info *dpyinfo, *last = touchpoint->next; /* The tool was unlinked. Free it and generate the - appropriate Emacs event. */ + appropriate Emacs event (assuming that it was not + grabbed by the tool bar). */ xfree (touchpoint); + + if (touchpoint->tool_bar_p) + { + /* Do what is necessary to release the tool bar and + possibly trigger a click. */ + + if (any->last_tool_bar_item != -1) + handle_tool_bar_click (any, event->touch.x, + event->touch.y, false, + 0); + + /* Cancel any outstanding mouse highlight. */ + note_mouse_highlight (any, -1, -1); + android_flush_dirty_back_buffer_on (any); + + goto OTHER; + } + inev.ie.kind = TOUCHSCREEN_END_EVENT; inev.ie.timestamp = event->touch.time; @@ -1227,6 +1307,9 @@ android_frame_up_to_date (struct frame *f) /* The frame is now complete, as its contents have been drawn. */ FRAME_ANDROID_COMPLETE_P (f) = true; + + /* Shrink the scanline buffer used by the font backend. */ + sfntfont_android_shrink_scanline_buffer (); unblock_input (); } @@ -1513,7 +1596,7 @@ android_wait_for_event (struct frame *f, int eventtype) break; tmo = timespec_sub (tmo_at, time_now); - if (android_select (0, NULL, NULL, NULL, &tmo, NULL) == 0) + if (android_select (0, NULL, NULL, NULL, &tmo) == 0) break; /* Timeout */ } @@ -4061,6 +4144,7 @@ android_create_terminal (struct android_display_info *dpyinfo) terminal->set_bitmap_icon_hook = android_bitmap_icon; terminal->implicit_set_name_hook = android_implicitly_set_name; terminal->menu_show_hook = android_menu_show; + terminal->popup_dialog_hook = android_popup_dialog; terminal->change_tab_bar_height_hook = android_change_tab_bar_height; terminal->change_tool_bar_height_hook = android_change_tool_bar_height; terminal->set_scroll_bar_default_width_hook diff --git a/src/androidterm.h b/src/androidterm.h index 9aa09877196..c0f862e35fb 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -148,6 +148,9 @@ struct android_touch_point /* The tool ID and the last known X and Y positions. */ int tool_id, x, y; + + /* Whether or not the tool is pressed on the tool bar. */ + bool tool_bar_p; }; struct android_output @@ -410,6 +413,9 @@ extern void android_finalize_font_entity (struct font_entity *); extern Lisp_Object android_menu_show (struct frame *, int, int, int, Lisp_Object, const char **); +extern Lisp_Object android_popup_dialog (struct frame *, Lisp_Object, + Lisp_Object); + extern void init_androidmenu (void); extern void syms_of_androidmenu (void); @@ -417,6 +423,7 @@ extern void syms_of_androidmenu (void); extern const struct font_driver android_sfntfont_driver; +extern void sfntfont_android_shrink_scanline_buffer (void); extern void init_sfntfont_android (void); extern void syms_of_sfntfont_android (void); diff --git a/src/keyboard.c b/src/keyboard.c index 990b5307f14..834049b496a 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -10380,7 +10380,7 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt, if (EVENT_HAS_PARAMETERS (key)) { Lisp_Object kind = EVENT_HEAD_KIND (EVENT_HEAD (key)); - if (EQ (kind, Qmouse_click)) + if (EQ (kind, Qmouse_click) || EQ (kind, Qtouchscreen)) { Lisp_Object window = POSN_WINDOW (EVENT_START (key)); Lisp_Object posn = POSN_POSN (EVENT_START (key)); @@ -12185,7 +12185,9 @@ static const struct event_head head_table[] = { {SYMBOL_INDEX (Qmake_frame_visible), SYMBOL_INDEX (Qmake_frame_visible)}, /* `select-window' should be handled just like `switch-frame' in read_key_sequence. */ - {SYMBOL_INDEX (Qselect_window), SYMBOL_INDEX (Qswitch_frame)} + {SYMBOL_INDEX (Qselect_window), SYMBOL_INDEX (Qswitch_frame)}, + /* Touchscreen events should be prefixed by the posn. */ + {SYMBOL_INDEX (Qtouchscreen_begin), SYMBOL_INDEX (Qtouchscreen)}, }; static Lisp_Object @@ -12895,6 +12897,7 @@ See also `pre-command-hook'. */); "display-monitors-changed-functions"); DEFSYM (Qcoding, "coding"); + DEFSYM (Qtouchscreen, "touchscreen"); Fset (Qecho_area_clear_hook, Qnil); diff --git a/src/keyboard.h b/src/keyboard.h index 3f86a8e03ad..26eecd48b00 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -395,8 +395,17 @@ extern void unuse_menu_items (void); #define EVENT_HEAD(event) \ (EVENT_HAS_PARAMETERS (event) ? XCAR (event) : (event)) -/* Extract the starting and ending positions from a composite event. */ -#define EVENT_START(event) (CAR_SAFE (CDR_SAFE (event))) +/* Extract the starting and ending positions from a composite event. */ + +/* Unlike Lisp `event-start', this also handles touch screen events, + which are not actually mouse events in the general sense. */ +#define EVENT_START(event) \ + ((EQ (EVENT_HEAD (event), Qtouchscreen_begin) \ + || EQ (EVENT_HEAD (event), Qtouchscreen_end)) \ + ? CDR_SAFE (CAR_SAFE (CDR_SAFE (event))) \ + : CAR_SAFE (CDR_SAFE (event))) + +/* This does not handle touchscreen events. */ #define EVENT_END(event) (CAR_SAFE (CDR_SAFE (CDR_SAFE (event)))) /* Extract the click count from a multi-click event. */ diff --git a/src/process.c b/src/process.c index 111e0c80e43..651b5fa035b 100644 --- a/src/process.c +++ b/src/process.c @@ -5689,7 +5689,7 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY nfds = android_select (max_desc + 1, &Available, (check_write ? &Writeok : 0), - NULL, &timeout, NULL); + NULL, &timeout); #else /* Non-macOS HAVE_GLIB builds call thread_select in diff --git a/src/sfnt.c b/src/sfnt.c index ee74ba0fefe..6d58798c599 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -129,8 +129,12 @@ _sfnt_swap32 (uint32_t *value) #define sfnt_swap32(what) (_sfnt_swap32 ((uint32_t *) (what))) /* Read the table directory from the file FD. FD must currently be at - the start of the file, and must be seekable. Return the table - directory upon success, else NULL. */ + the start of the file (or an offset defined in the TTC header, if + applicable), and must be seekable. Return the table directory upon + success, else NULL. + + Value is NULL upon failure, and the offset subtable upon success. + If FD is actually a TrueType collection file, value is -1. */ TEST_STATIC struct sfnt_offset_subtable * sfnt_read_table_directory (int fd) @@ -147,11 +151,34 @@ sfnt_read_table_directory (int fd) if (rc < offset) { + if (rc >= sizeof (uint32_t)) + { + /* Detect a TTC file. In that case, the first long will be + ``ttcf''. */ + sfnt_swap32 (&subtable->scaler_type); + + if (subtable->scaler_type == SFNT_TTC_TTCF) + { + xfree (subtable); + return (struct sfnt_offset_subtable *) -1; + } + } + xfree (subtable); return NULL; } sfnt_swap32 (&subtable->scaler_type); + + /* Bail out early if this font is actually a TrueType collection + file. */ + + if (subtable->scaler_type == SFNT_TTC_TTCF) + { + xfree (subtable); + return (struct sfnt_offset_subtable *) -1; + } + sfnt_swap16 (&subtable->num_tables); sfnt_swap16 (&subtable->search_range); sfnt_swap16 (&subtable->entry_selector); @@ -4183,6 +4210,101 @@ sfnt_find_metadata (struct sfnt_meta_table *meta, +/* TrueType collection format support. */ + +/* Read a TrueType collection header from the font file FD. + FD must currently at the start of the file. + + Value is the header upon success, else NULL. */ + +TEST_STATIC struct sfnt_ttc_header * +sfnt_read_ttc_header (int fd) +{ + struct sfnt_ttc_header *ttc; + size_t size, i; + ssize_t rc; + + /* First, allocate only as much as required. */ + + ttc = xmalloc (sizeof *ttc); + + /* Read the version 1.0 data. */ + + size = SFNT_ENDOF (struct sfnt_ttc_header, num_fonts, + uint32_t); + rc = read (fd, ttc, size); + if (rc < size) + { + xfree (ttc); + return NULL; + } + + /* Now swap what was read. */ + sfnt_swap32 (&ttc->ttctag); + sfnt_swap32 (&ttc->version); + sfnt_swap32 (&ttc->num_fonts); + + /* Verify that the tag is as expected. */ + if (ttc->ttctag != SFNT_TTC_TTCF) + { + xfree (ttc); + return NULL; + } + + /* Now, read the variable length data. Make sure to check for + overflow. */ + + if (INT_MULTIPLY_WRAPV (ttc->num_fonts, + sizeof *ttc->offset_table, + &size)) + { + xfree (ttc); + return NULL; + } + + ttc = xrealloc (ttc, sizeof *ttc + size); + ttc->offset_table = (uint32_t *) (ttc + 1); + rc = read (fd, ttc->offset_table, size); + if (rc < size) + { + xfree (ttc); + return NULL; + } + + /* Swap each of the offsets read. */ + for (i = 0; i < ttc->num_fonts; ++i) + sfnt_swap32 (&ttc->offset_table[i]); + + /* Now, look at the version. If it is earlier than 2.0, then + reading is finished. */ + + if (ttc->version < 0x00020000) + return ttc; + + /* If it is 2.0 or later, then continue to read ul_dsig_tag to + ul_dsig_offset. */ + + size = (SFNT_ENDOF (struct sfnt_ttc_header, ul_dsig_offset, + uint32_t) + - offsetof (struct sfnt_ttc_header, ul_dsig_tag)); + rc = read (fd, &ttc->ul_dsig_offset, size); + if (rc < size) + { + xfree (ttc); + return NULL; + } + + /* Swap what was read. */ + sfnt_swap32 (&ttc->ul_dsig_tag); + sfnt_swap32 (&ttc->ul_dsig_length); + sfnt_swap32 (&ttc->ul_dsig_offset); + + /* All done. */ + return ttc; +} + + + #ifdef TEST struct sfnt_test_dcontext @@ -4397,6 +4519,7 @@ main (int argc, char **argv) unsigned char *string; struct sfnt_name_record record; struct sfnt_meta_table *meta; + struct sfnt_ttc_header *ttc; if (argc != 2) return 1; @@ -4406,8 +4529,41 @@ main (int argc, char **argv) if (fd < 1) return 1; + ttc = NULL; + font = sfnt_read_table_directory (fd); + if (font == (struct sfnt_offset_subtable *) -1) + { + if (lseek (fd, 0, SEEK_SET) != 0) + return 1; + + ttc = sfnt_read_ttc_header (fd); + + if (!ttc) + return 1; + + fprintf (stderr, "TrueType collection: %"PRIu32" fonts installed\n", + ttc->num_fonts); + fflush (stderr); + + printf ("Which font? "); + if (scanf ("%d", &i) == EOF) + return 1; + + if (i >= ttc->num_fonts || i < 0) + { + printf ("out of range\n"); + return 1; + } + + if (lseek (fd, ttc->offset_table[i], SEEK_SET) + != ttc->offset_table[i]) + return 1; + + font = sfnt_read_table_directory (fd); + } + if (!font) { close (fd); @@ -4432,9 +4588,9 @@ main (int argc, char **argv) for (i = 0; i < table->num_subtables; ++i) { fprintf (stderr, "Found cmap table %"PRIu32": %p\n", - subtables[i].offset, data); + subtables[i].offset, data[i]); - if (data) + if (data[i]) fprintf (stderr, " format: %"PRIu16"\n", data[i]->format); } @@ -4552,7 +4708,7 @@ main (int argc, char **argv) if (scanf ("%d %"SCNu32"", &i, &character) == EOF) break; - if (i >= table->num_subtables) + if (i < 0 || i >= table->num_subtables) { printf ("table out of range\n"); continue; @@ -4699,6 +4855,7 @@ main (int argc, char **argv) xfree (hmtx); xfree (name); xfree (meta); + xfree (ttc); return 0; } diff --git a/src/sfnt.h b/src/sfnt.h index 91d7b261cb0..fe6b6ec3dd7 100644 --- a/src/sfnt.h +++ b/src/sfnt.h @@ -875,6 +875,44 @@ enum sfnt_meta_data_tag +/* TrueType collection format support. */ + +struct sfnt_ttc_header +{ + /* TrueType collection ID tag. */ + uint32_t ttctag; + + /* Version of the TTC header. */ + uint32_t version; + + /* Number of fonts in the TTC header. */ + uint32_t num_fonts; + + /* Array of offsets to the offset table for each font in the + file. */ + uint32_t *offset_table; + + /* Tag indicating that a DSIG table exists, or 0. Fields from here + on are only set on version 2.0 headers or later. */ + uint32_t ul_dsig_tag; + + /* Length in bytes of the signature table, or 0 if there is no + signature. */ + uint32_t ul_dsig_length; + + /* Offset in bytes of the dsig table from the beginning of the TTC + file. */ + uint32_t ul_dsig_offset; +}; + +enum sfnt_ttc_tag + { + SFNT_TTC_TTCF = 0x74746366, + SFNT_TTC_DSIG = 0x44534947, + }; + + + #define SFNT_CEIL_FIXED(fixed) \ (!((fixed) & 0177777) ? (fixed) \ : ((fixed) + 0200000) & 037777600000) @@ -960,5 +998,7 @@ extern char *sfnt_find_metadata (struct sfnt_meta_table *, enum sfnt_meta_data_tag, struct sfnt_meta_data_map *); +extern struct sfnt_ttc_header *sfnt_read_ttc_header (int); + #endif /* TEST */ #endif /* _SFNT_H_ */ diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index cddb3fd40f3..1b01a4d9be4 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -31,6 +31,17 @@ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "blockinput.h" #include "android.h" +/* Structure describing a temporary buffer. */ + +struct sfntfont_android_scanline_buffer +{ + /* Size of this buffer. */ + size_t buffer_size; + + /* Pointer to the buffer data. */ + void *buffer_data; +}; + /* Array of directories to search for system fonts. */ const char *system_font_directories[] = { @@ -40,6 +51,49 @@ const char *system_font_directories[] = /* The font cache. */ static Lisp_Object font_cache; +/* The scanline buffer. */ +static struct sfntfont_android_scanline_buffer scanline_buffer; + +/* The largest size of the scanline buffer since the last window + update. */ +static size_t max_scanline_buffer_size; + + + +/* Return a temporary buffer for storing scan lines. + Set BUFFER to the buffer upon success. */ + +#define GET_SCANLINE_BUFFER(buffer, height, stride) \ + do \ + { \ + size_t _size; \ + \ + if (INT_MULTIPLY_WRAPV (height, stride, &_size)) \ + memory_full (SIZE_MAX); \ + \ + if (_size < MAX_ALLOCA) \ + (buffer) = alloca (_size); \ + else \ + { \ + if (_size > scanline_buffer.buffer_size) \ + { \ + (buffer) \ + = scanline_buffer.buffer_data \ + = xrealloc (scanline_buffer.buffer_data, \ + _size); \ + scanline_buffer.buffer_size = _size; \ + } \ + else if (_size <= scanline_buffer.buffer_size) \ + (buffer) = scanline_buffer.buffer_data; \ + /* This is unreachable but clang says it is. */ \ + else \ + emacs_abort (); \ + \ + max_scanline_buffer_size \ + = max (_size, max_scanline_buffer_size); \ + } \ + } while (false); + /* Scale each of the four packed bytes in P in the low 16 bits of P by @@ -205,8 +259,6 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, back_pixel &= ~0x00ff00ff; back_pixel |= rb >> 16 | rb << 16 | 0xff000000; - USE_SAFE_ALLOCA; - prepare_face_for_display (s->f, s->face); /* Build the scanline buffer. Figure out the bounds of the @@ -259,7 +311,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, /* Allocate enough to hold text_rectangle.height, aligned to 8 bytes. Then fill it with the background. */ stride = (text_rectangle.width * sizeof *buffer) + 7 & ~7; - SAFE_NALLOCA (buffer, text_rectangle.height, stride); + GET_SCANLINE_BUFFER (buffer, text_rectangle.height, stride); memset (buffer, 0, text_rectangle.height * stride); if (with_background) @@ -327,10 +379,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, /* If locking the bitmap fails, just discard the data that was allocated. */ if (!bitmap_data) - { - SAFE_FREE (); - return; - } + return; /* Loop over each clip rect in the GC. */ eassert (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_8888); @@ -366,8 +415,33 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, android_damage_window (FRAME_ANDROID_DRAWABLE (s->f), &text_rectangle); - /* Release the temporary scanline buffer. */ - SAFE_FREE (); +#undef MAX_ALLOCA +} + + + +/* Shrink the scanline buffer after a window update. If + max_scanline_buffer_size is not zero, and is less than + scanline_buffer.buffer_size / 2, then resize the scanline buffer to + max_scanline_buffer_size. */ + +void +sfntfont_android_shrink_scanline_buffer (void) +{ + if (!max_scanline_buffer_size) + return; + + if (max_scanline_buffer_size + < scanline_buffer.buffer_size / 2) + { + scanline_buffer.buffer_size + = max_scanline_buffer_size; + scanline_buffer.buffer_data + = xrealloc (scanline_buffer.buffer_data, + max_scanline_buffer_size); + } + + max_scanline_buffer_size = 0; } @@ -437,10 +511,11 @@ loaded before character sets are made available. */) while ((dirent = readdir (dir))) { - /* If it contains (not ends with!) with .ttf, then enumerate - it. */ + /* If it contains (not ends with!) with .ttf or .ttc, then + enumerate it. */ - if (strstr (dirent->d_name, ".ttf")) + if (strstr (dirent->d_name, ".ttf") + || strstr (dirent->d_name, ".ttc")) { sprintf (name, "%s/%s", system_font_directories[i], dirent->d_name); diff --git a/src/sfntfont.c b/src/sfntfont.c index 56977622211..e2d18517fcb 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -77,6 +77,9 @@ struct sfnt_font_desc /* The header of the cmap being used. May be invalid, in which case platform_id will be 500. */ struct sfnt_cmap_encoding_subtable subtable; + + /* The offset of the table directory within PATH. */ + off_t offset; }; /* List of fonts. */ @@ -426,15 +429,17 @@ sfnt_parse_style (Lisp_Object style_name, struct sfnt_font_desc *desc) } } -/* Enumerate the font FILE into the list of system fonts. Return 1 if - it could not be enumerated, 0 otherwise. */ +/* Enumerate the offset subtable SUBTABLES in the file FD, whose file + name is FILE. OFFSET should be the offset of the subtable within + the font file, and is recorded for future use. Value is 1 upon + failure, else 0. */ -int -sfnt_enum_font (const char *file) +static int +sfnt_enum_font_1 (int fd, const char *file, + struct sfnt_offset_subtable *subtables, + off_t offset) { struct sfnt_font_desc *desc; - int fd; - struct sfnt_offset_subtable *subtables; struct sfnt_head_table *head; struct sfnt_name_table *name; struct sfnt_meta_table *meta; @@ -444,18 +449,7 @@ sfnt_enum_font (const char *file) desc = xzalloc (sizeof *desc + strlen (file) + 1); desc->path = (char *) (desc + 1); memcpy (desc->path, file, strlen (file) + 1); - - /* Now open the font for reading. */ - fd = emacs_open (file, O_RDONLY, 0); - - if (fd == -1) - goto bail; - - /* Read the table directory. */ - subtables = sfnt_read_table_directory (fd); - - if (!subtables) - goto bail0; + desc->offset = offset; /* Check that this is a TrueType font. */ if (subtables->scaler_type != SFNT_SCALER_TRUE @@ -511,8 +505,6 @@ sfnt_enum_font (const char *file) xfree (meta); xfree (name); xfree (head); - xfree (subtables); - emacs_close (fd); return 0; bail3: @@ -521,11 +513,84 @@ sfnt_enum_font (const char *file) bail2: xfree (head); bail1: + xfree (desc); + return 1; +} + +/* Enumerate the font FILE into the list of system fonts. Return 1 if + it could not be enumerated, 0 otherwise. + + FILE can either be a TrueType collection file containing TrueType + fonts, or a TrueType font itself. */ + +int +sfnt_enum_font (const char *file) +{ + int fd, rc; + struct sfnt_offset_subtable *subtables; + struct sfnt_ttc_header *ttc; + size_t i; + + /* Now open the font for reading. */ + fd = emacs_open (file, O_RDONLY, 0); + + if (fd == -1) + goto bail; + + /* Read the table directory. */ + subtables = sfnt_read_table_directory (fd); + + if (subtables == (struct sfnt_offset_subtable *) -1) + { + /* This is actually a TrueType container file. Go back to the + beginning and read the TTC header. */ + + if (lseek (fd, 0, SEEK_SET)) + goto bail0; + + ttc = sfnt_read_ttc_header (fd); + + if (!ttc) + goto bail0; + + /* Enumerate each of the fonts in the collection. */ + + for (i = 0; i < ttc->num_fonts; ++i) + { + if (lseek (fd, ttc->offset_table[i], SEEK_SET) + != ttc->offset_table[i]) + continue; + + subtables = sfnt_read_table_directory (fd); + + if (!subtables) + continue; + + sfnt_enum_font_1 (fd, file, subtables, + ttc->offset_table[i]); + xfree (subtables); + } + + /* Always treat reading containers as having been + successful. */ + + emacs_close (fd); + xfree (ttc); + return 0; + } + + if (!subtables) + goto bail0; + + /* Now actually enumerate this font. */ + rc = sfnt_enum_font_1 (fd, file, subtables, 0); xfree (subtables); + emacs_close (fd); + return rc; + bail0: emacs_close (fd); bail: - xfree (desc); return 1; } @@ -1730,6 +1795,12 @@ sfntfont_open (struct frame *f, Lisp_Object font_entity, if (fd == -1) goto bail; + /* Seek to the offset specified. */ + + if (desc->offset + && lseek (fd, desc->offset, SEEK_SET) != desc->offset) + goto bail; + /* Read the offset subtable. */ subtable = sfnt_read_table_directory (fd); diff --git a/xcompile/Makefile.in b/xcompile/Makefile.in index 9f817bb4c53..ca3af4a9586 100644 --- a/xcompile/Makefile.in +++ b/xcompile/Makefile.in @@ -170,7 +170,7 @@ distclean bootstrap-clean: clean # Just in case. rm -rf lib/Makefile lib/gnulib.mk -maintainer-clean: clean +maintainer-clean: distclean bootstrap-clean if [ -e lib/Makefile ]; then \ make -C lib maintainer-clean; \ fi -- cgit v1.3 From a496509cedb17109d0e6297a74e2ff8ed526333c Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 19 Jan 2023 22:19:06 +0800 Subject: Update Android port * .gitignore: Add new files. * INSTALL.android: Explain how to build Emacs for ancient versions of Android. * admin/merge-gnulib (GNULIB_MODULES): Add getdelim. * build-aux/config.guess (timestamp, version): * build-aux/config.sub (timestamp, version): Autoupdate. * configure.ac (BUILD_DETAILS, ANDROID_MIN_SDK): (ANDROID_STUBIFY): Allow specifying CFLAGS via ANDROID_CFLAGS. Add new configure tests for Android API version when not explicitly specified. * doc/emacs/android.texi (Android): Add reference to ``Other Input Devices''. (Android File System): Remove restrictions on directory-files on the assets directory. * doc/emacs/emacs.texi (Top): Add Other Input Devices to menu. * doc/emacs/input.texi (Other Input Devices): New node. * doc/lispref/commands.texi (Touchscreen Events): Document changes to touchscreen input events. * doc/lispref/frames.texi (Pop-Up Menus): Likewise. * etc/NEWS: Announce changes. * java/Makefile.in: Use lib-src/asset-directory-tool to generate an `directory-tree' file placed in /assets. * java/debug.sh: Large adjustments to support Android 2.2 and later. * java/org/gnu/emacs/EmacsContextMenu.java (inflateMenuItems): * java/org/gnu/emacs/EmacsCopyArea.java (perform): * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog): * java/org/gnu/emacs/EmacsDrawLine.java (perform): * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): * java/org/gnu/emacs/EmacsDrawable.java (EmacsDrawable): * java/org/gnu/emacs/EmacsFillPolygon.java (perform): * java/org/gnu/emacs/EmacsFillRectangle.java (perform): * java/org/gnu/emacs/EmacsGC.java (EmacsGC): * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): (destroyHandle): * java/org/gnu/emacs/EmacsSdk7FontDriver.java (draw): Avoid redundant canvas saves and restores. * java/org/gnu/emacs/EmacsService.java (run): * java/org/gnu/emacs/EmacsView.java (EmacsView): (handleDirtyBitmap): * java/org/gnu/emacs/EmacsWindow.java (changeWindowBackground) (EmacsWindow): Make compatible with Android 2.2 and later. * lib-src/Makefile.in (DONT_INSTALL): Add asset-directory-tool on Android.:(asset-directory-tool{EXEEXT}): New target. * lib-src/asset-directory-tool.c (struct directory_tree, xmalloc) (main_1, main_2, main): New file. * lib, m4: Merge from gnulib. This will be reverted before merging to master. * lisp/button.el (button-map): (push-button): * lisp/frame.el (display-popup-menus-p): Improve touchscreen support. * lisp/subr.el (event-start): (event-end): Handle touchscreen events. * lisp/touch-screen.el (touch-screen-handle-timeout): (touch-screen-handle-point-update): (touch-screen-handle-point-up): (touch-screen-track-tap): (touch-screen-track-drag): (touch-screen-drag-mode-line-1): (touch-screen-drag-mode-line): New functions. ([mode-line touchscreen-begin]): ([bottom-divider touchscreen-begin]): Bind new events. * lisp/wid-edit.el (widget-event-point): (widget-keymap): (widget-event-start): (widget-button--check-and-call-button): (widget-button-click): Improve touchscreen support. * src/alloc.c (make_lisp_symbol): Avoid ICE on Android NDK GCC. (mark_pinned_symbols): Likewise. * src/android.c (struct android_emacs_window): New struct. (window_class): New variable. (android_run_select_thread): Add workaround for Android platform bug. (android_extract_long, android_scan_directory_tree): New functions. (android_file_access_p): Use those functions instead. (android_init_emacs_window): New function. (android_init_emacs_gc_class): Update signature of `markDirty'. (android_change_gc, android_set_clip_rectangles): Tell the GC whether or not clip rects were dirtied. (android_swap_buffers): Do not look up method every time. (struct android_dir): Adjust for new directory tree lookup. (android_opendir, android_readdir, android_closedir): Likewise. (android_four_corners_bilinear): Fix coding style. (android_ftruncate): New function. * src/android.h: Update prototypes. Replace ftruncate with android_ftruncate when necessary. * src/androidterm.c (handle_one_android_event): Pacify GCC. Fix touch screen tool bar bug. * src/emacs.c (using_utf8): Fix compilation error. * src/fileio.c (Ffile_system_info): Return Qnil when fsusage.o is not built. * src/filelock.c (BOOT_TIME_FILE): Fix definition for Android. * src/frame.c (Fx_parse_geometry): Fix uninitialized variable uses. * src/keyboard.c (lispy_function_keys): Fix `back'. * src/menu.c (x_popup_menu_1): Handle touch screen events. (Fx_popup_menu): Document changes. * src/sfnt.c (main): Improve tests. * src/sfntfont-android.c (sfntfont_android_put_glyphs): Fix minor problem. (init_sfntfont_android): Check for HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL. * src/sfntfont.c (struct sfnt_font_desc): New fields `adstyle' and `languages'. (sfnt_parse_style): Append tokens to adstyle. (sfnt_parse_languages): New function. (sfnt_enum_font_1): Parse supported languages and adstyle. (sfntfont_list_1): Handle new fields. (sfntfont_text_extents): Fix uninitialized variable use. (syms_of_sfntfont, mark_sfntfont): Adjust accordingly. --- .gitignore | 2 + INSTALL.android | 35 ++ admin/merge-gnulib | 4 +- build-aux/config.guess | 6 +- build-aux/config.sub | 6 +- configure.ac | 160 ++++++- doc/emacs/android.texi | 11 +- doc/emacs/emacs.texi | 6 + doc/emacs/input.texi | 60 +++ doc/lispref/commands.texi | 46 +- doc/lispref/frames.texi | 6 +- etc/NEWS | 20 + java/Makefile.in | 37 +- java/debug.sh | 155 ++++--- java/org/gnu/emacs/EmacsContextMenu.java | 20 +- java/org/gnu/emacs/EmacsCopyArea.java | 11 +- java/org/gnu/emacs/EmacsDialog.java | 3 - java/org/gnu/emacs/EmacsDrawLine.java | 11 +- java/org/gnu/emacs/EmacsDrawRectangle.java | 45 +- java/org/gnu/emacs/EmacsDrawable.java | 2 +- java/org/gnu/emacs/EmacsFillPolygon.java | 11 +- java/org/gnu/emacs/EmacsFillRectangle.java | 12 +- java/org/gnu/emacs/EmacsGC.java | 33 +- java/org/gnu/emacs/EmacsPixmap.java | 77 +++- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 11 +- java/org/gnu/emacs/EmacsService.java | 7 +- java/org/gnu/emacs/EmacsView.java | 34 +- java/org/gnu/emacs/EmacsWindow.java | 80 +++- lib-src/Makefile.in | 11 + lib-src/asset-directory-tool.c | 280 ++++++++++++ lib/faccessat.c | 4 - lib/fpending.c | 2 +- lib/getdelim.c | 147 ++++++ lib/getline.c | 27 ++ lib/gnulib.mk.in | 40 ++ lib/stdalign.in.h | 109 +---- lib/stdio-impl.h | 8 +- lisp/button.el | 30 +- lisp/frame.el | 8 +- lisp/subr.el | 14 +- lisp/touch-screen.el | 189 +++++++- lisp/wid-edit.el | 122 +++-- m4/getdelim.m4 | 111 +++++ m4/getline.m4 | 109 +++++ m4/gnulib-comp.m4 | 32 ++ m4/stdalign.m4 | 127 ++++-- m4/unistd_h.m4 | 1 + m4/utimens.m4 | 3 +- m4/utimensat.m4 | 4 +- m4/xattr.m4 | 42 +- src/alloc.c | 34 +- src/android.c | 569 +++++++++++++++++++----- src/android.h | 8 + src/androidterm.c | 5 +- src/emacs.c | 8 + src/fileio.c | 15 + src/filelock.c | 12 +- src/frame.c | 2 + src/keyboard.c | 6 +- src/menu.c | 9 +- src/sfnt.c | 10 +- src/sfntfont-android.c | 4 +- src/sfntfont.c | 111 ++++- xcompile/lib/faccessat.c | 4 - xcompile/lib/getdelim.c | 147 ++++++ xcompile/lib/getline.c | 27 ++ xcompile/lib/gnulib.mk.in | 42 ++ xcompile/lib/qcopy-acl.c | 36 +- xcompile/lib/verify.h | 8 +- xcompile/malloc/dynarray-skeleton.c | 528 ++++++++++++++++++++++ xcompile/malloc/dynarray.h | 177 ++++++++ xcompile/malloc/dynarray_at_failure.c | 40 ++ xcompile/malloc/dynarray_emplace_enlarge.c | 77 ++++ xcompile/malloc/dynarray_finalize.c | 66 +++ xcompile/malloc/dynarray_resize.c | 68 +++ xcompile/malloc/dynarray_resize_clear.c | 39 ++ xcompile/malloc/scratch_buffer.h | 135 ++++++ xcompile/malloc/scratch_buffer_dupfree.c | 41 ++ xcompile/malloc/scratch_buffer_grow.c | 56 +++ xcompile/malloc/scratch_buffer_grow_preserve.c | 67 +++ xcompile/malloc/scratch_buffer_set_array_size.c | 64 +++ 81 files changed, 4118 insertions(+), 628 deletions(-) create mode 100644 doc/emacs/input.texi create mode 100644 lib-src/asset-directory-tool.c create mode 100644 lib/getdelim.c create mode 100644 lib/getline.c create mode 100644 m4/getdelim.m4 create mode 100644 m4/getline.m4 create mode 100644 xcompile/lib/getdelim.c create mode 100644 xcompile/lib/getline.c create mode 100644 xcompile/malloc/dynarray-skeleton.c create mode 100644 xcompile/malloc/dynarray.h create mode 100644 xcompile/malloc/dynarray_at_failure.c create mode 100644 xcompile/malloc/dynarray_emplace_enlarge.c create mode 100644 xcompile/malloc/dynarray_finalize.c create mode 100644 xcompile/malloc/dynarray_resize.c create mode 100644 xcompile/malloc/dynarray_resize_clear.c create mode 100644 xcompile/malloc/scratch_buffer.h create mode 100644 xcompile/malloc/scratch_buffer_dupfree.c create mode 100644 xcompile/malloc/scratch_buffer_grow.c create mode 100644 xcompile/malloc/scratch_buffer_grow_preserve.c create mode 100644 xcompile/malloc/scratch_buffer_set_array_size.c (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/.gitignore b/.gitignore index cf739958403..3bc40c67489 100644 --- a/.gitignore +++ b/.gitignore @@ -231,6 +231,7 @@ ID # Executables. *.exe a.out +lib-src/asset-directory-tool lib-src/be-resources lib-src/blessmail lib-src/ctags @@ -253,6 +254,7 @@ nextstep/GNUstep/Emacs.base/Resources/Info-gnustep.plist src/bootstrap-emacs src/emacs src/emacs-[0-9]* +src/sfnt src/Emacs src/temacs src/dmpstruct.h diff --git a/INSTALL.android b/INSTALL.android index ee54b053a9b..63c856fe56c 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -59,6 +59,41 @@ built for. The generated package can be uploaded onto an SD card (or similar medium) and installed on-device. +BUILDING WITH OLD NDK VERSIONS + +Building Emacs with an old version of the Android NDK requires special +setup. This is because there is no separate C compiler binary for +each version of Android in those versions of the NDK. + +Before running `configure', you must identify three variables: + + - What kind of Android system you are building Emacs for. + + - The minimum API version of Android you want to build Emacs for. + + - The locations of the system root and include files for that + version of Android in the NDK. + +That information must then be specified as arguments to the NDK C +compiler. For example: + + ./configure [...] \ + ANDROID_CC="i686-linux-android-gcc \ + --sysroot=/path/to/ndk/platforms/android-14/arch-x86/" + ANDROID_CFLAGS="-isystem /path/to/ndk/sysroot/usr/include \ + -isystem /path/to/ndk/sysroot/usr/include/i686-linux-android \ + -D__ANDROID_API__=14" + +Where __ANDROID_API__ and the version identifier in +"platforms/android-14" defines the version of Android you are building +for, and the include directories specify the paths to the relevant +Android headers. In addition, it may be necessary to specify +"-gdwarf-2", due to a bug in the Android NDK. + +Emacs is known to build for Android 2.2 (API version 8) or later, and +run on Android 2.3 or later. It is supposed to run on Android 2.2 as +well. + This file is part of GNU Emacs. diff --git a/admin/merge-gnulib b/admin/merge-gnulib index b99e38672a4..38f1418c759 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -37,7 +37,7 @@ GNULIB_MODULES=' fchmodat fcntl fcntl-h fdopendir file-has-acl filemode filename filevercmp flexmember fpieee free-posix fstatat fsusage fsync futimens - getloadavg getopt-gnu getrandom gettime gettimeofday gitlog-to-changelog + getline getloadavg getopt-gnu getrandom gettime gettimeofday gitlog-to-changelog ieee754-h ignore-value intprops largefile libgmp lstat manywarnings memmem-simple mempcpy memrchr memset_explicit minmax mkostemp mktime @@ -141,7 +141,7 @@ cp -- "$gnulib_srcdir"/lib/af_alg.h \ ./autogen.sh # Finally, update the files in lib/ to xcompile/lib. -rsync "$src"/lib "$src"/xcompile +rsync -r "$src"/lib "$src"/xcompile # Remove unnecessary files. rm -f "$src"/xcompile/lib/*.mk.in "$src"/xcompile/lib/Makefile.in diff --git a/build-aux/config.guess b/build-aux/config.guess index 980b0208381..69188da73d7 100755 --- a/build-aux/config.guess +++ b/build-aux/config.guess @@ -1,10 +1,10 @@ #! /bin/sh # Attempt to guess a canonical system name. -# Copyright 1992-2022 Free Software Foundation, Inc. +# Copyright 1992-2023 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale -timestamp='2022-09-17' +timestamp='2023-01-01' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -60,7 +60,7 @@ version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. -Copyright 1992-2022 Free Software Foundation, Inc. +Copyright 1992-2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." diff --git a/build-aux/config.sub b/build-aux/config.sub index baf1512b3c0..d6731d655c0 100755 --- a/build-aux/config.sub +++ b/build-aux/config.sub @@ -1,10 +1,10 @@ #! /bin/sh # Configuration validation subroutine script. -# Copyright 1992-2022 Free Software Foundation, Inc. +# Copyright 1992-2023 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale -timestamp='2022-09-17' +timestamp='2023-01-01' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -76,7 +76,7 @@ Report bugs and patches to ." version="\ GNU config.sub ($timestamp) -Copyright 1992-2022 Free Software Foundation, Inc. +Copyright 1992-2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." diff --git a/configure.ac b/configure.ac index 92cf58fdf28..1f5618a172f 100644 --- a/configure.ac +++ b/configure.ac @@ -31,8 +31,19 @@ if test "$XCONFIGURE" = "android"; then # Android! AC_MSG_NOTICE([called to recursively configure Emacs \ for Android.]) - # Set CC to ANDROID_CC. + # Set CC to ANDROID_CC and CFLAGS to ANDROID_CFLAGS. CC=$ANDROID_CC + # Set -Wno-implicit-function-declaration. Building Emacs for older + # versions of Android requires configure tests to fail if the + # functions are not defined, as the Android library in the NDK + # defines subroutines that are not available in the headers being + # used. + CFLAGS="$ANDROID_CFLAGS -Werror=implicit-function-declaration" + # Don't explicitly enable support for large files unless Emacs is + # being built for API 21 or later. Otherwise, mmap does not work. + if test "$ANDROID_SDK" -lt "21"; then + enable_largefile=no + fi fi dnl Set emacs_config_options to the options of 'configure', quoted for the shell, @@ -758,7 +769,10 @@ tools such as aapt, dx, and aidl): The cross-compiler should then be specified: - ANDROID_CC=/path/to/armv7a-linux-androideabi19-clang]) + ANDROID_CC=/path/to/armv7a-linux-androideabi19-clang + +In addition, you may pass any special arguments to the cross-compiler +via the ANDROID_CFLAGS environment variable.]) elif test "$with_android" = "no" || test "$with_android" = ""; then ANDROID=no else @@ -808,6 +822,29 @@ EOF a valid path to android.jar. See config.log for more details.]) fi + AC_CACHE_CHECK([whether or not android.jar is new enough], + [emacs_cv_android_s_or_later], + AS_IF([rm -f conftest.class +cat << EOF > conftest.java + +import android.os.Build; + +class conftest +{ + private static int test = Build.VERSION_CODES.S; +} + +EOF +("$JAVAC" -classpath "$with_android" -target 1.7 -source 1.7 conftest.java \ + -d . >&AS_MESSAGE_LOG_FD 2>&1) && test -s conftest.class && rm -f conftest.class], + [emacs_cv_android_s_or_later=yes], + [emacs_cv_android_s_or_later=no])) + + if test "$emacs_cv_android_s_or_later" = "no"; then + AC_MSG_ERROR([Emacs must be built with an android.jar file produced for \ +Android 13 (S) or later.]) + fi + ANDROID_JAR="$with_android" AC_PATH_PROGS([AAPT], [aapt], [], "${SDK_BUILD_TOOLS}:$PATH") @@ -845,6 +882,7 @@ for your machine. For example: AC_MSG_CHECKING([for the kind of Android system Emacs is being built for]) cc_target=`${ANDROID_CC} -v 2>&1 | sed -n 's/Target: //p'` case "$cc_target" in +[ *i[3-6]86*) android_abi=x86 ;; *x86_64*) android_abi=x86_64 @@ -857,10 +895,13 @@ for your machine. For example: ;; *arm*) android_abi=armeabi ;; +] *) 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 variable when you ran configure.]) +in the ANDROID_CC variable when you ran configure. + +The compiler target is: $cc_target]) ;; esac AC_MSG_RESULT([$android_abi]) @@ -879,18 +920,121 @@ in the ANDROID_CC variable when you ran configure.]) AC_MSG_RESULT([$android_sdk]) ANDROID_MIN_SDK=$android_sdk else - AC_MSG_RESULT([unknown ($cc_target); assuming 8]) - AC_MSG_WARN([configure could not determine the versions of Android \ + # This is probably GCC. + [ cat << EOF > conftest.c +#include +extern const char *foo; + +int +main (void) +{ +#if __ANDROID_API__ < 7 + foo = "emacs_api_6"; +#elif __ANDROID_API__ < 8 + foo = "emacs_api_7"; +#elif __ANDROID_API__ < 9 + foo = "emacs_api_8"; +#elif __ANDROID_API__ < 10 + foo = "emacs_api_9"; +#elif __ANDROID_API__ < 11 + foo = "emacs_api_10"; +#elif __ANDROID_API__ < 12 + foo = "emacs_api_11"; +#elif __ANDROID_API__ < 13 + foo = "emacs_api_12"; +#elif __ANDROID_API__ < 14 + foo = "emacs_api_13"; +#elif __ANDROID_API__ < 15 + foo = "emacs_api_14"; +#elif __ANDROID_API__ < 16 + foo = "emacs_api_15"; +#elif __ANDROID_API__ < 17 + foo = "emacs_api_16"; +#elif __ANDROID_API__ < 18 + foo = "emacs_api_17"; +#elif __ANDROID_API__ < 19 + foo = "emacs_api_18"; +#elif __ANDROID_API__ < 20 + foo = "emacs_api_19"; +#elif __ANDROID_API__ < 21 + foo = "emacs_api_20"; +#elif __ANDROID_API__ < 22 + foo = "emacs_api_21"; +#elif __ANDROID_API__ < 23 + foo = "emacs_api_22"; +#elif __ANDROID_API__ < 24 + foo = "emacs_api_23"; +#elif __ANDROID_API__ < 25 + foo = "emacs_api_24"; +#elif __ANDROID_API__ < 26 + foo = "emacs_api_25"; +#elif __ANDROID_API__ < 27 + foo = "emacs_api_26"; +#elif __ANDROID_API__ < 28 + foo = "emacs_api_27"; +#elif __ANDROID_API__ < 29 + foo = "emacs_api_28"; +#elif __ANDROID_API__ < 30 + foo = "emacs_api_29"; +#elif __ANDROID_API__ < 31 + foo = "emacs_api_30"; +#elif __ANDROID_API__ < 32 + foo = "emacs_api_31"; +#elif __ANDROID_API__ < 33 + foo = "emacs_api_32"; +#elif __ANDROID_API__ < 34 + foo = "emacs_api_33"; +#else + foo = "emacs_api_future"; +#endif +} +EOF] + + AC_CACHE_VAL([emacs_cv_android_api], + [$ANDROID_CC $ANDROID_CFLAGS -c conftest.c -o conftest.o \ + && emacs_cv_android_api=`grep -ao -E \ + "emacs_api_([[0-9][0-9]]?|future)" conftest.o`]) + android_sdk="$emacs_cv_android_api" + rm -rf conftest.c conftest.o + + # If this version of the NDK requires __ANDROID_API__ to be + # specified, then complain to the user. + if test "$android_sdk" = "emacs_api_future"; then + AC_MSG_ERROR([The version of Android to build for was not specified. +You must tell the Android compiler what version of Android to build for, +by defining the __ANDROID_API__ preprocessor macro in ANDROID_CC, like so: + + ANDROID_CC="/path/to/ndk/arm-linux-android-gcc -D__ANDROID_API__=8"]) + fi + + if test -n "$android_sdk"; then + android_sdk=`echo $android_sdk | sed -n 's/emacs_api_//p'` + AC_MSG_RESULT([$android_sdk]) + ANDROID_MIN_SDK=$android_sdk + else + AC_MSG_RESULT([unknown ($cc_target); assuming 8]) + AC_MSG_ERROR([configure could not determine the versions of Android \ a binary built with this compiler will run on. The generated application \ package will likely install on older systems but crash on startup.]) + android_sdk=8 + fi fi AC_SUBST([ANDROID_MIN_SDK]) + # Now tell java/Makefile if Emacs is being built for Android 4.3 or + # earlier. + ANDROID_SDK_18_OR_EARLIER= + if test "$android_sdk" -lt "18"; then + ANDROID_SDK_18_OR_EARLIER=yes + fi + AC_SUBST([ANDROID_SDK_18_OR_EARLIER]) + # Save confdefs.h and config.log for now. mv -f confdefs.h _confdefs.h mv -f config.log _config.log - AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" $0], [], + AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" \ + ANDROID_SDK="$android_sdk" $0], [], [AC_MSG_ERROR([Failed to cross-configure Emacs for android.])]) # Now set ANDROID to yes. @@ -1989,7 +2133,6 @@ AC_CACHE_CHECK([for math library], d = frexp (d, &i); d = ldexp (d, i); d = log (d); - d = log2 (d); d = log10 (d); d = pow (d, d); d = rint (d); @@ -2283,6 +2426,9 @@ for Android, but all API calls need to be stubbed out]) # Link with the sfnt font library and sfntfont.o, along with # sfntfont-android.o. ANDROID_OBJ="$ANDROID_OBJ sfnt.o sfntfont.o sfntfont-android.o" + + # Check for some functions not always present in the NDK. + AC_CHECK_DECLS([android_get_device_api_level]) fi fi diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 8806a2a2bf6..01a2e68a97e 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -1,5 +1,5 @@ @c This is part of the Emacs manual. -@c Copyright (C) 2021--2023 Free Software Foundation, Inc. +@c Copyright (C) 2023 Free Software Foundation, Inc. @c See file emacs.texi for copying conditions. @node Android @appendix Emacs and Android @@ -9,6 +9,10 @@ Alliance. This section describes the peculiarities of using Emacs on an Android device running Android 2.2 or later. + Android devices commonly rely on user input through a touch screen +or digitizer device. For more information about using them with +Emacs, @pxref{other Input Devices}. + @menu * What is Android?:: Preamble. * Android Startup:: Starting up Emacs on Android. @@ -108,11 +112,6 @@ There are no @file{.} and @file{..} directories inside the @item Files in the @file{/assets} directory are always read only, and have to be completely read in to memory each time they are opened. - -@item -@code{directory-files} does not return a useful value on the -@file{/assets} directory itself, and does not return subdirectories -inside subdirectories of the @file{/assets} directory. @end itemize Aside from the @file{/assets} directory, Android programs normally diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index d21d09bf920..9fd169cd5cc 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -225,6 +225,7 @@ Appendices * Haiku:: Using Emacs on Haiku. * Android:: Using Emacs on Android. * Microsoft Windows:: Using Emacs on Microsoft Windows and MS-DOS. +* Other Input Devices:: Using Emacs with other input devices. * Manifesto:: What's GNU? Gnu's Not Unix! * Glossary:: Terms used in this manual. @@ -1265,6 +1266,10 @@ Emacs and Android * Android Environment:: Running Emacs under Android. * Android Fonts:: Font selection under Android. +Emacs and unconventional input devices + +* Touchscreens:: Using Emacs on touchscreens. + Emacs and Microsoft Windows/MS-DOS * Windows Startup:: How to start Emacs on Windows. @@ -1636,6 +1641,7 @@ Lisp programming. @include macos.texi @include haiku.texi @include android.texi +@include input.texi @c Includes msdos-xtra. @include msdos.texi @include gnu.texi diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi new file mode 100644 index 00000000000..0e029c401e3 --- /dev/null +++ b/doc/emacs/input.texi @@ -0,0 +1,60 @@ +@c This is part of the Emacs manual. +@c Copyright (C) 2023 Free Software Foundation, Inc. +@c See file emacs.texi for copying conditions. +@node Other Input Devices +@appendix Emacs and unconventional input devices +@cindex other input devices + + Emacs was originally developed with the assumption that users will +be sitting in front of a desktop computer, with a keyboard and perhaps +a suitable pointing device such as a mouse. + + However, recent developments in the X Window System, and in other +operating systems such as Android, mean that this assumption no longer +holds true. As a result, Emacs now has support for other kinds of +input devices, which is detailed here. + +@menu +* Touchscreens:: Using Emacs on touchscreens. +@end menu + +@node Touchscreens +@section Using Emacs on touchscreens +@cindex touchscreens + + Touchscreen input works by having the user press tools onto the +screen, which can be his own fingers, or a pointing device such as a +stylus, in order to manipulate the contents there in. + + When running under the X Window System or Android, Emacs +automatically detects and maps the following touchscreen gestures to +common actions: + +@itemize @bullet +@item +@cindex tapping, touchscreens + ``Tapping'', meaning to briefly place and lift a tool from the +display, will result in Emacs selecting the window that was tapped, +and executing any command bound to @code{mouse-1} at that location in +the window. If the tap happened on top of a link (@pxref{Mouse +References}), then Emacs will follow the link instead. + +@item +@cindex scrolling, touchscreens + ``Scrolling'', meaning to place a tool on the display and move it up +or down, will result in Emacs scrolling the window contents in the +direction where the tool moves. + +@item +@cindex dragging, touchscreens + ``Dragging'', meaning to place a tool on the display and leave it +there for a while before moving the tool around, will make Emacs set +the point to where the tool was and begin selecting text under the +tool as it moves around, much like what would happen if @code{mouse-1} +were to be held down. @xref{Mouse Commands}. +@end itemize + +@vindex touch-screen-delay + By default, Emacs considers a tool as having been left on the +display for a while after 0.7 seconds, but this can be changed by +customizing the variable @code{touch-screen-delay}. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 6d1ce145fbf..59367a2cecc 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -1999,24 +1999,24 @@ each point is represented by a cons of an arbitrary number identifying the point and a mouse position list (@pxref{Click Events}) specifying the position of the finger when the event occurred. -In addition, @code{touchscreen-begin} events also have imaginary -prefixes keys added by @code{read-key-sequence} when they originate on -top of a special part of a frame or window. @xref{Key Sequence -Input}. The reason the other touch screen events do not undergo this -treatment is that they are rarely useful without being used in tandem -from their corresponding @code{touchscreen-begin} events. - @table @code @cindex @code{touchscreen-begin} event @item (touchscreen-begin @var{point}) This event is sent when @var{point} is created by the user pressing a finger against the touchscreen. +These events also have imaginary prefixes keys added by +@code{read-key-sequence} when they originate on top of a special part +of a frame or window. @xref{Key Sequence Input}. The reason the +other touch screen events do not undergo this treatment is that they +are rarely useful without being used in tandem from their +corresponding @code{touchscreen-begin} events. + @cindex @code{touchscreen-update} event @item (touchscreen-update @var{points}) This event is sent when a point on the touchscreen has changed position. @var{points} is a list of touch points containing the -up-to-date positions of each touch point currently on the touchscreen. +up-to-date positions of each touch point currently on the touchscxcompile/reen. @cindex @code{touchscreen-end} event @item (touchscreen-end @var{point}) @@ -2030,6 +2030,36 @@ generate any corresponding @code{touchscreen-begin} or @code{touchscreen-end} events; instead, the menu bar may be displayed when @code{touchscreen-end} should have been delivered. +@cindex handling touch screen events +@cindex tap and drag, touch screen gestures +Emacs provides two functions to handle touch screen events. They are +intended to be used by a command bound to @code{touchscreen-begin} to +handle common gestures. + +@defun touch-screen-track-tap event &optional update data +This function is used to track a single ``tap'' gesture originating +from the @code{touchscreen-begin} event @var{event}, often used to +set the point or to activate a button. It waits for a +@code{touchscreen-end} event with the same touch identifier to arrive, +at which point it returns @code{t}, signifying the end of the gesture. + +If a @code{touchscreen-update} event arrives in the mean time and +contains at least one touchpoint with the same identifier as in +@var{event}, the function @var{update} is called with two arguments, +the list of touchpoints in that @code{touchscreen-update} event, and +@var{data}. + +If any other event arrives in the mean time, @code{nil} is returned. +The caller should not perform any action in that case. +@end defun + +@defun touch-screen-track-drag event update &optional data +This function is used to track a single ``drag'' gesture originating +from the @code{touchscreen-begin} event @code{event}. Currently, it +behaves identically to @code{touch-screen-track-tap}, but differences +are anticipated in the future. +@end defun + @node Focus Events @subsection Focus Events @cindex focus event diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index fb96b96ec8c..2ceafab7a6b 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -3725,9 +3725,9 @@ This function displays a pop-up menu and returns an indication of what selection the user makes. The argument @var{position} specifies where on the screen to put the -top left corner of the menu. It can be either a mouse button event -(which says to put the menu where the user actuated the button) or a -list of this form: +top left corner of the menu. It can be either a mouse button or +@code{touchscreen-begin} event (which says to put the menu where the +user actuated the button) or a list of this form: @example ((@var{xoffset} @var{yoffset}) @var{window}) diff --git a/etc/NEWS b/etc/NEWS index cde6783349f..a309cd17a93 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -24,6 +24,12 @@ applies, and please also update docstrings as needed. * Installation Changes in Emacs 30.1 +** Emacs has been ported to the Android operating system. +This requires Emacs to be compiled on another computer. The Android +NDK, SDK, and a suitable Java compiler must also be installed. + +See the file 'INSTALL.android' for more details. + * Startup Changes in Emacs 30.1 @@ -53,6 +59,10 @@ trash when deleting. Default is nil. * Editing Changes in Emacs 30.1 +** Emacs now has better support for touchscreen events. +Many touch screen gestures are now implemented, as is support for +tapping buttons and opening menus. + ** New helper variable 'transpose-sexps-function'. Emacs now can set this variable to customize the behavior of the 'transpose-sexps' function. @@ -189,6 +199,16 @@ This user option has been obsoleted in Emacs 27, use * Lisp Changes in Emacs 30.1 ++++ +** 'x-popup-menu' now understands touch screen events. +When a 'touchscreen-begin' or 'touchscreen-end' event is passed as the +POSITION argument, it will behave as if that event was a mouse event. + +** New functions for handling touch screen events. +The new functions 'touch-screen-track-tap' and +'touch-screen-track-drag' handle tracking common touch screen gestures +from within a command. + ** New or changed byte-compilation warnings --- diff --git a/java/Makefile.in b/java/Makefile.in index c539fb0f1fb..22c912fdce5 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -21,6 +21,10 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ version = @version@ +# This is the host lib-src and lib, not the cross compiler's lib-src. +libsrc = ../lib-src +EXEEXT = @EXEEXT@ + -include ${top_builddir}/src/verbose.mk SHELL = @SHELL@ @@ -29,14 +33,25 @@ AAPT = @AAPT@ D8 = @D8@ ZIPALIGN = @ZIPALIGN@ JARSIGNER = @JARSIGNER@ +JARSIGNER_FLAGS = ANDROID_JAR = @ANDROID_JAR@ ANDROID_ABI = @ANDROID_ABI@ +ANDROID_SDK_18_OR_EARLIER = @ANDROID_SDK_18_OR_EARLIER@ WARN_JAVAFLAGS = -Xlint:deprecation JAVAFLAGS = -classpath "$(ANDROID_JAR):." -target 1.7 -source 1.7 \ $(WARN_JAVAFLAGS) -SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 +# Android 4.3 and earlier require Emacs to be signed with a different +# digital signature algorithm. + +ifneq (,$(ANDROID_SDK_18_OR_EARLIER)) +JARSIGNER_FLAGS = -sigalg MD5withRSA -digestalg SHA1 +else +JARSIGNER_FLAGS = +endif + +SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 $(JARSIGNER_FLAGS) JAVA_FILES = $(shell find . -type f -name *.java) CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) @@ -82,7 +97,14 @@ CROSS_LIBS = ../xcompile/src/libemacs.so ../xcompile/lib-src/ctags ../xcompile/lib-src/ebrowse &: make -C ../xcompile lib-src/$(notdir $@) -emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml +# This is needed to generate the ``.directory-tree'' file used by the +# Android emulations of readdir and faccessat. + +$(libsrc)/asset-directory-tool: + $(MAKE) -C $(libsrc) $(notdir $@) + +emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ + AndroidManifest.xml # Make the working directory for this stuff rm -rf install_temp mkdir -p install_temp/lib/$(ANDROID_ABI) @@ -106,6 +128,9 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml rm -rf $${subdir}/[mM]akefile*[.-]in ; \ rm -rf $${subdir}/Makefile; \ done +# Generate the directory tree for those directories. + $(libsrc)/asset-directory-tool install_temp/assets \ + install_temp/assets/directory-tree # Install architecture dependents to lib/$(ANDROID_ABI). This # perculiar naming scheme is required to make Android preserve these # binaries upon installation. @@ -120,10 +145,12 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml cp -f $$file install_temp/lib/$(ANDROID_ABI); \ fi \ done -# Package everything. - $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f -M AndroidManifest.xml +# Package everything. Specifying the assets on this command line is +# necessary for AAssetManager_getNextFileName to work on old versions +# of Android. + $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f -M AndroidManifest.xml \ + -A install_temp/assets pushd install_temp; $(AAPT) add ../$@ `find lib -type f`; popd - pushd install_temp; $(AAPT) add ../$@ `find assets -type f`; popd rm -rf install_temp # Makefile itself. diff --git a/java/debug.sh b/java/debug.sh index aa80aeeebcd..7008664c049 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -93,7 +93,7 @@ while [ $# -gt 0 ]; do shift done -if [ -z $devices ]; then +if [ -z "$devices" ]; then echo "No devices are available." exit 1 fi @@ -117,25 +117,43 @@ if [ -z $app_data_dir ]; then echo "Is it installed?" fi -echo "Found application data directory at $app_data_dir..." - -# Find which PIDs are associated with org.gnu.emacs -package_uid=`adb -s $device shell run-as $package id -u` - -if [ -z $package_uid ]; then - echo "Failed to obtain UID of packages named $package" - exit 1 -fi - -# First, run ps -u $package_uid -o PID,CMD to fetch the list of -# process IDs. -package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` - -# Next, remove lines matching "ps" itself. -package_pids=`awk -- '{ - if (!match ($0, /(PID|ps)/)) - print $1 -}' <<< $package_pids` +echo "Found application data directory at" "$app_data_dir" + +# Generate an awk script to extract PIDs from Android ps output. It +# is enough to run `ps' as the package user on newer versions of +# Android, but that doesn't work on Android 2.3. +cat << EOF > tmp.awk +BEGIN { + pid = 0; + pid_column = 2; +} + +{ + # Remove any trailing carriage return from the input line. + gsub ("\r", "", \$NF) + + # If this is line 1, figure out which column contains the PID. + if (NR == 1) + { + for (n = 1; n <= NF; ++n) + { + if (\$n == "PID") + pid_column=n; + } + } + else if (\$NF == "$package") + print \$pid_column +} +EOF + +# Make sure that file disappears once this script exits. +trap "rm -f $(pwd)/tmp.awk" 0 + +# First, run ps to fetch the list of process IDs. +package_pids=`adb -s $device shell ps` + +# Next, extract the list of PIDs currently running. +package_pids=`awk -f tmp.awk <<< $package_pids` if [ "$attach_existing" != "yes" ]; then # Finally, kill each existing process. @@ -149,19 +167,20 @@ if [ "$attach_existing" != "yes" ]; then echo "Starting activity $activity and attaching debugger" # Exit if the activity could not be started. - adb -s $device shell am start -D "$package/$activity" + adb -s $device shell am start -D -n "$package/$activity" if [ ! $? ]; then exit 1; fi + # Sleep for a bit. Otherwise, the process may not have started + # yet. + sleep 1 + # Now look for processes matching the package again. - package_pids=`adb -s $device shell run-as $package ps -u $package_uid -o PID,CMD` + package_pids=`adb -s $device shell ps` # Next, remove lines matching "ps" itself. - package_pids=`awk -- '{ - if (!match ($0, /(PID|ps)/)) - print $1 -}' <<< $package_pids` + package_pids=`awk -f tmp.awk <<< $package_pids` fi pid=$package_pids @@ -170,10 +189,10 @@ num_pids=`wc -w <<< "$package_pids"` if [ $num_pids -gt 1 ]; then echo "More than one process was started:" echo "" - adb -s $device shell run-as $package ps -u $package_uid | awk -- '{ - if (!match ($0, /ps/)) - print $0 - }' + adb -s $device shell run-as $package ps | awk -- "{ + if (!match (\$0, /ps/) && match (\$0, /$package/)) + print \$0 + }" echo "" printf "Which one do you want to attach to? " read pid @@ -182,10 +201,12 @@ elif [ -z $package_pids ]; then exit 1 fi -# This isn't necessary when attaching gdb to an existing process. +# If either --jdb was specified or debug.sh is not connecting to an +# existing process, then store a suitable JDB invocation in +# jdb_command. GDB will then run JDB to unblock the application from +# the wait dialog after startup. + if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then - # Start JDB to make the wait dialog disappear. - echo "Attaching JDB to unblock the application." adb -s $device forward --remove-all adb -s $device forward "tcp:$jdb_port" "jdwp:$pid" @@ -203,20 +224,42 @@ if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then $jdb_command exit 1 fi +fi - exec 4<> /tmp/file-descriptor-stamp +if [ -n "$jdb_command" ]; then + echo "Starting JDB to unblock application." - # Now run JDB with IO redirected to file descriptor 4 in a subprocess. - $jdb_command <&4 >&4 & + # Start JDB to unblock the application. + coproc JDB { $jdb_command; } - character= - # Next, wait until the prompt is found. - while read -n1 -u 4 character; do - if [ "$character" = ">" ]; then - echo "JDB attached successfully" - break; + # Tell JDB to first suspend all threads. + echo "suspend" >&${JDB[1]} + + # Tell JDB to print a magic string once the program is + # initialized. + echo "print \"__verify_jdb_has_started__\"" >&${JDB[1]} + + # Now wait for JDB to give the string back. + line= + while :; do + read -u ${JDB[0]} line + if [ ! $? ]; then + echo "Failed to read JDB output" + exit 1 fi + + case "$line" in + *__verify_jdb_has_started__*) + # Android only polls for a Java debugger every 200ms, so + # the debugger must be connected for at least that long. + echo "Pausing 1 second for the program to continue." + sleep 1 + break + ;; + esac done + + # Note that JDB does not exit until GDB is fully attached! fi # See if gdbserver has to be uploaded @@ -234,18 +277,19 @@ fi echo "Attaching gdbserver to $pid on $device..." exec 5<> /tmp/file-descriptor-stamp +rm -f /tmp/file-descriptor-stamp if [ -z "$gdbserver" ]; then adb -s $device shell run-as $package $gdbserver_bin --once \ - "+debug.$package_uid.socket" --attach $pid >&5 & - gdb_socket="localfilesystem:$app_data_dir/debug.$package_uid.socket" + "+debug.$package.socket" --attach $pid >&5 & + gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket" else # Normally the program cannot access $gdbserver_bin when it is # placed in /data/local/tmp. adb -s $device shell $gdbserver_bin --once \ - "+/data/local/tmp/debug.$package_uid.socket" \ + "+/data/local/tmp/debug.$package.socket" \ --attach $pid >&5 & - gdb_socket="localfilesystem:/data/local/tmp/debug.$package_uid.socket" + gdb_socket="localfilesystem:/data/local/tmp/debug.$package.socket" fi # Wait until gdbserver successfully runs. @@ -256,7 +300,7 @@ while read -u 5 line; do break; ;; *error* | *Error* | failed ) - echo $line + echo "GDB error:" $line exit 1 ;; * ) @@ -264,19 +308,18 @@ while read -u 5 line; do esac done -if [ "$attach_existing" != "yes" ]; then - # Send EOF to JDB to make it go away. This will also cause - # Android to allow Emacs to continue executing. - echo "Making JDB go away..." - echo "exit" >&4 - read -u 4 line - echo "JDB has gone away with $line" +# Now that GDB is attached, tell the Java debugger to resume execution +# and then exit. + +if [ -n "$jdb_command" ]; then + echo "resume" >&${JDB[1]} + echo "exit" >&${JDB[1]} fi # Forward the gdb server port here. adb -s $device forward "tcp:$gdb_port" $gdb_socket if [ ! $? ]; then - echo "Failed to forward $app_data_dir/debug.$package_uid.socket" + echo "Failed to forward $app_data_dir/debug.$package.socket" echo "to $gdb_port! Perhaps you need to specify a different port" echo "with --port?" exit 1; @@ -284,4 +327,4 @@ fi # Finally, start gdb with any extra arguments needed. cd "$oldpwd" -gdb --eval-command "" --eval-command "target remote localhost:$gdb_port" $gdbargs +gdb --eval-command "target remote localhost:$gdb_port" $gdbargs diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 00e204c9949..ac67ebe4aa0 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -168,10 +168,22 @@ public class EmacsContextMenu { if (item.subMenu != null) { - /* This is a submenu. Create the submenu and add the - contents of the menu to it. */ - submenu = menu.addSubMenu (item.itemName); - item.subMenu.inflateMenuItems (submenu); + try + { + /* This is a submenu. On versions of Android which + support doing so, create the submenu and add the + contents of the menu to it. */ + submenu = menu.addSubMenu (item.itemName); + } + catch (UnsupportedOperationException exception) + { + /* This version of Android has a restriction + preventing submenus from being added to submenus. + Inflate everything into the parent menu + instead. */ + item.subMenu.inflateMenuItems (menu); + continue; + } /* This is still needed to set wasSubmenuSelected. */ menuItem = submenu.getItem (); diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 5d72a7860c8..7a97d706794 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -66,19 +66,11 @@ public class EmacsCopyArea paint = gc.gcPaint; - canvas = destination.lockCanvas (); + canvas = destination.lockCanvas (gc); if (canvas == null) return; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - /* A copy must be created or drawBitmap could end up overwriting itself. */ srcBitmap = source.getBitmap (); @@ -189,7 +181,6 @@ public class EmacsCopyArea maskBitmap.recycle (); } - canvas.restore (); destination.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index 5bc8efa5978..7d88a23c58f 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -168,9 +168,6 @@ public class EmacsDialog implements DialogInterface.OnDismissListener button = buttons.get (1); dialog.setButton (DialogInterface.BUTTON_NEUTRAL, button.name, button); - buttonView - = dialog.getButton (DialogInterface.BUTTON_NEUTRAL); - buttonView.setEnabled (button.enabled); } if (size >= 3) diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index 8941d4c217f..827feb96dfb 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -49,19 +49,11 @@ public class EmacsDrawLine Math.min (y, y2 + 1), Math.max (x2 + 1, x), Math.max (y2 + 1, y)); - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - paint.setStyle (Paint.Style.STROKE); if (gc.clip_mask == null) @@ -71,7 +63,6 @@ public class EmacsDrawLine /* DrawLine with clip mask not implemented; it is not used by Emacs. */ - canvas.restore (); drawable.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index c29d413f66e..695a8c6ea44 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -23,6 +23,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.RectF; import android.util.Log; @@ -36,51 +37,31 @@ public class EmacsDrawRectangle Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; + Rect rect; Rect maskRect, dstRect; Canvas canvas; Bitmap clipBitmap; - Rect clipRect; /* TODO implement stippling. */ if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - - /* Clip to the clipRect because some versions of Android draw an - overly wide line. */ - clipRect = new Rect (x, y, x + width + 1, - y + height + 1); - canvas.clipRect (clipRect); - paint = gc.gcPaint; + paint.setStyle (Paint.Style.STROKE); + rect = new Rect (x, y, x + width, y + height); if (gc.clip_mask == null) - { - /* canvas.drawRect just doesn't work on Android, producing - different results on various devices. Do a 5 point - PolyLine instead. */ - canvas.drawLine ((float) x, (float) y, (float) x + width, - (float) y, paint); - canvas.drawLine ((float) x + width, (float) y, - (float) x + width, (float) y + height, - paint); - canvas.drawLine ((float) x + width, (float) y + height, - (float) x, (float) y + height, paint); - canvas.drawLine ((float) x, (float) y + height, - (float) x, (float) y, paint); - } + /* Use canvas.drawRect with a RectF. That seems to reliably + get PostScript behavior. */ + canvas.drawRect (new RectF (x + 0.5f, y + 0.5f, + x + width + 0.5f, + y + height + 0.5f), + paint); else { /* Drawing with a clip mask involves calculating the @@ -137,7 +118,7 @@ public class EmacsDrawRectangle maskBitmap.recycle (); } - canvas.restore (); - drawable.damageRect (clipRect); + drawable.damageRect (new Rect (x, y, x + width + 1, + y + height + 1)); } } diff --git a/java/org/gnu/emacs/EmacsDrawable.java b/java/org/gnu/emacs/EmacsDrawable.java index 6a6199ff214..f2f8885e976 100644 --- a/java/org/gnu/emacs/EmacsDrawable.java +++ b/java/org/gnu/emacs/EmacsDrawable.java @@ -25,7 +25,7 @@ import android.graphics.Canvas; public interface EmacsDrawable { - public Canvas lockCanvas (); + public Canvas lockCanvas (EmacsGC gc); public void damageRect (Rect damageRect); public Bitmap getBitmap (); public boolean isDestroyed (); diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java b/java/org/gnu/emacs/EmacsFillPolygon.java index 42b73886dff..22e2dd0d8a9 100644 --- a/java/org/gnu/emacs/EmacsFillPolygon.java +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -41,21 +41,13 @@ public class EmacsFillPolygon RectF rectF; int i; - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return; paint = gc.gcPaint; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - /* Build the path from the given array of points. */ path = new Path (); @@ -83,7 +75,6 @@ public class EmacsFillPolygon if (gc.clip_mask == null) canvas.drawPath (path, paint); - canvas.restore (); drawable.damageRect (rect); /* FillPolygon with clip mask not implemented; it is not used by diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index 7cc55d3db96..aed0a540c8f 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -32,7 +32,6 @@ public class EmacsFillRectangle perform (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { - int i; Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; @@ -45,19 +44,11 @@ public class EmacsFillRectangle if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) return; - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return; - canvas.save (); - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - paint = gc.gcPaint; rect = new Rect (x, y, x + width, y + height); @@ -120,7 +111,6 @@ public class EmacsFillRectangle maskBitmap.recycle (); } - canvas.restore (); drawable.damageRect (rect); } } diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index c579625f3f7..bdc27a1ca5b 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -47,6 +47,14 @@ public class EmacsGC extends EmacsHandleObject public EmacsPixmap clip_mask, stipple; public Paint gcPaint; + /* ID incremented every time the clipping rectangles of any GC + changes. */ + private static long clip_serial; + + /* The value of clipRectID after the last time this GCs clip + rectangles changed. 0 if there are no clip rectangles. */ + public long clipRectID; + static { xorAlu = new PorterDuffXfermode (Mode.XOR); @@ -75,23 +83,28 @@ public class EmacsGC extends EmacsHandleObject recompute real_clip_rects. */ public void - markDirty () + markDirty (boolean clipRectsChanged) { int i; - if ((ts_origin_x != 0 || ts_origin_y != 0) - && clip_rects != null) + if (clipRectsChanged) { - real_clip_rects = new Rect[clip_rects.length]; - - for (i = 0; i < clip_rects.length; ++i) + if ((ts_origin_x != 0 || ts_origin_y != 0) + && clip_rects != null) { - real_clip_rects[i] = new Rect (clip_rects[i]); - real_clip_rects[i].offset (ts_origin_x, ts_origin_y); + real_clip_rects = new Rect[clip_rects.length]; + + for (i = 0; i < clip_rects.length; ++i) + { + real_clip_rects[i] = new Rect (clip_rects[i]); + real_clip_rects[i].offset (ts_origin_x, ts_origin_y); + } } + else + real_clip_rects = clip_rects; + + clipRectID = ++clip_serial; } - else - real_clip_rects = clip_rects; gcPaint.setStrokeWidth (1f); gcPaint.setColor (foreground | 0xff000000); diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index 85931c2abd4..a83d8f25542 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -42,6 +42,14 @@ public class EmacsPixmap extends EmacsHandleObject /* The canvas used to draw to BITMAP. */ public Canvas canvas; + /* Whether or not GC should be explicitly triggered upon + release. */ + private boolean needCollect; + + /* ID used to determine whether or not the GC clip rects + changed. */ + private long gcClipRectID; + public EmacsPixmap (short handle, int colors[], int width, int height, int depth) @@ -83,18 +91,41 @@ public class EmacsPixmap extends EmacsHandleObject switch (depth) { case 1: - bitmap = Bitmap.createBitmap (width, height, - Bitmap.Config.ALPHA_8, - false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ALPHA_8, + false); + else + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ALPHA_8); break; case 24: - bitmap = Bitmap.createBitmap (width, height, - Bitmap.Config.ARGB_8888, - false); + + /* Emacs doesn't just use the first kind of `createBitmap' + because the latter allows specifying that the pixmap is + always opaque, which really increases efficiency. */ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ARGB_8888); + else + bitmap = Bitmap.createBitmap (width, height, + Bitmap.Config.ARGB_8888, + false); break; } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) + /* On these old versions of Android, Bitmap.recycle frees bitmap + contents immediately. */ + needCollect = false; + else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + needCollect = (bitmap.getByteCount () + >= 1024 * 512); + else + needCollect = (bitmap.getAllocationByteCount () + >= 1024 * 512); + bitmap.eraseColor (0xff000000); this.width = width; @@ -104,11 +135,32 @@ public class EmacsPixmap extends EmacsHandleObject @Override public Canvas - lockCanvas () + lockCanvas (EmacsGC gc) { + int i; + if (canvas == null) - canvas = new Canvas (bitmap); + { + canvas = new Canvas (bitmap); + canvas.save (); + } + /* Now see if clipping has to be redone. */ + if (gc.clipRectID == gcClipRectID) + return canvas; + + /* It does have to be redone. Reapply gc.real_clip_rects. */ + canvas.restore (); + canvas.save (); + + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } + + /* Save the clip rect ID again. */ + gcClipRectID = gc.clipRectID; return canvas; } @@ -130,15 +182,6 @@ public class EmacsPixmap extends EmacsHandleObject public void destroyHandle () { - boolean needCollect; - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - needCollect = (bitmap.getByteCount () - >= 1024 * 512); - else - needCollect = (bitmap.getAllocationByteCount () - >= 1024 * 512); - bitmap.recycle (); bitmap = null; diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index c0f24c7433a..a964cadb74c 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -510,20 +510,12 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver backgroundRect.right = x + backgroundWidth; backgroundRect.bottom = y + sdk7FontObject.descent; - canvas = drawable.lockCanvas (); + canvas = drawable.lockCanvas (gc); if (canvas == null) return 0; - canvas.save (); paint = gc.gcPaint; - - if (gc.real_clip_rects != null) - { - for (i = 0; i < gc.real_clip_rects.length; ++i) - canvas.clipRect (gc.real_clip_rects[i]); - } - paint.setStyle (Paint.Style.FILL); if (withBackground) @@ -538,7 +530,6 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver paint.setAntiAlias (true); canvas.drawText (charsArray, 0, chars.length, x, y, paint); - canvas.restore (); bounds = new Rect (); paint.getTextBounds (charsArray, 0, chars.length, bounds); bounds.offset (x, y); diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index ca38f93dc98..bcf8d9ff6e8 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -175,7 +175,12 @@ public class EmacsService extends Service { view.thing = new EmacsView (window); view.thing.setVisibility (visibility); - view.thing.setFocusedByDefault (isFocusedByDefault); + + /* The following function is only present on Android 26 + or later. */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + view.thing.setFocusedByDefault (isFocusedByDefault); + notify (); } } diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 6137fd74a7f..82f44acaebe 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -83,6 +83,9 @@ public class EmacsView extends ViewGroup /* The last measured width and height. */ private int measuredWidth, measuredHeight; + /* The serial of the last clip rectangle change. */ + private long lastClipSerial; + public EmacsView (EmacsWindow window) { @@ -105,10 +108,6 @@ public class EmacsView extends ViewGroup on Android? */ setChildrenDrawingOrderEnabled (true); - /* Get rid of the foreground and background tint. */ - setBackgroundTintList (null); - setForegroundTintList (null); - /* Get rid of the default focus highlight. */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) setDefaultFocusHighlightEnabled (false); @@ -145,6 +144,11 @@ public class EmacsView extends ViewGroup /* And canvases. */ canvas = new Canvas (bitmap); + canvas.save (); + + /* Since the clip rectangles have been cleared, clear the clip + rectangle ID. */ + lastClipSerial = 0; /* Copy over the contents of the old bitmap. */ if (oldBitmap != null) @@ -177,11 +181,31 @@ public class EmacsView extends ViewGroup } public synchronized Canvas - getCanvas () + getCanvas (EmacsGC gc) { + int i; + if (bitmapDirty || bitmap == null) handleDirtyBitmap (); + if (canvas == null) + return null; + + /* Update clip rectangles if necessary. */ + if (gc.clipRectID != lastClipSerial) + { + canvas.restore (); + canvas.save (); + + if (gc.real_clip_rects != null) + { + for (i = 0; i < gc.real_clip_rects.length; ++i) + canvas.clipRect (gc.real_clip_rects[i]); + } + + lastClipSerial = gc.clipRectID; + } + return canvas; } diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index f5b50f11f14..c5b1522086c 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -164,7 +164,7 @@ public class EmacsWindow extends EmacsHandleObject { /* scratchGC is used as the argument to a FillRectangles req. */ scratchGC.foreground = pixel; - scratchGC.markDirty (); + scratchGC.markDirty (false); } public Rect @@ -466,9 +466,9 @@ public class EmacsWindow extends EmacsHandleObject @Override public Canvas - lockCanvas () + lockCanvas (EmacsGC gc) { - return view.getCanvas (); + return view.getCanvas (gc); } @Override @@ -512,37 +512,75 @@ public class EmacsWindow extends EmacsHandleObject public void onKeyDown (int keyCode, KeyEvent event) { - int state; + int state, state_1; - state = event.getModifiers (); - state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) + state = event.getModifiers (); + else + { + /* Replace this with getMetaState and manual + normalization. */ + state = event.getMetaState (); + + /* Normalize the state by setting the generic modifier bit if + either a left or right modifier is pressed. */ + + if ((state & KeyEvent.META_ALT_LEFT_ON) != 0 + || (state & KeyEvent.META_ALT_RIGHT_ON) != 0) + state |= KeyEvent.META_ALT_MASK; + + if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0 + || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0) + state |= KeyEvent.META_CTRL_MASK; + } + + /* Ignore meta-state understood by Emacs for now, or Ctrl+C will + not be recognized as an ASCII key press event. */ + state_1 + = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); EmacsNative.sendKeyPress (this.handle, event.getEventTime (), - event.getModifiers (), - 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)); - lastModifiers = event.getModifiers (); + state, keyCode, + event.getUnicodeChar (state_1)); + lastModifiers = state; } public void onKeyUp (int keyCode, KeyEvent event) { - int state; + int state, state_1; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) + state = event.getModifiers (); + else + { + /* Replace this with getMetaState and manual + normalization. */ + state = event.getMetaState (); + + /* Normalize the state by setting the generic modifier bit if + either a left or right modifier is pressed. */ + + if ((state & KeyEvent.META_ALT_LEFT_ON) != 0 + || (state & KeyEvent.META_ALT_RIGHT_ON) != 0) + state |= KeyEvent.META_ALT_MASK; + + if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0 + || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0) + state |= KeyEvent.META_CTRL_MASK; + } - state = event.getModifiers (); - state &= ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); + /* Ignore meta-state understood by Emacs for now, or Ctrl+C will + not be recognized as an ASCII key press event. */ + state_1 + = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); EmacsNative.sendKeyRelease (this.handle, event.getEventTime (), - event.getModifiers (), - keyCode, - event.getUnicodeChar (state)); - lastModifiers = event.getModifiers (); + state, keyCode, + event.getUnicodeChar (state_1)); + lastModifiers = state; } public void diff --git a/lib-src/Makefile.in b/lib-src/Makefile.in index 7d491557529..ddf141e2770 100644 --- a/lib-src/Makefile.in +++ b/lib-src/Makefile.in @@ -148,6 +148,9 @@ HAVE_BE_APP=@HAVE_BE_APP@ HAIKU_LIBS=@HAIKU_LIBS@ HAIKU_CFLAGS=@HAIKU_CFLAGS@ +## Android build-time support +ANDROID=@ANDROID@ + # emacsclientw.exe for MinGW, empty otherwise CLIENTW = @CLIENTW@ @@ -164,8 +167,13 @@ UTILITIES = hexl${EXEEXT} \ ifeq ($(HAVE_BE_APP),yes) DONT_INSTALL= make-docfile${EXEEXT} make-fingerprint${EXEEXT} be-resources else +ifeq ($(HAVE_ANDROID),yes) +DONT_INSTALL = make-docfile${EXEEXT} make-fingerprint${EXEEXT} \ + asset-directory-tool +else DONT_INSTALL= make-docfile${EXEEXT} make-fingerprint${EXEEXT} endif +endif # Like UTILITIES, but they're not system-dependent, and should not be # deleted by the distclean target. @@ -414,6 +422,9 @@ etags${EXEEXT}: ${etags_deps} ctags${EXEEXT}: ${srcdir}/ctags.c ${etags_deps} $(AM_V_CCLD)$(CC) ${ALL_CFLAGS} -o $@ $< $(etags_libs) +asset-directory-tool${EXEEXT}: ${srcdir}/asset-directory-tool.c $(config_h) + $(AM_V_CCLD)$(CC) ${ALL_CFLAGS} $< $(LOADLIBES) -o $@ + ebrowse${EXEEXT}: ${srcdir}/ebrowse.c ${srcdir}/../lib/min-max.h $(NTLIB) \ $(config_h) $(AM_V_CCLD)$(CC) ${ALL_CFLAGS} -o $@ $< $(NTLIB) $(LOADLIBES) diff --git a/lib-src/asset-directory-tool.c b/lib-src/asset-directory-tool.c new file mode 100644 index 00000000000..e53398eceb0 --- /dev/null +++ b/lib-src/asset-directory-tool.c @@ -0,0 +1,280 @@ +/* Android asset directory tool. + +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 . */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* This program takes a directory as input, and generates a + ``directory-tree'' file suitable for inclusion in an Android + application package. + + Such a file records the layout of the `assets' directory in the + package. Emacs records this information itself and uses it in the + Android emulation of readdir, because the system asset manager APIs + are routinely buggy, and are often unable to locate directories or + files. + + The file is packed, with no data alignment guarantees made. The + file starts with the bytes "EMACS", following which is the name of + the first file or directory, a NULL byte and an unsigned int + indicating the offset from the start of the file to the start of + the next sibling. Following that is a list of subdirectories or + files in the same format. The long is stored LSB first. */ + + + +struct directory_tree +{ + /* The offset to the next sibling. */ + size_t offset; + + /* The name of this directory or file. */ + char *name; + + /* Subdirectories and files inside this directory. */ + struct directory_tree *children, *next; +}; + +/* Exit with EXIT_FAILURE, after printing a description of a failing + function WHAT along with the details of the error. */ + +static _Noreturn void +croak (const char *what) +{ + perror (what); + exit (EXIT_FAILURE); +} + +/* Like malloc, but aborts on failure. */ + +static void * +xmalloc (size_t size) +{ + void *ptr; + + ptr = malloc (size); + + if (!ptr) + croak ("malloc"); + + return ptr; +} + +/* Recursively build a struct directory_tree structure for each + subdirectory or file in DIR, in preparation for writing it out to + disk. PARENT should be the directory tree associated with the + parent directory, or else PARENT->offset must be initialized to + 5. */ + +static void +main_1 (DIR *dir, struct directory_tree *parent) +{ + struct dirent *dirent; + int dir_fd, fd; + struct stat statb; + struct directory_tree *this, **last; + size_t length; + DIR *otherdir; + + dir_fd = dirfd (dir); + last = &parent->children; + + while ((dirent = readdir (dir))) + { + /* Determine what kind of file DIRENT is. */ + + if (fstatat (dir_fd, dirent->d_name, &statb, + AT_SYMLINK_NOFOLLOW) == -1) + croak ("fstatat"); + + /* Ignore . and ... */ + + if (!strcmp (dirent->d_name, ".") + || !strcmp (dirent->d_name, "..")) + continue; + + length = strlen (dirent->d_name); + + if (statb.st_mode & S_IFDIR) + { + /* This is a directory. Write its name followed by a + trailing slash, then a NULL byte, and the offset to the + next sibling. */ + this = xmalloc (sizeof *this); + this->children = NULL; + this->next = NULL; + *last = this; + last = &this->next; + this->name = xmalloc (length + 2); + strcpy (this->name, dirent->d_name); + + /* Now record the offset to the end of this directory. This + is length + 1, for the file name, and 5 more bytes for + the trailing NULL and long. */ + this->offset = parent->offset + length + 6; + + /* Terminate that with a slash and trailing NULL byte. */ + this->name[length] = '/'; + this->name[length + 1] = '\0'; + + /* Open and build that directory recursively. */ + + fd = openat (dir_fd, dirent->d_name, O_DIRECTORY, + O_RDONLY); + if (fd < 0) + croak ("openat"); + otherdir = fdopendir (fd); + if (!otherdir) + croak ("fdopendir"); + + main_1 (otherdir, this); + + /* Close this directory. */ + closedir (otherdir); + + /* Finally, set parent->offset to this->offset as well. */ + parent->offset = this->offset; + } + else if (statb.st_mode & S_IFREG) + { + /* This is a regular file. */ + this = xmalloc (sizeof *this); + this->children = NULL; + this->next = NULL; + *last = this; + last = &this->next; + this->name = xmalloc (length + 1); + strcpy (this->name, dirent->d_name); + + /* This is one byte shorter because there is no trailing + slash. */ + this->offset = parent->offset + length + 5; + parent->offset = this->offset; + } + } +} + +/* Write the struct directory_tree TREE and all of is children to the + file descriptor FD. OFFSET is the offset of TREE and may be + modified; it is only used for checking purposes. */ + +static void +main_2 (int fd, struct directory_tree *tree, size_t *offset) +{ + ssize_t size; + struct directory_tree *child; + unsigned int output; + + /* Write tree->name with the trailing NULL byte. */ + size = strlen (tree->name) + 1; + if (write (fd, tree->name, size) < size) + croak ("write"); + + /* Write the offset. */ + output = htole32 (tree->offset); + if (write (fd, &output, 4) < 1) + croak ("write"); + size += 4; + + /* Now update offset. */ + *offset += size; + + /* Write out each child. */ + for (child = tree->children; child; child = child->next) + main_2 (fd, child, offset); + + /* Verify the offset is correct. */ + if (tree->offset != *offset) + { + fprintf (stderr, + "asset-directory-tool: invalid offset: expected %tu, " + "got %tu.\n" + "Please report this bug to bug-gnu-emacs@gnu.org, along\n" + "with an archive containing the contents of the java/inst" + "all_temp directory.\n", + tree->offset, *offset); + abort (); + } +} + +int +main (int argc, char **argv) +{ + int fd; + DIR *indir; + struct directory_tree tree; + size_t offset; + + if (argc != 3) + { + fprintf (stderr, "usage: %s directory output-file\n", + argv[0]); + return EXIT_FAILURE; + } + + fd = open (argv[2], O_CREAT | O_TRUNC | O_RDWR, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + + if (fd < 0) + { + perror ("open"); + return EXIT_FAILURE; + } + + indir = opendir (argv[1]); + + if (!indir) + { + perror ("opendir"); + return EXIT_FAILURE; + } + + /* Write the first 5 byte header to FD. */ + + if (write (fd, "EMACS", 5) < 5) + { + perror ("write"); + return EXIT_FAILURE; + } + + /* Now iterate through children of INDIR, building the directory + tree. */ + tree.offset = 5; + tree.children = NULL; + + main_1 (indir, &tree); + closedir (indir); + + /* Finally, write the directory tree to the output file. */ + offset = 5; + for (; tree.children; tree.children = tree.children->next) + main_2 (fd, tree.children, &offset); + + return 0; +} diff --git a/lib/faccessat.c b/lib/faccessat.c index 807e2669683..ac8977cfd65 100644 --- a/lib/faccessat.c +++ b/lib/faccessat.c @@ -43,11 +43,7 @@ orig_faccessat (int fd, char const *name, int mode, int flag) /* Write "unistd.h" here, not , otherwise OSF/1 5.1 DTK cc eliminates this include because of the preliminary #include above. */ -#ifdef __ANROID__ -#include -#else #include "unistd.h" -#endif #ifndef HAVE_ACCESS /* Mingw lacks access, but it also lacks real vs. effective ids, so diff --git a/lib/fpending.c b/lib/fpending.c index afa840b8512..e57155e586e 100644 --- a/lib/fpending.c +++ b/lib/fpending.c @@ -41,7 +41,7 @@ __fpending (FILE *fp) return fp->_IO_write_ptr - fp->_IO_write_base; #elif defined __sferror || defined __DragonFly__ || defined __ANDROID__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, Mac OS X, Cygwin < 1.7.34, Minix 3, Android */ - return fp->_p - fp->_bf._base; + return fp_->_p - fp_->_bf._base; #elif defined __EMX__ /* emx+gcc */ return fp->_ptr - fp->_buffer; #elif defined __minix /* Minix */ diff --git a/lib/getdelim.c b/lib/getdelim.c new file mode 100644 index 00000000000..79ec3dd12a3 --- /dev/null +++ b/lib/getdelim.c @@ -0,0 +1,147 @@ +/* getdelim.c --- Implementation of replacement getdelim function. + Copyright (C) 1994, 1996-1998, 2001, 2003, 2005-2023 Free Software + Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Ported from glibc by Simon Josefsson. */ + +/* Don't use __attribute__ __nonnull__ in this compilation unit. Otherwise gcc + optimizes away the lineptr == NULL || n == NULL || fp == NULL tests below. */ +#define _GL_ARG_NONNULL(params) + +#include + +#include + +#include +#include +#include +#include + +#ifndef SSIZE_MAX +# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) +#endif + +#if USE_UNLOCKED_IO +# include "unlocked-io.h" +# define getc_maybe_unlocked(fp) getc(fp) +#elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED +# undef flockfile +# undef funlockfile +# define flockfile(x) ((void) 0) +# define funlockfile(x) ((void) 0) +# define getc_maybe_unlocked(fp) getc(fp) +#else +# define getc_maybe_unlocked(fp) getc_unlocked(fp) +#endif + +static void +alloc_failed (void) +{ +#if defined _WIN32 && ! defined __CYGWIN__ + /* Avoid errno problem without using the realloc module; see: + https://lists.gnu.org/r/bug-gnulib/2016-08/msg00025.html */ + errno = ENOMEM; +#endif +} + +/* Read up to (and including) a DELIMITER from FP into *LINEPTR (and + NUL-terminate it). *LINEPTR is a pointer returned from malloc (or + NULL), pointing to *N characters of space. It is realloc'ed as + necessary. Returns the number of characters read (not including + the null terminator), or -1 on error or EOF. */ + +ssize_t +getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp) +{ + ssize_t result; + size_t cur_len = 0; + + if (lineptr == NULL || n == NULL || fp == NULL) + { + errno = EINVAL; + return -1; + } + + flockfile (fp); + + if (*lineptr == NULL || *n == 0) + { + char *new_lineptr; + *n = 120; + new_lineptr = (char *) realloc (*lineptr, *n); + if (new_lineptr == NULL) + { + alloc_failed (); + result = -1; + goto unlock_return; + } + *lineptr = new_lineptr; + } + + for (;;) + { + int i; + + i = getc_maybe_unlocked (fp); + if (i == EOF) + { + result = -1; + break; + } + + /* Make enough space for len+1 (for final NUL) bytes. */ + if (cur_len + 1 >= *n) + { + size_t needed_max = + SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX; + size_t needed = 2 * *n + 1; /* Be generous. */ + char *new_lineptr; + + if (needed_max < needed) + needed = needed_max; + if (cur_len + 1 >= needed) + { + result = -1; + errno = EOVERFLOW; + goto unlock_return; + } + + new_lineptr = (char *) realloc (*lineptr, needed); + if (new_lineptr == NULL) + { + alloc_failed (); + result = -1; + goto unlock_return; + } + + *lineptr = new_lineptr; + *n = needed; + } + + (*lineptr)[cur_len] = i; + cur_len++; + + if (i == delimiter) + break; + } + (*lineptr)[cur_len] = '\0'; + result = cur_len ? cur_len : result; + + unlock_return: + funlockfile (fp); /* doesn't set errno */ + + return result; +} diff --git a/lib/getline.c b/lib/getline.c new file mode 100644 index 00000000000..85f16ab8bac --- /dev/null +++ b/lib/getline.c @@ -0,0 +1,27 @@ +/* getline.c --- Implementation of replacement getline function. + Copyright (C) 2005-2007, 2009-2023 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by Simon Josefsson. */ + +#include + +#include + +ssize_t +getline (char **lineptr, size_t *n, FILE *stream) +{ + return getdelim (lineptr, n, '\n', stream); +} diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in index 2097850c812..66ba4a4adf9 100644 --- a/lib/gnulib.mk.in +++ b/lib/gnulib.mk.in @@ -109,6 +109,7 @@ # fsusage \ # fsync \ # futimens \ +# getline \ # getloadavg \ # getopt-gnu \ # getrandom \ @@ -172,11 +173,19 @@ MOSTLYCLEANFILES += core *.stackdump # Start of GNU Make output. +AAPT = @AAPT@ ALLOCA = @ALLOCA@ ALLOCA_H = @ALLOCA_H@ ALSA_CFLAGS = @ALSA_CFLAGS@ ALSA_LIBS = @ALSA_LIBS@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +ANDROID = @ANDROID@ +ANDROID_ABI = @ANDROID_ABI@ +ANDROID_CFLAGS = @ANDROID_CFLAGS@ +ANDROID_JAR = @ANDROID_JAR@ +ANDROID_LIBS = @ANDROID_LIBS@ +ANDROID_MIN_SDK = @ANDROID_MIN_SDK@ +ANDROID_OBJ = @ANDROID_OBJ@ APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@ AR = @AR@ ARFLAGS = @ARFLAGS@ @@ -216,6 +225,7 @@ CYGWIN_OBJ = @CYGWIN_OBJ@ C_SWITCH_MACHINE = @C_SWITCH_MACHINE@ C_SWITCH_SYSTEM = @C_SWITCH_SYSTEM@ C_SWITCH_X_SITE = @C_SWITCH_X_SITE@ +D8 = @D8@ DBUS_CFLAGS = @DBUS_CFLAGS@ DBUS_LIBS = @DBUS_LIBS@ DBUS_OBJ = @DBUS_OBJ@ @@ -278,8 +288,10 @@ GL_COND_OBJ_FSTATAT_CONDITION = @GL_COND_OBJ_FSTATAT_CONDITION@ GL_COND_OBJ_FSUSAGE_CONDITION = @GL_COND_OBJ_FSUSAGE_CONDITION@ GL_COND_OBJ_FSYNC_CONDITION = @GL_COND_OBJ_FSYNC_CONDITION@ GL_COND_OBJ_FUTIMENS_CONDITION = @GL_COND_OBJ_FUTIMENS_CONDITION@ +GL_COND_OBJ_GETDELIM_CONDITION = @GL_COND_OBJ_GETDELIM_CONDITION@ GL_COND_OBJ_GETDTABLESIZE_CONDITION = @GL_COND_OBJ_GETDTABLESIZE_CONDITION@ GL_COND_OBJ_GETGROUPS_CONDITION = @GL_COND_OBJ_GETGROUPS_CONDITION@ +GL_COND_OBJ_GETLINE_CONDITION = @GL_COND_OBJ_GETLINE_CONDITION@ GL_COND_OBJ_GETLOADAVG_CONDITION = @GL_COND_OBJ_GETLOADAVG_CONDITION@ GL_COND_OBJ_GETOPT_CONDITION = @GL_COND_OBJ_GETOPT_CONDITION@ GL_COND_OBJ_GETRANDOM_CONDITION = @GL_COND_OBJ_GETRANDOM_CONDITION@ @@ -883,6 +895,8 @@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INT32_MAX_LT_INTMAX_MAX = @INT32_MAX_LT_INTMAX_MAX@ INT64_MAX_EQ_LONG_MAX = @INT64_MAX_EQ_LONG_MAX@ +JARSIGNER = @JARSIGNER@ +JAVAC = @JAVAC@ JSON_CFLAGS = @JSON_CFLAGS@ JSON_LIBS = @JSON_LIBS@ JSON_OBJ = @JSON_OBJ@ @@ -1211,6 +1225,7 @@ REPLACE_WCTOMB = @REPLACE_WCTOMB@ REPLACE_WRITE = @REPLACE_WRITE@ RSVG_CFLAGS = @RSVG_CFLAGS@ RSVG_LIBS = @RSVG_LIBS@ +SDK_BULD_TOOLS = @SDK_BULD_TOOLS@ SEPCHAR = @SEPCHAR@ SETFATTR = @SETFATTR@ SETTINGS_CFLAGS = @SETTINGS_CFLAGS@ @@ -1268,6 +1283,7 @@ XARGS_LIMIT = @XARGS_LIMIT@ XCB_LIBS = @XCB_LIBS@ XCOMPOSITE_CFLAGS = @XCOMPOSITE_CFLAGS@ XCOMPOSITE_LIBS = @XCOMPOSITE_LIBS@ +XCONFIGURE = @XCONFIGURE@ XCRUN = @XCRUN@ XDBE_CFLAGS = @XDBE_CFLAGS@ XDBE_LIBS = @XDBE_LIBS@ @@ -1292,6 +1308,7 @@ XSYNC_CFLAGS = @XSYNC_CFLAGS@ XSYNC_LIBS = @XSYNC_LIBS@ XWIDGETS_OBJ = @XWIDGETS_OBJ@ X_TOOLKIT_TYPE = @X_TOOLKIT_TYPE@ +ZIPALIGN = @ZIPALIGN@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_OBJC = @ac_ct_OBJC@ @@ -1337,6 +1354,7 @@ gl_GNULIB_ENABLED_e80bf6f757095d2e5fc94dafb8f8fc8b_CONDITION = @gl_GNULIB_ENABLE gl_GNULIB_ENABLED_ef455225c00f5049c808c2eda3e76866_CONDITION = @gl_GNULIB_ENABLED_ef455225c00f5049c808c2eda3e76866_CONDITION@ gl_GNULIB_ENABLED_euidaccess_CONDITION = @gl_GNULIB_ENABLED_euidaccess_CONDITION@ gl_GNULIB_ENABLED_fd38c7e463b54744b77b98aeafb4fa7c_CONDITION = @gl_GNULIB_ENABLED_fd38c7e463b54744b77b98aeafb4fa7c_CONDITION@ +gl_GNULIB_ENABLED_getdelim_CONDITION = @gl_GNULIB_ENABLED_getdelim_CONDITION@ gl_GNULIB_ENABLED_getdtablesize_CONDITION = @gl_GNULIB_ENABLED_getdtablesize_CONDITION@ gl_GNULIB_ENABLED_getgroups_CONDITION = @gl_GNULIB_ENABLED_getgroups_CONDITION@ gl_GNULIB_ENABLED_lchmod_CONDITION = @gl_GNULIB_ENABLED_lchmod_CONDITION@ @@ -2093,6 +2111,18 @@ gl_V_at = $(AM_V_GEN) endif ## end gnulib module gen-header +## begin gnulib module getdelim +ifeq (,$(OMIT_GNULIB_MODULE_getdelim)) + +ifneq (,$(gl_GNULIB_ENABLED_getdelim_CONDITION)) +ifneq (,$(GL_COND_OBJ_GETDELIM_CONDITION)) +libgnu_a_SOURCES += getdelim.c +endif + +endif +endif +## end gnulib module getdelim + ## begin gnulib module getdtablesize ifeq (,$(OMIT_GNULIB_MODULE_getdtablesize)) @@ -2117,6 +2147,16 @@ endif endif ## end gnulib module getgroups +## begin gnulib module getline +ifeq (,$(OMIT_GNULIB_MODULE_getline)) + +ifneq (,$(GL_COND_OBJ_GETLINE_CONDITION)) +libgnu_a_SOURCES += getline.c +endif + +endif +## end gnulib module getline + ## begin gnulib module getloadavg ifeq (,$(OMIT_GNULIB_MODULE_getloadavg)) diff --git a/lib/stdalign.in.h b/lib/stdalign.in.h index 17357810c7c..6523546f16d 100644 --- a/lib/stdalign.in.h +++ b/lib/stdalign.in.h @@ -17,117 +17,18 @@ /* Written by Paul Eggert and Bruno Haible. */ +/* Define two obsolescent C11 macros, assuming alignas and alignof are + either keywords or alignasof-defined macros. */ + #ifndef _GL_STDALIGN_H #define _GL_STDALIGN_H -/* ISO C11 for platforms that lack it. - - References: - ISO C11 (latest free draft - ) - sections 6.5.3.4, 6.7.5, 7.15. - C++11 (latest free draft - ) - section 18.10. */ - -/* alignof (TYPE), also known as _Alignof (TYPE), yields the alignment - requirement of a structure member (i.e., slot or field) that is of - type TYPE, as an integer constant expression. - - This differs from GCC's and clang's __alignof__ operator, which can - yield a better-performing alignment for an object of that type. For - example, on x86 with GCC and on Linux/x86 with clang, - __alignof__ (double) and __alignof__ (long long) are 8, whereas - alignof (double) and alignof (long long) are 4 unless the option - '-malign-double' is used. - - The result cannot be used as a value for an 'enum' constant, if you - want to be portable to HP-UX 10.20 cc and AIX 3.2.5 xlc. */ - -/* FreeBSD 9.1 , included by and lots of other - standard headers, defines conflicting implementations of _Alignas - and _Alignof that are no better than ours; override them. */ -#undef _Alignas -#undef _Alignof - -/* GCC releases before GCC 4.9 had a bug in _Alignof. See GCC bug 52023 - . - clang versions < 8.0.0 have the same bug. */ -#if (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 \ - || (defined __GNUC__ && __GNUC__ < 4 + (__GNUC_MINOR__ < 9) \ - && !defined __clang__) \ - || (defined __clang__ && __clang_major__ < 8)) -# ifdef __cplusplus -# if (201103 <= __cplusplus || defined _MSC_VER) -# define _Alignof(type) alignof (type) -# else - template struct __alignof_helper { char __a; __t __b; }; -# define _Alignof(type) offsetof (__alignof_helper, __b) -# define _GL_STDALIGN_NEEDS_STDDEF 1 -# endif -# else -# define _Alignof(type) offsetof (struct { char __a; type __b; }, __b) -# define _GL_STDALIGN_NEEDS_STDDEF 1 -# endif -#endif -#if ! (defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER)) -# define alignof _Alignof -#endif -#define __alignof_is_defined 1 - -/* alignas (A), also known as _Alignas (A), aligns a variable or type - to the alignment A, where A is an integer constant expression. For - example: - - int alignas (8) foo; - struct s { int a; int alignas (8) bar; }; - - aligns the address of FOO and the offset of BAR to be multiples of 8. - - A should be a power of two that is at least the type's alignment - and at most the implementation's alignment limit. This limit is - 2**28 on typical GNUish hosts, and 2**13 on MSVC. To be portable - to MSVC through at least version 10.0, A should be an integer - constant, as MSVC does not support expressions such as 1 << 3. - To be portable to Sun C 5.11, do not align auto variables to - anything stricter than their default alignment. - - The following C11 requirements are not supported here: - - - If A is zero, alignas has no effect. - - alignas can be used multiple times; the strictest one wins. - - alignas (TYPE) is equivalent to alignas (alignof (TYPE)). - - */ - -#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 -# if defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER) -# define _Alignas(a) alignas (a) -# elif (!defined __attribute__ \ - && ((defined __APPLE__ && defined __MACH__ \ - ? 4 < __GNUC__ + (1 <= __GNUC_MINOR__) \ - : __GNUC__ && !defined __ibmxl__) \ - || (4 <= __clang_major__) \ - || (__ia64 && (61200 <= __HP_cc || 61200 <= __HP_aCC)) \ - || __ICC || 0x590 <= __SUNPRO_C || 0x0600 <= __xlC__)) -# define _Alignas(a) __attribute__ ((__aligned__ (a))) -# elif 1300 <= _MSC_VER -# define _Alignas(a) __declspec (align (a)) -# endif -#endif -#if ((defined _Alignas \ - && !(defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER))) \ - || (defined __STDC_VERSION__ && 201112 <= __STDC_VERSION__)) -# define alignas _Alignas -#endif #if (defined alignas \ + || (defined __STDC_VERSION__ && 202311 <= __STDC_VERSION__) \ || (defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER))) # define __alignas_is_defined 1 #endif -/* Include if needed for offsetof. */ -#if _GL_STDALIGN_NEEDS_STDDEF -# include -#endif +#define __alignof_is_defined 1 #endif /* _GL_STDALIGN_H */ diff --git a/lib/stdio-impl.h b/lib/stdio-impl.h index 81e7f838372..46608bed198 100644 --- a/lib/stdio-impl.h +++ b/lib/stdio-impl.h @@ -70,6 +70,12 @@ # define _gl_flags_file_t int # else # define _gl_flags_file_t short +# endif +# ifdef __LP64__ +# define _gl_file_offset_t int64_t +# else + /* see https://android.googlesource.com/platform/bionic/+/master/docs/32-bit-abi.md */ +# define _gl_file_offset_t __kernel_off_t # endif /* Up to this commit from 2015-10-12 @@ -96,7 +102,7 @@ unsigned char _nbuf[1]; \ struct { unsigned char *_base; size_t _size; } _lb; \ int _blksize; \ - fpos_t _offset; \ + _gl_file_offset_t _offset; \ /* More fields, not relevant here. */ \ } *) fp) # else diff --git a/lisp/button.el b/lisp/button.el index f043073ea86..65abb81ec46 100644 --- a/lisp/button.el +++ b/lisp/button.el @@ -72,7 +72,12 @@ Mode-specific keymaps may want to use this as their parent keymap." ;; mode-line or header-line, the `mode-line' or `header-line' prefix ;; shouldn't be necessary! " " #'push-button - " " #'push-button) + " " #'push-button + ;; `push-button' will automatically dispatch to + ;; `touch-screen-track-tap'. + " " #'push-button + " " #'push-button + "" #'push-button) (define-minor-mode button-mode "A minor mode for navigating to buttons with the TAB key." @@ -454,18 +459,22 @@ instead of starting at the next button." (defun push-button (&optional pos use-mouse-action) "Perform the action specified by a button at location POS. -POS may be either a buffer position or a mouse-event. If -USE-MOUSE-ACTION is non-nil, invoke the button's `mouse-action' -property instead of its `action' property; if the button has no -`mouse-action', the value of `action' is used instead. +POS may be either a buffer position, a mouse-event, or a +`touchscreen-down' event. If USE-MOUSE-ACTION is non-nil, invoke +the button's `mouse-action' property instead of its `action' +property; if the button has no `mouse-action', the value of +`action' is used instead. + +If POS is a `touchscreen-down' event, wait for the corresponding +`touchscreen-up' event before calling `push-button'. The action in both cases may be either a function to call or a marker to display and is invoked using `button-activate' (which see). POS defaults to point, except when `push-button' is invoked -interactively as the result of a mouse-event, in which case, the -mouse event is used. +interactively as the result of a mouse-event or touchscreen +event, in which case, the position in the event event is used. If there's no button at POS, do nothing and return nil, otherwise return t. @@ -483,7 +492,12 @@ pushing a button, use the `button-describe' command." (if str-button ;; mode-line, header-line, or display string event. (button-activate str t) - (push-button (posn-point posn) t))))) + (if (eq (car pos) 'touchscreen-down) + ;; If touch-screen-track tap returns nil, then the + ;; tap was cancelled. + (when (touch-screen-track-tap pos) + (push-button (posn-point posn) t)) + (push-button (posn-point posn) t)))))) ;; POS is just normal position (let ((button (button-at (or pos (point))))) (when button diff --git a/lisp/frame.el b/lisp/frame.el index d35df71a6cc..f21c0c369bd 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2149,8 +2149,12 @@ frame's display)." "Return non-nil if popup menus are supported on DISPLAY. DISPLAY can be a display name, a frame, or nil (meaning the selected frame's display). -Support for popup menus requires that the mouse be available." - (display-mouse-p display)) +Support for popup menus requires that a suitable pointing device +be available." + ;; Android menus work fine with touch screens as well, and one must + ;; be present. + (or (eq (framep-on-display display) 'android) + (display-mouse-p display))) (defun display-graphic-p (&optional display) "Return non-nil if DISPLAY is a graphic display. diff --git a/lisp/subr.el b/lisp/subr.el index f909b63aabe..345816dbd23 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -1636,7 +1636,13 @@ nil or (STRING . POSITION)'. `posn-timestamp': The time the event occurred, in milliseconds. For more information, see Info node `(elisp)Click Events'." - (or (and (consp event) (nth 1 event)) + (or (and (consp event) + ;; Ignore touchscreen events. They store the posn in a + ;; different format, and can have multiple posns. + (not (memq (car event) '(touchscreen-begin + touchscreen-update + touchscreen-end))) + (nth 1 event)) (event--posn-at-point))) (defun event-end (event) @@ -1644,7 +1650,11 @@ For more information, see Info node `(elisp)Click Events'." EVENT should be a click, drag, or key press event. See `event-start' for a description of the value returned." - (or (and (consp event) (nth (if (consp (nth 2 event)) 2 1) event)) + (or (and (consp event) + (not (memq (car event) '(touchscreen-begin + touchscreen-update + touchscreen-end))) + (nth (if (consp (nth 2 event)) 2 1) event)) (event--posn-at-point))) (defsubst event-click-count (event) diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el index 192a09b3a29..bcc4f5e9be3 100644 --- a/lisp/touch-screen.el +++ b/lisp/touch-screen.el @@ -105,10 +105,10 @@ known position of the tool." (setcar (nthcdr 3 touch-screen-current-tool) 'held) ;; Go to the initial position of the touchpoint and activate the ;; mark. - (with-selected-window (cadr touch-screen-current-tool) - (set-mark (posn-point (nth 4 touch-screen-current-tool))) - (goto-char (mark)) - (activate-mark))))) + (select-window (cadr touch-screen-current-tool)) + (set-mark (posn-point (nth 4 touch-screen-current-tool))) + (goto-char (mark)) + (activate-mark)))) (defun touch-screen-handle-point-update (point) "Notice that the touch point POINT has changed position. @@ -132,9 +132,7 @@ happens, cancel `touch-screen-current-timer', and set the field to `drag'. Then, activate the mark and start dragging. If the fourth element of `touch-screen-current-tool' is `drag', -then move point to the position of POINT. - -Set `touch-screen-current-tool' to nil should any error occur." +then move point to the position of POINT." (let ((window (nth 1 touch-screen-current-tool)) (what (nth 3 touch-screen-current-tool))) (cond ((null what) @@ -252,19 +250,42 @@ POINT should be the point currently tracked as If the fourth argument of `touch-screen-current-tool' is nil, move point to the position of POINT, selecting the window under -POINT as well; if there is a button at POINT, then activate the -button there. Otherwise, deactivate the mark. Then, display the -on-screen keyboard." +POINT as well, and deactivate the mark; if there is a button or +link at POINT, call the command bound to `mouse-2' there. +Otherwise, call the command bound to `mouse-1'." (let ((what (nth 3 touch-screen-current-tool))) (cond ((null what) (when (windowp (posn-window (cdr point))) ;; Select the window that was tapped. (select-window (posn-window (cdr point))) - (let ((button (button-at (posn-point (cdr point))))) - (when button - (button-activate button t)) + ;; Now simulate a mouse click there. If there is a link + ;; or a button, use mouse-2 to push it. + (let ((event (list (if (or (mouse-on-link-p (cdr point)) + (button-at (posn-point (cdr point)))) + 'mouse-2 + 'mouse-1) + (cdr point))) + ;; Look for an extra keymap to look in. + (keymap (and (posn-object (cdr point)) + (stringp + (posn-object (cdr point))) + (get-text-property + 0 'keymap + (posn-object (cdr point))))) + command) + (save-excursion + (when (posn-point (cdr point)) + (goto-char (posn-point (cdr point)))) + (if keymap + (setq keymap (cons keymap (current-active-maps t))) + (setq keymap (current-active-maps t))) + (setq command (lookup-key keymap (vector (car event))))) + (deactivate-mark) + ;; This is necessary for following links. (goto-char (posn-point (cdr point))) - (deactivate-mark))))))) + (when command + (call-interactively command nil + (vector event))))))))) (defun touch-screen-handle-touch (event) "Handle a single touch EVENT, and perform associated actions. @@ -317,6 +338,146 @@ touchscreen-end event." (define-key global-map [touchscreen-update] #'touch-screen-handle-touch) (define-key global-map [touchscreen-end] #'touch-screen-handle-touch) + +;; Exports. These functions are intended for use externally. + +(defun touch-screen-track-tap (event &optional update data) + "Track a single tap starting from EVENT. +EVENT should be a `touchscreen-begin' event. + +Read touch screen events until a `touchscreen-end' event is +received with the same ID as in EVENT. If UPDATE is non-nil and +a `touchscreen-update' event is received in the mean time and +contains a touch point with the same ID as in EVENT, call UPDATE +with that event and DATA. + +Return nil immediately if any other kind of event is received; +otherwise, return t once the `touchscreen-end' event arrives." + (catch 'finish + (while t + (let ((new-event (read-event))) + (cond + ((eq (car-safe new-event) 'touchscreen-update) + (when (and update (assq (caadr event) (cadr new-event))) + (funcall update new-event data))) + ((eq (car-safe new-event) 'touchscreen-end) + (throw 'finish + ;; Now determine whether or not the `touchscreen-end' + ;; event has the same ID as EVENT. If it doesn't, + ;; then this is another touch, so return nil. + (eq (caadr event) (caadr new-event)))) + (t (throw 'finish nil))))))) + +(defun touch-screen-track-drag (event update &optional data) + "Track a single drag starting from EVENT. +EVENT should be a `touchscreen-end' event. + +Read touch screen events until a `touchscreen-end' event is +received with the same ID as in EVENT. For each +`touchscreen-update' event received in the mean time containing a +touch point with the same ID as in EVENT, call UPDATE with the +touch point in event and DATA. + +Return nil immediately if any other kind of event is received; +otherwise, return t once the `touchscreen-end' event arrives." + (catch 'finish + (while t + (let ((new-event (read-event))) + (cond + ((eq (car-safe new-event) 'touchscreen-update) + (let ((tool (assq (caadr event) (nth 1 new-event)))) + (when (and update tool) + (funcall update new-event data)))) + ((eq (car-safe new-event) 'touchscreen-end) + (throw 'finish + ;; Now determine whether or not the `touchscreen-end' + ;; event has the same ID as EVENT. If it doesn't, + ;; then this is another touch, so return nil. + (eq (caadr event) (caadr new-event)))) + (t (throw 'finish nil))))))) + + + +;; Modeline dragging. + +(defun touch-screen-drag-mode-line-1 (event) + "Internal helper for `touch-screen-drag-mode-line'. +This is called when that function determines it need not execute +any keymaps on the mode line at that particular spot." + ;; Find the window that should be dragged and the starting position. + (let* ((window (posn-window (cdadr event))) + (relative-xy (touch-screen-relative-xy + (cdadr event) window)) + (last-position (cdr relative-xy))) + (when (window-resizable window 0) + (touch-screen-track-drag + event (lambda (new-event &optional _data) + ;; Find the position of the touchpoint in NEW-EVENT. + (let* ((touchpoint (assq (caadr event) (cadr new-event))) + (new-relative-xy + (touch-screen-relative-xy (cdr touchpoint) + window)) + (position (cdr new-relative-xy)) + growth) + ;; Now set the new height of the window. + ;; If new-relative-y is above relative-xy, then + ;; make the window that much shorter. Otherwise, + ;; make it bigger. + (unless (or (zerop (setq growth (- position last-position))) + (and (> growth 0) + (< position (+ (window-pixel-top window) + (window-pixel-height window)))) + (and (< growth 0) + (> position (+ (window-pixel-top window) + (window-pixel-height window))))) + (adjust-window-trailing-edge window growth nil t)) + (setq last-position position))))))) + +(defun touch-screen-drag-mode-line (event) + "Begin dragging the mode line in response to a touch EVENT. +If EVENT lies on top of text with a mouse command bound, run +that command instead. + +Change the height of the window based on where the touch point +in EVENT moves." + (interactive "e") + ;; If there is an object at EVENT, then look either a keymap bound + ;; to [down-mouse-1] or a command bound to [mouse-1]. Then, if a + ;; keymap was found, pop it up as a menu. Otherwise, wait for a tap + ;; to complete and run the command found. + (let* ((object (posn-object (cdadr event))) + (object-keymap (and (consp object) + (stringp (car object)) + (or (get-text-property (cdr object) + 'keymap + (car object)) + (get-text-property (cdr object) + 'local-map + (car object))))) + (keymap (lookup-key object-keymap [mode-line down-mouse-1])) + (command (or (lookup-key object-keymap [mode-line mouse-1]) + keymap))) + (if (or (keymapp keymap) command) + (if (keymapp keymap) + (when (touch-screen-track-tap event) + (when-let* ((command (x-popup-menu event keymap)) + (tem (lookup-key keymap + (if (consp command) + (apply #'vector command) + (vector command)) + t))) + (call-interactively tem))) + (when (and (commandp command) + (touch-screen-track-tap event)) + (call-interactively command nil + (vector (list 'mouse-1 (cdadr event)))))) + (touch-screen-drag-mode-line-1 event)))) + +(global-set-key [mode-line touchscreen-begin] + #'touch-screen-drag-mode-line) +(global-set-key [bottom-divider touchscreen-begin] + #'touch-screen-drag-mode-line) + (provide 'touch-screen) ;;; touch-screen ends here diff --git a/lisp/wid-edit.el b/lisp/wid-edit.el index 60bd2baa6fb..4c52d827980 100644 --- a/lisp/wid-edit.el +++ b/lisp/wid-edit.el @@ -65,8 +65,11 @@ ;;; Compatibility. (defun widget-event-point (event) - "Character position of the end of event if that exists, or nil." - (posn-point (event-end event))) + "Character position of the end of event if that exists, or nil. +EVENT can either be a mouse event or a touch screen event." + (if (eq (car-safe event) 'touchscreen-begin) + (posn-point (cdadr event)) + (posn-point (event-end event)))) (defun widget-button-release-event-p (event) "Non-nil if EVENT is a mouse-button-release event object." @@ -1017,6 +1020,7 @@ button end points." (define-key map [backtab] 'widget-backward) (define-key map [down-mouse-2] 'widget-button-click) (define-key map [down-mouse-1] 'widget-button-click) + (define-key map [touchscreen-begin] 'widget-button-click) ;; The following definition needs to avoid using escape sequences that ;; might get converted to ^M when building loaddefs.el (define-key map [(control ?m)] 'widget-button-press) @@ -1072,8 +1076,18 @@ Note that such modes will need to require wid-edit.") "If non-nil, `widget-button-click' moves point to a button after invoking it. If nil, point returns to its original position after invoking a button.") +(defun widget-event-start (event) + "Return the start of EVENT. +If EVENT is not a touchscreen event, simply return its +`event-start'. Otherwise, it is a touchscreen event, so return +the posn of its touchpoint." + (if (eq (car event) 'touchscreen-begin) + (cdadr event) + (event-start event))) + (defun widget-button--check-and-call-button (event button) "Call BUTTON if BUTTON is a widget and EVENT is correct for it. +EVENT can either be a mouse event or a touchscreen-begin event. If nothing was called, return non-nil." (let* ((oevent event) (mouse-1 (memq (event-basic-type event) '(mouse-1 down-mouse-1))) @@ -1084,49 +1098,58 @@ If nothing was called, return non-nil." ;; in a save-excursion so that the click on the button ;; doesn't change point. (save-selected-window - (select-window (posn-window (event-start event))) + (select-window (posn-window (widget-event-start event))) (save-excursion - (goto-char (posn-point (event-start event))) + (goto-char (posn-point (widget-event-start event))) (let* ((overlay (widget-get button :button-overlay)) (pressed-face (or (widget-get button :pressed-face) widget-button-pressed-face)) (face (overlay-get overlay 'face)) (mouse-face (overlay-get overlay 'mouse-face))) (unwind-protect - ;; Read events, including mouse-movement - ;; events, waiting for a release event. If we - ;; began with a mouse-1 event and receive a - ;; movement event, that means the user wants - ;; to perform drag-selection, so cancel the - ;; button press and do the default mouse-1 - ;; action. For mouse-2, just highlight/ - ;; unhighlight the button the mouse was - ;; initially on when we move over it. + ;; Read events, including mouse-movement events, + ;; waiting for a release event. If we began with a + ;; mouse-1 event and receive a movement event, that + ;; means the user wants to perform drag-selection, so + ;; cancel the button press and do the default mouse-1 + ;; action. For mouse-2, just highlight/ unhighlight + ;; the button the mouse was initially on when we move + ;; over it. + ;; + ;; If this function was called in response to a + ;; touchscreen event, then wait for a corresponding + ;; touchscreen-end event instead. (save-excursion (when face ; avoid changing around image (overlay-put overlay 'face pressed-face) (overlay-put overlay 'mouse-face pressed-face)) - (unless (widget-apply button :mouse-down-action event) - (let ((track-mouse t)) - (while (not (widget-button-release-event-p event)) - (setq event (read--potential-mouse-event)) - (when (and mouse-1 (mouse-movement-p event)) - (push event unread-command-events) - (setq event oevent) - (throw 'button-press-cancelled t)) - (unless (or (integerp event) - (memq (car event) - '(switch-frame select-window)) - (eq (car event) 'scroll-bar-movement)) - (setq pos (widget-event-point event)) - (if (and pos - (eq (get-char-property pos 'button) - button)) - (when face - (overlay-put overlay 'face pressed-face) - (overlay-put overlay 'mouse-face pressed-face)) - (overlay-put overlay 'face face) - (overlay-put overlay 'mouse-face mouse-face)))))) + (if (eq (car event) 'touchscreen-begin) + ;; This a touchscreen event and must be handled + ;; specially through `touch-screen-track-tap'. + (progn + (unless (touch-screen-track-tap event) + (throw 'button-press-cancelled t))) + (unless (widget-apply button :mouse-down-action event) + (let ((track-mouse t)) + (while (not (widget-button-release-event-p event)) + (setq event (read--potential-mouse-event)) + (when (and mouse-1 (mouse-movement-p event)) + (push event unread-command-events) + (setq event oevent) + (throw 'button-press-cancelled t)) + (unless (or (integerp event) + (memq (car event) + '(switch-frame select-window)) + (eq (car event) 'scroll-bar-movement)) + (setq pos (widget-event-point event)) + (if (and pos + (eq (get-char-property pos 'button) + button)) + (when face + (overlay-put overlay 'face pressed-face) + (overlay-put overlay 'mouse-face pressed-face)) + (overlay-put overlay 'face face) + (overlay-put overlay 'mouse-face mouse-face))))))) ;; When mouse is released over the button, run ;; its action function. @@ -1148,32 +1171,35 @@ If nothing was called, return non-nil." (if (widget-event-point event) (let* ((mouse-1 (memq (event-basic-type event) '(mouse-1 down-mouse-1))) (pos (widget-event-point event)) - (start (event-start event)) + (start (widget-event-start event)) (button (get-char-property pos 'button (and (windowp (posn-window start)) (window-buffer (posn-window start)))))) (when (or (null button) (widget-button--check-and-call-button event button)) - (let ((up t) + (let ((up (not (eq (car event) 'touchscreen-begin))) command) ;; Mouse click not on a widget button. Find the global ;; command to run, and check whether it is bound to an ;; up event. - (if mouse-1 - (cond ((setq command ;down event - (lookup-key widget-global-map [down-mouse-1])) - (setq up nil)) - ((setq command ;up event - (lookup-key widget-global-map [mouse-1])))) - (cond ((setq command ;down event - (lookup-key widget-global-map [down-mouse-2])) - (setq up nil)) - ((setq command ;up event - (lookup-key widget-global-map [mouse-2]))))) + (cond + ((eq (car event) 'touchscreen-begin) + (setq command (lookup-key widget-global-map + [touchscreen-begin]))) + (mouse-1 (cond ((setq command ;down event + (lookup-key widget-global-map [down-mouse-1])) + (setq up nil)) + ((setq command ;up event + (lookup-key widget-global-map [mouse-1]))))) + (t (cond ((setq command ;down event + (lookup-key widget-global-map [down-mouse-2])) + (setq up nil)) + ((setq command ;up event + (lookup-key widget-global-map [mouse-2])))))) (when up ;; Don't execute up events twice. - (while (not (widget-button-release-event-p event)) + (while (not (and (widget-button-release-event-p event))) (setq event (read--potential-mouse-event)))) (when command (call-interactively command))))) diff --git a/m4/getdelim.m4 b/m4/getdelim.m4 new file mode 100644 index 00000000000..9aaed202abe --- /dev/null +++ b/m4/getdelim.m4 @@ -0,0 +1,111 @@ +# getdelim.m4 serial 16 + +dnl Copyright (C) 2005-2007, 2009-2023 Free Software Foundation, Inc. +dnl +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_PREREQ([2.59]) + +AC_DEFUN([gl_FUNC_GETDELIM], +[ + AC_REQUIRE([gl_STDIO_H_DEFAULTS]) + AC_REQUIRE([AC_CANONICAL_HOST]) + + dnl Persuade glibc to declare getdelim(). + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + + AC_CHECK_DECLS_ONCE([getdelim]) + + AC_CHECK_FUNCS_ONCE([getdelim]) + if test $ac_cv_func_getdelim = yes; then + HAVE_GETDELIM=1 + dnl Found it in some library. Verify that it works. + AC_CACHE_CHECK([for working getdelim function], + [gl_cv_func_working_getdelim], + [case "$host_os" in + darwin*) + dnl On macOS 10.13, valgrind detected an out-of-bounds read during + dnl the GNU sed test suite: + dnl Invalid read of size 16 + dnl at 0x100EE6A05: _platform_memchr$VARIANT$Base (in /usr/lib/system/libsystem_platform.dylib) + dnl by 0x100B7B0BD: getdelim (in /usr/lib/system/libsystem_c.dylib) + dnl by 0x10000B0BE: ck_getdelim (utils.c:254) + gl_cv_func_working_getdelim=no ;; + *) + echo fooNbarN | tr -d '\012' | tr N '\012' > conftest.data + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +# include +# include +# include + int main () + { + FILE *in = fopen ("./conftest.data", "r"); + if (!in) + return 1; + { + /* Test result for a NULL buffer and a zero size. + Based on a test program from Karl Heuer. */ + char *line = NULL; + size_t siz = 0; + int len = getdelim (&line, &siz, '\n', in); + if (!(len == 4 && line && strcmp (line, "foo\n") == 0)) + { free (line); fclose (in); return 2; } + free (line); + } + { + /* Test result for a NULL buffer and a non-zero size. + This crashes on FreeBSD 8.0. */ + char *line = NULL; + size_t siz = (size_t)(~0) / 4; + if (getdelim (&line, &siz, '\n', in) == -1) + { fclose (in); return 3; } + free (line); + } + fclose (in); + return 0; + } + ]])], + [gl_cv_func_working_getdelim=yes], + [gl_cv_func_working_getdelim=no], + [dnl We're cross compiling. + dnl Guess it works on glibc2 systems and musl systems. + AC_EGREP_CPP([Lucky GNU user], + [ +#include +#ifdef __GNU_LIBRARY__ + #if (__GLIBC__ >= 2) && !defined __UCLIBC__ + Lucky GNU user + #endif +#endif + ], + [gl_cv_func_working_getdelim="guessing yes"], + [case "$host_os" in + *-musl*) gl_cv_func_working_getdelim="guessing yes" ;; + *) gl_cv_func_working_getdelim="$gl_cross_guess_normal" ;; + esac + ]) + ]) + ;; + esac + ]) + case "$gl_cv_func_working_getdelim" in + *yes) ;; + *) REPLACE_GETDELIM=1 ;; + esac + else + HAVE_GETDELIM=0 + fi + + if test $ac_cv_have_decl_getdelim = no; then + HAVE_DECL_GETDELIM=0 + fi +]) + +# Prerequisites of lib/getdelim.c. +AC_DEFUN([gl_PREREQ_GETDELIM], +[ + AC_CHECK_FUNCS([flockfile funlockfile]) + AC_CHECK_DECLS([getc_unlocked]) +]) diff --git a/m4/getline.m4 b/m4/getline.m4 new file mode 100644 index 00000000000..03569f06b2c --- /dev/null +++ b/m4/getline.m4 @@ -0,0 +1,109 @@ +# getline.m4 serial 30 + +dnl Copyright (C) 1998-2003, 2005-2007, 2009-2023 Free Software Foundation, +dnl Inc. +dnl +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_PREREQ([2.59]) + +dnl See if there's a working, system-supplied version of the getline function. +dnl We can't just do AC_REPLACE_FUNCS([getline]) because some systems +dnl have a function by that name in -linet that doesn't have anything +dnl to do with the function we need. +AC_DEFUN([gl_FUNC_GETLINE], +[ + AC_REQUIRE([gl_STDIO_H_DEFAULTS]) + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + + dnl Persuade glibc to declare getline(). + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + + AC_CHECK_DECLS_ONCE([getline]) + + gl_getline_needs_run_time_check=no + AC_CHECK_FUNC([getline], + [dnl Found it in some library. Verify that it works. + gl_getline_needs_run_time_check=yes], + [am_cv_func_working_getline=no]) + if test $gl_getline_needs_run_time_check = yes; then + AC_CACHE_CHECK([for working getline function], + [am_cv_func_working_getline], + [echo fooNbarN | tr -d '\012' | tr N '\012' > conftest.data + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +# include +# include +# include + int main () + { + FILE *in = fopen ("./conftest.data", "r"); + if (!in) + return 1; + { + /* Test result for a NULL buffer and a zero size. + Based on a test program from Karl Heuer. */ + char *line = NULL; + size_t siz = 0; + int len = getline (&line, &siz, in); + if (!(len == 4 && line && strcmp (line, "foo\n") == 0)) + { free (line); fclose (in); return 2; } + free (line); + } + { + /* Test result for a NULL buffer and a non-zero size. + This crashes on FreeBSD 8.0. */ + char *line = NULL; + size_t siz = (size_t)(~0) / 4; + if (getline (&line, &siz, in) == -1) + { fclose (in); return 3; } + free (line); + } + fclose (in); + return 0; + } + ]])], + [am_cv_func_working_getline=yes], + [am_cv_func_working_getline=no], + [dnl We're cross compiling. + dnl Guess it works on glibc2 systems and musl systems. + AC_EGREP_CPP([Lucky GNU user], + [ +#include +#ifdef __GNU_LIBRARY__ + #if (__GLIBC__ >= 2) && !defined __UCLIBC__ + Lucky GNU user + #endif +#endif + ], + [am_cv_func_working_getline="guessing yes"], + [case "$host_os" in + *-musl*) am_cv_func_working_getline="guessing yes" ;; + *) am_cv_func_working_getline="$gl_cross_guess_normal" ;; + esac + ]) + ]) + ]) + fi + + if test $ac_cv_have_decl_getline = no; then + HAVE_DECL_GETLINE=0 + fi + + case "$am_cv_func_working_getline" in + *yes) ;; + *) + dnl Set REPLACE_GETLINE always: Even if we have not found the broken + dnl getline function among $LIBS, it may exist in libinet and the + dnl executable may be linked with -linet. + REPLACE_GETLINE=1 + ;; + esac +]) + +# Prerequisites of lib/getline.c. +AC_DEFUN([gl_PREREQ_GETLINE], +[ + : +]) diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4 index 10c74fa2392..501fe20f4e5 100644 --- a/m4/gnulib-comp.m4 +++ b/m4/gnulib-comp.m4 @@ -44,6 +44,7 @@ AC_DEFUN([gl_EARLY], # Code from module absolute-header: # Code from module acl-permissions: + # Code from module alignasof: # Code from module alloca-opt: # Code from module allocator: # Code from module assert-h: @@ -103,8 +104,10 @@ AC_DEFUN([gl_EARLY], # Code from module fsync: # Code from module futimens: # Code from module gen-header: + # Code from module getdelim: # Code from module getdtablesize: # Code from module getgroups: + # Code from module getline: # Code from module getloadavg: # Code from module getopt-gnu: # Code from module getopt-posix: @@ -231,6 +234,7 @@ AC_DEFUN([gl_INIT], gl_source_base='lib' gl_source_base_prefix= gl_FUNC_ACL + gl_ALIGNASOF gl_FUNC_ALLOCA gl_CONDITIONAL_HEADER([alloca.h]) AC_PROG_MKDIR_P @@ -342,6 +346,12 @@ AC_DEFUN([gl_INIT], gl_CONDITIONAL([GL_COND_OBJ_FUTIMENS], [test $HAVE_FUTIMENS = 0 || test $REPLACE_FUTIMENS = 1]) gl_SYS_STAT_MODULE_INDICATOR([futimens]) + gl_FUNC_GETLINE + gl_CONDITIONAL([GL_COND_OBJ_GETLINE], [test $REPLACE_GETLINE = 1]) + AM_COND_IF([GL_COND_OBJ_GETLINE], [ + gl_PREREQ_GETLINE + ]) + gl_STDIO_MODULE_INDICATOR([getline]) AC_REQUIRE([AC_CANONICAL_HOST]) gl_GETLOADAVG gl_CONDITIONAL([GL_COND_OBJ_GETLOADAVG], [test $HAVE_GETLOADAVG = 0]) @@ -637,6 +647,7 @@ AC_DEFUN([gl_INIT], gl_gnulib_enabled_dirfd=false gl_gnulib_enabled_925677f0343de64b89a9f0c790b4104c=false gl_gnulib_enabled_euidaccess=false + gl_gnulib_enabled_getdelim=false gl_gnulib_enabled_getdtablesize=false gl_gnulib_enabled_getgroups=false gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=false @@ -708,6 +719,19 @@ AC_DEFUN([gl_INIT], func_gl_gnulib_m4code_6099e9737f757db36c47fa9d9f02e88c fi } + func_gl_gnulib_m4code_getdelim () + { + if ! $gl_gnulib_enabled_getdelim; then + gl_FUNC_GETDELIM + gl_CONDITIONAL([GL_COND_OBJ_GETDELIM], + [test $HAVE_GETDELIM = 0 || test $REPLACE_GETDELIM = 1]) + AM_COND_IF([GL_COND_OBJ_GETDELIM], [ + gl_PREREQ_GETDELIM + ]) + gl_STDIO_MODULE_INDICATOR([getdelim]) + gl_gnulib_enabled_getdelim=true + fi + } func_gl_gnulib_m4code_getdtablesize () { if ! $gl_gnulib_enabled_getdtablesize; then @@ -973,6 +997,9 @@ AC_DEFUN([gl_INIT], if test $HAVE_FUTIMENS = 0 || test $REPLACE_FUTIMENS = 1; then func_gl_gnulib_m4code_utimens fi + if test $REPLACE_GETLINE = 1; then + func_gl_gnulib_m4code_getdelim + fi if case $host_os in mingw*) false;; *) test $HAVE_GETLOADAVG = 0;; esac; then func_gl_gnulib_m4code_open fi @@ -1012,6 +1039,7 @@ AC_DEFUN([gl_INIT], AM_CONDITIONAL([gl_GNULIB_ENABLED_dirfd], [$gl_gnulib_enabled_dirfd]) AM_CONDITIONAL([gl_GNULIB_ENABLED_925677f0343de64b89a9f0c790b4104c], [$gl_gnulib_enabled_925677f0343de64b89a9f0c790b4104c]) AM_CONDITIONAL([gl_GNULIB_ENABLED_euidaccess], [$gl_gnulib_enabled_euidaccess]) + AM_CONDITIONAL([gl_GNULIB_ENABLED_getdelim], [$gl_gnulib_enabled_getdelim]) AM_CONDITIONAL([gl_GNULIB_ENABLED_getdtablesize], [$gl_gnulib_enabled_getdtablesize]) AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getgroups]) AM_CONDITIONAL([gl_GNULIB_ENABLED_be453cec5eecf5731a274f2de7f2db36], [$gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36]) @@ -1278,8 +1306,10 @@ AC_DEFUN([gl_FILE_LIST], [ lib/ftoastr.h lib/futimens.c lib/get-permissions.c + lib/getdelim.c lib/getdtablesize.c lib/getgroups.c + lib/getline.c lib/getloadavg.c lib/getopt-cdefs.in.h lib/getopt-core.h @@ -1456,8 +1486,10 @@ AC_DEFUN([gl_FILE_LIST], [ m4/fsusage.m4 m4/fsync.m4 m4/futimens.m4 + m4/getdelim.m4 m4/getdtablesize.m4 m4/getgroups.m4 + m4/getline.m4 m4/getloadavg.m4 m4/getopt.m4 m4/getrandom.m4 diff --git a/m4/stdalign.m4 b/m4/stdalign.m4 index b1438eeaced..0bb9281f5ee 100644 --- a/m4/stdalign.m4 +++ b/m4/stdalign.m4 @@ -5,9 +5,11 @@ dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. +dnl Written by Paul Eggert and Bruno Haible. + # Prepare for substituting if it is not supported. -AC_DEFUN([gl_STDALIGN_H], +AC_DEFUN([gl_ALIGNASOF], [ AC_CACHE_CHECK([for alignas and alignof], [gl_cv_header_working_stdalign_h], @@ -58,16 +60,11 @@ AC_DEFUN([gl_STDALIGN_H], test "$gl_cv_header_working_stdalign_h" != no && break done]) - GL_GENERATE_STDALIGN_H=false AS_CASE([$gl_cv_header_working_stdalign_h], - [no], - [GL_GENERATE_STDALIGN_H=true], [yes*keyword*], [AC_DEFINE([HAVE_C_ALIGNASOF], [1], [Define to 1 if the alignas and alignof keywords work.])]) - AC_CHECK_HEADERS_ONCE([stdalign.h]) - dnl The "zz" puts this toward config.h's end, to avoid potential dnl collisions with other definitions. AH_VERBATIM([zzalignas], @@ -75,11 +72,33 @@ AC_DEFUN([gl_STDALIGN_H], # if HAVE_STDALIGN_H # include # else - /* Substitute. Keep consistent with gnulib/lib/stdalign.in.h. */ -# ifndef _GL_STDALIGN_H -# define _GL_STDALIGN_H -# undef _Alignas -# undef _Alignof +/* ISO C23 alignas and alignof for platforms that lack it. + + References: + ISO C23 (latest free draft + ) + sections 6.5.3.4, 6.7.5, 7.15. + C++11 (latest free draft + ) + section 18.10. */ + +/* alignof (TYPE), also known as _Alignof (TYPE), yields the alignment + requirement of a structure member (i.e., slot or field) that is of + type TYPE, as an integer constant expression. + + This differs from GCC's and clang's __alignof__ operator, which can + yield a better-performing alignment for an object of that type. For + example, on x86 with GCC and on Linux/x86 with clang, + __alignof__ (double) and __alignof__ (long long) are 8, whereas + alignof (double) and alignof (long long) are 4 unless the option + '-malign-double' is used. + + The result cannot be used as a value for an 'enum' constant, if you + want to be portable to HP-UX 10.20 cc and AIX 3.2.5 xlc. */ + +/* GCC releases before GCC 4.9 had a bug in _Alignof. See GCC bug 52023 + . + clang versions < 8.0.0 have the same bug. */ # if (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 \ || (defined __GNUC__ && __GNUC__ < 4 + (__GNUC_MINOR__ < 9) \ && !defined __clang__) \ @@ -100,35 +119,65 @@ AC_DEFUN([gl_STDALIGN_H], # if ! (defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER)) # define alignof _Alignof # endif -# define __alignof_is_defined 1 -# if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 -# if defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER) -# define _Alignas(a) alignas (a) -# elif (!defined __attribute__ \ - && ((defined __APPLE__ && defined __MACH__ \ - ? 4 < __GNUC__ + (1 <= __GNUC_MINOR__) \ - : __GNUC__ && !defined __ibmxl__) \ - || (4 <= __clang_major__) \ - || (__ia64 && (61200 <= __HP_cc || 61200 <= __HP_aCC)) \ - || __ICC || 0x590 <= __SUNPRO_C || 0x0600 <= __xlC__)) -# define _Alignas(a) __attribute__ ((__aligned__ (a))) -# elif 1300 <= _MSC_VER -# define _Alignas(a) __declspec (align (a)) -# endif -# endif -# if ((defined _Alignas \ - && !(defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER))) \ - || (defined __STDC_VERSION__ && 201112 <= __STDC_VERSION__)) -# define alignas _Alignas -# endif -# if (defined alignas \ - || (defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER))) -# define __alignas_is_defined 1 -# endif -# if _GL_STDALIGN_NEEDS_STDDEF -# include + +/* alignas (A), also known as _Alignas (A), aligns a variable or type + to the alignment A, where A is an integer constant expression. For + example: + + int alignas (8) foo; + struct s { int a; int alignas (8) bar; }; + + aligns the address of FOO and the offset of BAR to be multiples of 8. + + A should be a power of two that is at least the type's alignment + and at most the implementation's alignment limit. This limit is + 2**28 on typical GNUish hosts, and 2**13 on MSVC. To be portable + to MSVC through at least version 10.0, A should be an integer + constant, as MSVC does not support expressions such as 1 << 3. + To be portable to Sun C 5.11, do not align auto variables to + anything stricter than their default alignment. + + The following C23 requirements are not supported here: + + - If A is zero, alignas has no effect. + - alignas can be used multiple times; the strictest one wins. + - alignas (TYPE) is equivalent to alignas (alignof (TYPE)). + + */ +# if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 +# if defined __cplusplus && (201103 <= __cplusplus || defined _MSC_VER) +# define _Alignas(a) alignas (a) +# elif (!defined __attribute__ \ + && ((defined __APPLE__ && defined __MACH__ \ + ? 4 < __GNUC__ + (1 <= __GNUC_MINOR__) \ + : __GNUC__ && !defined __ibmxl__) \ + || (4 <= __clang_major__) \ + || (__ia64 && (61200 <= __HP_cc || 61200 <= __HP_aCC)) \ + || __ICC || 0x590 <= __SUNPRO_C || 0x0600 <= __xlC__)) +# define _Alignas(a) __attribute__ ((__aligned__ (a))) +# elif 1300 <= _MSC_VER +# define _Alignas(a) __declspec (align (a)) # endif -# endif /* _GL_STDALIGN_H */ +# endif +# if ((defined _Alignas \ + && !(defined __cplusplus \ + && (201103 <= __cplusplus || defined _MSC_VER))) \ + || (defined __STDC_VERSION__ && 201112 <= __STDC_VERSION__)) +# define alignas _Alignas +# endif +# if _GL_STDALIGN_NEEDS_STDDEF +# include +# endif # endif #endif]) ]) + +AC_DEFUN([gl_STDALIGN_H], +[ + AC_REQUIRE([gl_ALIGNASOF]) + GL_GENERATE_STDALIGN_H=false + AS_IF([test "$gl_cv_header_working_stdalign_h" = no], + [GL_GENERATE_STDALIGN_H=true]) + + AC_CHECK_HEADERS_ONCE([stdalign.h]) +]) diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4 index f4384027e37..dd799ae27db 100644 --- a/m4/unistd_h.m4 +++ b/m4/unistd_h.m4 @@ -50,6 +50,7 @@ AC_DEFUN_ONCE([gl_UNISTD_H], group_member isatty lchown link linkat lseek pipe pipe2 pread pwrite readlink readlinkat rmdir sethostname sleep symlink symlinkat truncate ttyname_r unlink unlinkat usleep]) + gl_CHECK_FUNCS_ANDROID([ftruncate], [[#include ]]) AC_REQUIRE([AC_C_RESTRICT]) diff --git a/m4/utimens.m4 b/m4/utimens.m4 index c5d9b69e6f5..2b87f0149b5 100644 --- a/m4/utimens.m4 +++ b/m4/utimens.m4 @@ -11,7 +11,8 @@ AC_DEFUN([gl_UTIMENS], AC_REQUIRE([gl_FUNC_UTIMES]) AC_REQUIRE([gl_CHECK_TYPE_STRUCT_TIMESPEC]) AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles - AC_CHECK_FUNCS_ONCE([futimens utimensat lutimes]) + AC_CHECK_FUNCS_ONCE([futimens lutimes]) + gl_CHECK_FUNCS_ANDROID([utimensat], [[#include ]]) gl_CHECK_FUNCS_ANDROID([futimes], [[#include ]]) gl_CHECK_FUNCS_ANDROID([futimesat], [[#include ]]) diff --git a/m4/utimensat.m4 b/m4/utimensat.m4 index dd210fc989a..f92fc3af7d1 100644 --- a/m4/utimensat.m4 +++ b/m4/utimensat.m4 @@ -13,7 +13,9 @@ AC_DEFUN([gl_FUNC_UTIMENSAT], AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS]) AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles - AC_CHECK_FUNCS_ONCE([utimensat]) + # This is necessary for cross-compiles, because otherwise utimensat + # will appear to work. + gl_CHECK_FUNCS_ANDROID([utimensat], [[#include ]]) if test $ac_cv_func_utimensat = no; then HAVE_UTIMENSAT=0 else diff --git a/m4/xattr.m4 b/m4/xattr.m4 index 6141515652a..0e179cc0d1d 100644 --- a/m4/xattr.m4 +++ b/m4/xattr.m4 @@ -1,5 +1,5 @@ # xattr.m4 - check for Extended Attributes (Linux) -# serial 5 +# serial 6 # Copyright (C) 2003-2023 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation @@ -17,23 +17,33 @@ AC_DEFUN([gl_FUNC_XATTR], AC_SUBST([LIB_XATTR]) if test "$use_xattr" = yes; then - AC_CHECK_HEADERS([attr/error_context.h attr/libattr.h]) - use_xattr=no - if test "$ac_cv_header_attr_libattr_h" = yes \ - && test "$ac_cv_header_attr_error_context_h" = yes; then - xattr_saved_LIBS=$LIBS - AC_SEARCH_LIBS([attr_copy_file], [attr], - [test "$ac_cv_search_attr_copy_file" = "none required" || - LIB_XATTR="$ac_cv_search_attr_copy_file"]) - AC_CHECK_FUNCS([attr_copy_file]) - LIBS=$xattr_saved_LIBS - if test "$ac_cv_func_attr_copy_file" = yes; then - use_xattr=yes - fi - fi - if test $use_xattr = no; then + AC_CACHE_CHECK([for xattr library with ATTR_ACTION_PERMISSIONS], + [gl_cv_xattr_lib], + [gl_cv_xattr_lib=no + AC_LANG_CONFTEST( + [AC_LANG_PROGRAM( + [[#include + #include + static int + is_attr_permissions (const char *name, struct error_context *ctx) + { + return attr_copy_action (name, ctx) == ATTR_ACTION_PERMISSIONS; + } + ]], + [[return attr_copy_fd ("/", 0, "/", 0, is_attr_permissions, 0); + ]])]) + AC_LINK_IFELSE([], + [gl_cv_xattr_lib='none required'], + [xattr_saved_LIBS=$LIBS + LIBS="-lattr $LIBS" + AC_LINK_IFELSE([], [gl_cv_xattr_lib=-lattr]) + LIBS=$xattr_saved_LIBS])]) + if test "$gl_cv_xattr_lib" = no; then AC_MSG_WARN([libattr development library was not found or not usable.]) AC_MSG_WARN([AC_PACKAGE_NAME will be built without xattr support.]) + use_xattr=no + elif test "$gl_cv_xattr_lib" != 'none required'; then + LIB_XATTR=$gl_cv_xattr_lib fi fi if test "$use_xattr" = yes; then diff --git a/src/alloc.c b/src/alloc.c index 86e019b931b..ed55ae32710 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -6172,16 +6172,44 @@ mark_pinned_objects (void) mark_object (pobj->object); } +#if defined HAVE_ANDROID && !defined (__clang__) + +/* The Android gcc is broken and needs the following version of + make_lisp_symbol. Otherwise a mysterious ICE pops up. */ + +#define make_lisp_symbol android_make_lisp_symbol + +static Lisp_Object +android_make_lisp_symbol (struct Lisp_Symbol *sym) +{ + intptr_t symoffset; + Lisp_Object a; + + symoffset = (intptr_t) sym; + INT_SUBTRACT_WRAPV (symoffset, (intptr_t) &lispsym, + &symoffset); + + a = TAG_PTR (Lisp_Symbol, symoffset); + return a; +} + +#endif + static void mark_pinned_symbols (void) { struct symbol_block *sblk; - int lim = (symbol_block_pinned == symbol_block - ? symbol_block_index : SYMBOL_BLOCK_SIZE); + int lim; + struct Lisp_Symbol *sym, *end; + + if (symbol_block_pinned == symbol_block) + lim = symbol_block_index; + else + lim = SYMBOL_BLOCK_SIZE; for (sblk = symbol_block_pinned; sblk; sblk = sblk->next) { - struct Lisp_Symbol *sym = sblk->symbols, *end = sym + lim; + sym = sblk->symbols, end = sym + lim; for (; sym < end; ++sym) if (sym->u.s.pinned) mark_object (make_lisp_symbol (sym)); diff --git a/src/android.c b/src/android.c index cfb79045c0b..eb9c404f1a3 100644 --- a/src/android.c +++ b/src/android.c @@ -54,6 +54,9 @@ bool android_init_gui; #include #include +#include + +#include #define ANDROID_THROW(env, class, msg) \ ((*(env))->ThrowNew ((env), (*(env))->FindClass ((env), class), msg)) @@ -114,6 +117,12 @@ struct android_emacs_drawable jmethodID damage_rect; }; +struct android_emacs_window +{ + jclass class; + jmethodID swap_buffers; +}; + /* The asset manager being used. */ static AAssetManager *asset_manager; @@ -181,6 +190,9 @@ static struct android_graphics_point point_class; /* Various methods associated with the EmacsDrawable class. */ static struct android_emacs_drawable drawable_class; +/* Various methods associated with the EmacsWindow class. */ +static struct android_emacs_window window_class; + /* Event handling functions. Events are stored on a (circular) queue @@ -247,10 +259,21 @@ android_run_select_thread (void *data) sigfillset (&signals); +#if __ANDROID_API__ < 16 + /* sigprocmask must be used instead of pthread_sigmask due to a bug + in Android versions earlier than 16. It only affects the calling + thread on Android anyhow. */ + + if (sigprocmask (SIG_BLOCK, &signals, NULL)) + __android_log_print (ANDROID_LOG_FATAL, __func__, + "sigprocmask: %s", + strerror (errno)); +#else if (pthread_sigmask (SIG_BLOCK, &signals, NULL)) __android_log_print (ANDROID_LOG_FATAL, __func__, "pthread_sigmask: %s", strerror (errno)); +#endif sigdelset (&signals, SIGUSR1); sigemptyset (&waitset); @@ -292,6 +315,8 @@ android_run_select_thread (void *data) still be locked, so this must come before. */ sem_post (&android_pselect_sem); } + + return NULL; } static void @@ -538,6 +563,200 @@ android_run_debug_thread (void *data) +/* Asset directory handling functions. ``directory-tree'' is a file in + the root of the assets directory describing its contents. + + See lib-src/asset-directory-tool for more details. */ + +/* The Android directory tree. */ +static const char *directory_tree; + +/* The size of the directory tree. */ +static size_t directory_tree_size; + +/* Read an unaligned (32-bit) long from the address POINTER. */ + +static unsigned int +android_extract_long (char *pointer) +{ + unsigned int number; + + memcpy (&number, pointer, sizeof number); + return number; +} + +/* Scan to the file FILE in the asset directory tree. Return a + pointer to the end of that file (immediately before any children) + in the directory tree, or NULL if that file does not exist. + + If returning non-NULL, also return the offset to the end of the + last subdirectory or file in *LIMIT_RETURN. LIMIT_RETURN may be + NULL. + + FILE must have less than 11 levels of nesting. If it ends with a + trailing slash, then NULL will be returned if it is not actually a + directory. */ + +static const char * +android_scan_directory_tree (char *file, size_t *limit_return) +{ + char *token, *saveptr, *copy, *copy1, *start, *max, *limit; + size_t token_length, ntokens, i; + char *tokens[10]; + + USE_SAFE_ALLOCA; + + /* Skip past the 5 byte header. */ + start = (char *) directory_tree + 5; + + /* Figure out the current limit. */ + limit = (char *) directory_tree + directory_tree_size; + + /* Now, split `file' into tokens, with the delimiter being the file + name separator. Look for the file and seek past it. */ + + ntokens = 0; + saveptr = NULL; + copy = copy1 = xstrdup (file); + memset (tokens, 0, sizeof tokens); + + while ((token = strtok_r (copy, "/", &saveptr))) + { + copy = NULL; + + /* Make sure ntokens is within bounds. */ + if (ntokens == ARRAYELTS (tokens)) + { + xfree (copy1); + goto fail; + } + + tokens[ntokens] = SAFE_ALLOCA (strlen (token) + 1); + memcpy (tokens[ntokens], token, strlen (token) + 1); + ntokens++; + } + + /* Free the copy created for strtok_r. */ + xfree (copy1); + + /* If there are no tokens, just return the start of the directory + tree. */ + if (!ntokens) + { + SAFE_FREE (); + + /* Subtract the initial header bytes. */ + if (limit_return) + *limit_return = directory_tree_size - 5; + + return start; + } + + /* Loop through tokens, indexing the directory tree each time. */ + + for (i = 0; i < ntokens; ++i) + { + token = tokens[i]; + + /* Figure out how many bytes to compare. */ + token_length = strlen (token); + + again: + + /* If this would be past the directory, return NULL. */ + if (start + token_length > limit) + goto fail; + + /* Now compare the file name. */ + if (!memcmp (start, token, token_length)) + { + /* They probably match. Find the NULL byte. It must be + either one byte past start + token_length, with the last + byte a trailing slash (indicating that it is a + directory), or just start + token_length. Return 4 bytes + past the next NULL byte. */ + + max = memchr (start, 0, limit - start); + + if (max != start + token_length + && !(max == start + token_length + 1 + && *(max - 1) == '/')) + goto false_positive; + + /* Return it if it exists and is in range, and this is the + last token. Otherwise, set it as start and the limit as + start + the offset and continue the loop. */ + + if (max && max + 5 <= limit) + { + if (i < ntokens - 1) + { + start = max + 5; + limit = ((char *) directory_tree + + android_extract_long (max + 1)); + + /* Make sure limit is still in range. */ + if (limit > directory_tree + directory_tree_size + || start > directory_tree + directory_tree_size) + goto fail; + + continue; + } + + /* Now see if max is not a directory and file is. If + file is a directory, then return NULL. */ + if (*(max - 1) != '/' && file[strlen (file) - 1] == '/') + max = NULL; + else + { + /* Figure out the limit. */ + if (limit_return) + *limit_return = android_extract_long (max + 1); + + /* Go to the end of this file. */ + max += 5; + } + + SAFE_FREE (); + return max; + } + + /* Return NULL otherwise. */ + __android_log_print (ANDROID_LOG_WARN, __func__, + "could not scan to end of directory tree" + ": %s", file); + goto fail; + } + + false_positive: + + /* No match was found. Set start to the next sibling and try + again. */ + + start = memchr (start, 0, limit - start); + + if (!start || start + 5 > limit) + goto fail; + + start = ((char *) directory_tree + + android_extract_long (start + 1)); + + /* Make sure start is still in bounds. */ + + if (start > limit) + goto fail; + + /* Continue the loop. */ + goto again; + } + + fail: + SAFE_FREE (); + return NULL; +} + + + /* Intercept USER_FULL_NAME and return something that makes sense if pw->pw_gecos is NULL. */ @@ -624,10 +843,6 @@ android_fstatat (int dirfd, const char *restrict pathname, bool android_file_access_p (const char *name, int amode) { - AAsset *asset; - AAssetDir *directory; - int length; - if (!asset_manager) return false; @@ -637,50 +852,11 @@ android_file_access_p (const char *name, int amode) /* /assets always exists. */ return true; - /* Check if the asset exists by opening it. Suboptimal! */ - asset = AAssetManager_open (asset_manager, name, - AASSET_MODE_UNKNOWN); - - if (!asset) - { - /* See if it's a directory as well. To open a directory - with the asset manager, the trailing slash (if specified) - must be removed. */ - directory = AAssetManager_openDir (asset_manager, name); - - if (directory) - { - /* Make sure the directory actually has files in it. */ - - if (!AAssetDir_getNextFileName (directory)) - { - AAssetDir_close (directory); - errno = ENOENT; - return false; - } - - AAssetDir_close (directory); - return true; - } - - errno = ENOENT; - return false; - } - - AAsset_close (asset); - - /* If NAME is a directory name, but it was a regular file, set - errno to ENOTDIR and return false. This is to behave like - faccessat. */ - - length = strlen (name); - if (name[length - 1] == '/') - { - errno = ENOTDIR; - return false; - } - - return true; + /* Check if the file exists by looking in the ``directory tree'' + asset generated during the build process. This is used + instead of the AAsset functions, because the latter are + buggy and treat directories inconsistently. */ + return android_scan_directory_tree ((char *) name, NULL) != NULL; } return false; @@ -908,8 +1084,13 @@ android_get_home_directory (void) /* JNI functions called by Java. */ +#ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmissing-prototypes" +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-prototypes" +#endif JNIEXPORT void JNICALL NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, @@ -923,6 +1104,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, int pipefd[2]; pthread_t thread; const char *java_string; + AAsset *asset; /* This may be called from multiple threads. setEmacsParams should only ever be called once. */ @@ -943,6 +1125,33 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, /* Set the asset manager. */ asset_manager = AAssetManager_fromJava (env, local_asset_manager); + /* Initialize the directory tree. */ + asset = AAssetManager_open (asset_manager, "directory-tree", + AASSET_MODE_BUFFER); + + if (!asset) + { + __android_log_print (ANDROID_LOG_FATAL, __func__, + "Failed to open directory tree"); + emacs_abort (); + } + + directory_tree = AAsset_getBuffer (asset); + + if (!directory_tree) + emacs_abort (); + + /* Now figure out how big the directory tree is, and compare the + first few bytes. */ + directory_tree_size = AAsset_getLength (asset); + if (directory_tree_size < 5 + || memcmp (directory_tree, "EMACS", 5)) + { + __android_log_print (ANDROID_LOG_FATAL, __func__, + "Directory tree has bad magic"); + emacs_abort (); + } + /* Hold a VM reference to the asset manager to prevent the native object from being deleted. */ (*env)->NewGlobalRef (env, local_asset_manager); @@ -1199,6 +1408,36 @@ android_init_emacs_drawable (void) #undef FIND_METHOD } +static void +android_init_emacs_window (void) +{ + jclass old; + + window_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsWindow"); + eassert (window_class.class); + + old = window_class.class; + window_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!window_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + window_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + window_class.class, \ + name, signature); \ + assert (window_class.c_name); + + FIND_METHOD (swap_buffers, "swapBuffers", "()V"); +#undef FIND_METHOD +} + extern JNIEXPORT void JNICALL NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv) { @@ -1232,6 +1471,7 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv) android_init_emacs_pixmap (); android_init_graphics_point (); android_init_emacs_drawable (); + android_init_emacs_window (); /* Set HOME to the app data directory. */ setenv ("HOME", android_files_dir, 1); @@ -1545,7 +1785,11 @@ NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object, android_write_event (&event); } +#ifdef __clang__ #pragma clang diagnostic pop +#else +#pragma GCC diagnostic pop +#endif @@ -1557,8 +1801,6 @@ NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object, This means that every local reference must be explicitly destroyed with DeleteLocalRef. A helper macro is provided to do this. */ -#define MAX_HANDLE 65535 - struct android_handle_entry { /* The type. */ @@ -1883,7 +2125,7 @@ android_init_emacs_gc_class (void) emacs_gc_mark_dirty = (*android_java_env)->GetMethodID (android_java_env, emacs_gc_class, - "markDirty", "()V"); + "markDirty", "(Z)V"); assert (emacs_gc_mark_dirty); old = emacs_gc_class; @@ -2011,6 +2253,9 @@ android_change_gc (struct android_gc *gc, struct android_gc_values *values) { jobject what, gcontext; + jboolean clip_changed; + + clip_changed = false; android_init_emacs_gc_class (); gcontext = android_resolve_handle (gc->gcontext, @@ -2041,16 +2286,22 @@ android_change_gc (struct android_gc *gc, values->function); if (mask & ANDROID_GC_CLIP_X_ORIGIN) - (*android_java_env)->SetIntField (android_java_env, - gcontext, - emacs_gc_clip_x_origin, - values->clip_x_origin); + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_clip_x_origin, + values->clip_x_origin); + clip_changed = true; + } if (mask & ANDROID_GC_CLIP_Y_ORIGIN) - (*android_java_env)->SetIntField (android_java_env, - gcontext, - emacs_gc_clip_y_origin, - values->clip_y_origin); + { + (*android_java_env)->SetIntField (android_java_env, + gcontext, + emacs_gc_clip_y_origin, + values->clip_y_origin); + clip_changed = true; + } if (mask & ANDROID_GC_CLIP_MASK) { @@ -2070,6 +2321,7 @@ android_change_gc (struct android_gc *gc, xfree (gc->clip_rects); gc->clip_rects = NULL; gc->num_clip_rects = -1; + clip_changed = true; } if (mask & ANDROID_GC_STIPPLE) @@ -2103,7 +2355,8 @@ android_change_gc (struct android_gc *gc, if (mask) (*android_java_env)->CallVoidMethod (android_java_env, gcontext, - emacs_gc_mark_dirty); + emacs_gc_mark_dirty, + (jboolean) clip_changed); } void @@ -2174,7 +2427,8 @@ android_set_clip_rectangles (struct android_gc *gc, int clip_x_origin, (*android_java_env)->CallVoidMethod (android_java_env, gcontext, - emacs_gc_mark_dirty); + emacs_gc_mark_dirty, + (jboolean) true); /* Cache the clip rectangles on the C side for sfntfont-android.c. */ @@ -2327,18 +2581,15 @@ android_swap_buffers (struct android_swap_info *swap_info, int num_windows) { jobject window; - jmethodID swap_buffers; int i; - swap_buffers = android_lookup_method ("org/gnu/emacs/EmacsWindow", - "swapBuffers", "()V"); - for (i = 0; i < num_windows; ++i) { window = android_resolve_handle (swap_info[i].swap_window, ANDROID_HANDLE_WINDOW); (*android_java_env)->CallVoidMethod (android_java_env, - window, swap_buffers); + window, + window_class.swap_buffers); } } @@ -3555,11 +3806,17 @@ android_sync (void) +#if __ANDROID_API__ >= 17 + #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. */ + it with zero here. This isn't caught during configuration. + + This replacement is only done when building for Android 17 or + later, because earlier versions use the gnulib replacement that + lacks these issues. */ int faccessat (int dirfd, const char *pathname, int mode, int flags) @@ -3572,6 +3829,8 @@ faccessat (int dirfd, const char *pathname, int mode, int flags) return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS); } +#endif /* __ANDROID_API__ < 16 */ + /* Directory listing emulation. */ @@ -3581,8 +3840,11 @@ struct android_dir /* The real DIR *, if it exists. */ DIR *dir; - /* Otherwise, the AAssetDir. */ - void *asset_dir; + /* Otherwise, the pointer to the directory in directory_tree. */ + char *asset_dir; + + /* And the end of the files in asset_dir. */ + char *asset_limit; }; /* Like opendir. However, return an asset directory if NAME points to @@ -3592,8 +3854,9 @@ struct android_dir * android_opendir (const char *name) { struct android_dir *dir; - AAssetDir *asset_dir; + char *asset_dir; const char *asset_name; + size_t limit; asset_name = android_get_asset_name (name); @@ -3601,8 +3864,9 @@ android_opendir (const char *name) directory. */ if (asset_manager && asset_name) { - asset_dir = AAssetManager_openDir (asset_manager, - asset_name); + asset_dir + = (char *) android_scan_directory_tree ((char *) asset_name, + &limit); if (!asset_dir) { @@ -3613,6 +3877,20 @@ android_opendir (const char *name) dir = xmalloc (sizeof *dir); dir->dir = NULL; dir->asset_dir = asset_dir; + dir->asset_limit = (char *) directory_tree + limit; + + /* Make sure dir->asset_limit is within bounds. It is a limit, + and as such can be exactly one byte past directory_tree. */ + if (dir->asset_limit > directory_tree + directory_tree_size) + { + xfree (dir); + __android_log_print (ANDROID_LOG_VERBOSE, __func__, + "Invalid dir tree, limit %zu, size %zu\n", + limit, directory_tree_size); + dir = NULL; + errno = EACCES; + } + return dir; } @@ -3636,23 +3914,55 @@ struct dirent * android_readdir (struct android_dir *dir) { static struct dirent dirent; - const char *filename; + const char *last; if (dir->asset_dir) { - filename = AAssetDir_getNextFileName (dir->asset_dir); - errno = 0; + /* There are no more files to read. */ + if (dir->asset_dir >= dir->asset_limit) + return NULL; + + /* Otherwise, scan forward looking for the next NULL byte. */ + last = memchr (dir->asset_dir, 0, + dir->asset_limit - dir->asset_dir); + + /* No more NULL bytes remain. */ + if (!last) + return NULL; + + /* Forward last past the NULL byte. */ + last++; - if (!filename) + /* Make sure it is still within the directory tree. */ + if (last >= directory_tree + directory_tree_size) return NULL; + /* Now, fill in the dirent with the name. */ memset (&dirent, 0, sizeof dirent); dirent.d_ino = 0; dirent.d_off = 0; dirent.d_reclen = sizeof dirent; dirent.d_type = DT_UNKNOWN; - strncpy (dirent.d_name, filename, - sizeof dirent.d_name - 1); + + /* Note that dir->asset_dir is actually a NULL terminated + string. */ + memcpy (dirent.d_name, dir->asset_dir, + MIN (sizeof dirent.d_name, + last - dir->asset_dir)); + dirent.d_name[sizeof dirent.d_name - 1] = '\0'; + + /* Strip off the trailing slash, if any. */ + if (dirent.d_name[MIN (sizeof dirent.d_name, + last - dir->asset_dir) + - 2] == '/') + dirent.d_name[MIN (sizeof dirent.d_name, + last - dir->asset_dir) + - 2] = '\0'; + + /* Finally, forward dir->asset_dir to the file past last. */ + dir->asset_dir = ((char *) directory_tree + + android_extract_long ((char *) last)); + return &dirent; } @@ -3666,8 +3976,9 @@ android_closedir (struct android_dir *dir) { if (dir->dir) closedir (dir->dir); - else - AAssetDir_close (dir->asset_dir); + + /* There is no need to close anything else, as the directory tree + lies in statically allocated memory. */ xfree (dir); } @@ -3795,40 +4106,40 @@ android_four_corners_bilinear (unsigned int tl, unsigned int tr, unsigned int bl, unsigned int br, int distx, int disty) { - int distxy, distxiy, distixy, distixiy; - uint32_t f, r; - - distxy = distx * disty; - distxiy = (distx << 8) - distxy; - distixy = (disty << 8) - distxy; - distixiy = (256 * 256 - (disty << 8) - - (distx << 8) + distxy); - - /* Red */ - r = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy - + (bl & 0x000000ff) * distixy + (br & 0x000000ff) * distxy); - - /* Green */ - f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy - + (bl & 0x0000ff00) * distixy + (br & 0x0000ff00) * distxy); - r |= f & 0xff000000; - - /* Now do the upper two components. */ - tl >>= 16; - tr >>= 16; - bl >>= 16; - br >>= 16; - r >>= 16; - - /* Blue */ - f = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy - + (bl & 0x000000ff) * distixy + (br & 0x000000ff) * distxy); - r |= f & 0x00ff0000; - - /* Alpha */ - f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy - + (bl & 0x0000ff00) * distixy + (br & 0x0000ff00) * distxy); - r |= f & 0xff000000; + int distxy, distxiy, distixy, distixiy; + uint32_t f, r; + + distxy = distx * disty; + distxiy = (distx << 8) - distxy; + distixy = (disty << 8) - distxy; + distixiy = (256 * 256 - (disty << 8) + - (distx << 8) + distxy); + + /* Red */ + r = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy + + (bl & 0x000000ff) * distixy + (br & 0x000000ff) * distxy); + + /* Green */ + f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy + + (bl & 0x0000ff00) * distixy + (br & 0x0000ff00) * distxy); + r |= f & 0xff000000; + + /* Now do the upper two components. */ + tl >>= 16; + tr >>= 16; + bl >>= 16; + br >>= 16; + r >>= 16; + + /* Blue */ + f = ((tl & 0x000000ff) * distixiy + (tr & 0x000000ff) * distxiy + + (bl & 0x000000ff) * distixy + (br & 0x000000ff) * distxy); + r |= f & 0x00ff0000; + + /* Alpha */ + f = ((tl & 0x0000ff00) * distixiy + (tr & 0x0000ff00) * distxiy + + (bl & 0x0000ff00) * distixy + (br & 0x0000ff00) * distxy); + r |= f & 0xff000000; return r; } @@ -4010,6 +4321,36 @@ android_project_image_nearest (struct android_image *image, } } + + +/* System call wrappers for stuff missing in bionic. */ + +#ifndef HAVE_FTRUNCATE + +/* ftruncate wrapper for Android, for systems without ftruncate in the + C library. + + Such systems are always 32 bit systems, since Android 21 and later + all support ftruncate. In addition, ARM and MIPS require registers + used to store long long parameters to be aligned to an even + register pair. */ + +int +android_ftruncate (int fd, off_t length) +{ + int rc; + +#if defined __arm__ || defined __mips__ + return syscall (SYS_ftruncate64, fd, 0, + (unsigned int) (length & 0xffffffff), + (unsigned int) (length >> 32)); +#else + return syscall (SYS_ftruncate64, fd, length); +#endif +} + +#endif + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ diff --git a/src/android.h b/src/android.h index 036e6d266fd..240bc90d831 100644 --- a/src/android.h +++ b/src/android.h @@ -102,6 +102,14 @@ extern struct android_dir *android_opendir (const char *); extern struct dirent *android_readdir (struct android_dir *); extern void android_closedir (struct android_dir *); +#ifndef HAVE_FTRUNCATE +extern int android_ftruncate (int, off_t); + +/* Replace calls to ftruncate with android_ftruncate when ftruncate is + not defined. */ +#define ftruncate android_ftruncate +#endif + #endif diff --git a/src/androidterm.c b/src/androidterm.c index f19cee5b11b..3c16b542d91 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -683,7 +683,7 @@ handle_one_android_event (struct android_display_info *dpyinfo, XSETFRAME (inev.ie.frame_or_window, f); } else - /* A new frame must be created. */; + ((void) 0) /* A new frame must be created. */; } case ANDROID_ENTER_NOTIFY: @@ -988,6 +988,9 @@ handle_one_android_event (struct android_display_info *dpyinfo, if (!NILP (Vmouse_highlight)) { + /* Clear the pointer invisible flag to always make + note_mouse_highlight do its thing. */ + any->pointer_invisible = false; note_mouse_highlight (any, x, y); /* Always allow future mouse motion to diff --git a/src/emacs.c b/src/emacs.c index f4973c70610..994a4d1db93 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -423,7 +423,15 @@ using_utf8 (void) the result is known in advance anyway... */ #if defined HAVE_WCHAR_H && !defined WINDOWSNT wchar_t wc; +#ifndef HAVE_ANDROID mbstate_t mbs = { 0 }; +#else + mbstate_t mbs; + + /* Not sure how mbstate works on Android, but this seems to be + required. */ + memset (&mbs, 0, sizeof mbs); +#endif return mbrtowc (&wc, "\xc4\x80", 2, &mbs) == 2 && wc == 0x100; #else return false; diff --git a/src/fileio.c b/src/fileio.c index 6fa524b3bb4..b5a79312d88 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -6325,6 +6325,11 @@ effect except for flushing STREAM's data. */) #ifndef DOS_NT +#if defined STAT_STATFS2_BSIZE || defined STAT_STATFS2_FRSIZE \ + || defined STAT_STATFS2_FSIZE || defined STAT_STATFS3_OSF1 \ + || defined STAT_STATFS4 || defined STAT_STATVFS \ + || defined STAT_STATVFS64 + /* Yield a Lisp number equal to BLOCKSIZE * BLOCKS, with the result negated if NEGATE. */ static Lisp_Object @@ -6339,6 +6344,8 @@ blocks_to_bytes (uintmax_t blocksize, uintmax_t blocks, bool negate) return CALLN (Ftimes, bs, make_uint (blocks)); } +#endif + DEFUN ("file-system-info", Ffile_system_info, Sfile_system_info, 1, 1, 0, doc: /* Return storage information about the file system FILENAME is on. Value is a list of numbers (TOTAL FREE AVAIL), where TOTAL is the total @@ -6360,6 +6367,11 @@ If the underlying system call fails, value is nil. */) error ("Invalid handler in `file-name-handler-alist'"); } + /* Try to detect whether or not fsusage.o is actually built. */ +#if defined STAT_STATFS2_BSIZE || defined STAT_STATFS2_FRSIZE \ + || defined STAT_STATFS2_FSIZE || defined STAT_STATFS3_OSF1 \ + || defined STAT_STATFS4 || defined STAT_STATVFS \ + || defined STAT_STATVFS64 struct fs_usage u; if (get_fs_usage (SSDATA (ENCODE_FILE (filename)), NULL, &u) != 0) return errno == ENOSYS ? Qnil : file_attribute_errno (filename, errno); @@ -6367,6 +6379,9 @@ If the underlying system call fails, value is nil. */) blocks_to_bytes (u.fsu_blocksize, u.fsu_bfree, false), blocks_to_bytes (u.fsu_blocksize, u.fsu_bavail, u.fsu_bavail_top_bit_set)); +#else + return Qnil; +#endif } #endif /* !DOS_NT */ diff --git a/src/filelock.c b/src/filelock.c index 51e1ffca9db..45eac5a19a1 100644 --- a/src/filelock.c +++ b/src/filelock.c @@ -65,6 +65,12 @@ along with GNU Emacs. If not, see . */ #define BOOT_TIME_FILE "/var/run/random-seed" #endif +/* Boot time is not available on Android. */ + +#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY +#undef BOOT_TIME +#endif + #if !defined WTMP_FILE && !defined WINDOWSNT && defined BOOT_TIME #define WTMP_FILE "/var/log/wtmp" #endif @@ -120,12 +126,6 @@ along with GNU Emacs. If not, see . */ * Non-forced locks on non-MS-Windows systems that support neither hard nor symbolic links. */ -/* Boot time is not available on Android. */ - -#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY -#undef BOOT_TIME -#endif - /* Return the time of the last system boot. */ diff --git a/src/frame.c b/src/frame.c index 286c9a2cb71..e98256fe9ed 100644 --- a/src/frame.c +++ b/src/frame.c @@ -5666,6 +5666,8 @@ On Nextstep, this just calls `ns-parse-geometry'. */) int x UNINIT, y UNINIT; unsigned int width, height; + width = height = 0; + CHECK_STRING (string); #ifdef HAVE_NS diff --git a/src/keyboard.c b/src/keyboard.c index 834049b496a..11fa1e62c89 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -62,6 +62,10 @@ along with GNU Emacs. If not, see . */ #include "syssignal.h" +#if defined HAVE_STACK_OVERFLOW_HANDLING && !defined WINDOWSNT +#include +#endif + #include #include #include @@ -4955,7 +4959,7 @@ const char *const lispy_function_keys[] = [278] = "copy", [279] = "paste", [28] = "clear", - [4] = "back", + [4] = "XF86Back", [61] = "tab", [66] = "return", [67] = "backspace", diff --git a/src/menu.c b/src/menu.c index e1f899858d3..e02ee880119 100644 --- a/src/menu.c +++ b/src/menu.c @@ -1152,7 +1152,7 @@ x_popup_menu_1 (Lisp_Object position, Lisp_Object menu) else { menuflags |= MENU_FOR_CLICK; - tem = Fcar (XCDR (position)); /* EVENT_START (position) */ + tem = EVENT_START (position); /* EVENT_START (position) */ window = Fcar (tem); /* POSN_WINDOW (tem) */ tem2 = Fcar (Fcdr (tem)); /* POSN_POSN (tem) */ /* The MENU_KBD_NAVIGATION field is set when the menu @@ -1466,9 +1466,10 @@ cached information about equivalent key sequences. If the user gets rid of the menu without making a valid choice, for instance by clicking the mouse away from a valid choice or by typing keyboard input, then this normally results in a quit and -`x-popup-menu' does not return. But if POSITION is a mouse button -event (indicating that the user invoked the menu with the mouse) then -no quit occurs and `x-popup-menu' returns nil. */) +`x-popup-menu' does not return. But if POSITION is a mouse button or +touch screen event (indicating that the user invoked the menu with the +a pointing device) then no quit occurs and `x-popup-menu' returns +nil. */) (Lisp_Object position, Lisp_Object menu) { init_raw_keybuf_count (); diff --git a/src/sfnt.c b/src/sfnt.c index 6d58798c599..7300915a504 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -4624,8 +4624,14 @@ main (int argc, char **argv) } if (meta) - fprintf (stderr, "meta table with count: %"PRIu32"\n", - meta->num_data_maps); + { + fprintf (stderr, "meta table with count: %"PRIu32"\n", + meta->num_data_maps); + + for (i = 0; i < meta->num_data_maps; ++i) + fprintf (stderr, " meta tag: %"PRIx32"\n", + meta->data_maps[i].tag); + } loca_long = NULL; loca_short = NULL; diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index 1b01a4d9be4..37fd81953f6 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -310,7 +310,7 @@ sfntfont_android_put_glyphs (struct glyph_string *s, int from, /* Allocate enough to hold text_rectangle.height, aligned to 8 bytes. Then fill it with the background. */ - stride = (text_rectangle.width * sizeof *buffer) + 7 & ~7; + stride = ((text_rectangle.width * sizeof *buffer) + 7) & ~7; GET_SCANLINE_BUFFER (buffer, text_rectangle.height, stride); memset (buffer, 0, text_rectangle.height * stride); @@ -546,6 +546,7 @@ init_sfntfont_android (void) { /* Make sure to pick the right Sans Serif font depending on what version of Android the device is running. */ +#if HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL if (android_get_device_api_level () >= 15) Vsfnt_default_family_alist = list3 (Fcons (build_string ("Monospace"), @@ -557,6 +558,7 @@ init_sfntfont_android (void) Fcons (build_string ("Sans Serif"), build_string ("Roboto"))); else +#endif Vsfnt_default_family_alist = list3 (Fcons (build_string ("Monospace"), build_string ("Droid Sans Mono")), diff --git a/src/sfntfont.c b/src/sfntfont.c index e2d18517fcb..87f93473ff3 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -21,6 +21,7 @@ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include +#include #include "lisp.h" @@ -61,6 +62,12 @@ struct sfnt_font_desc /* Designer (foundry) of the font. */ Lisp_Object designer; + /* Style tokens that could not be parsed. */ + Lisp_Object adstyle; + + /* List of design languages. */ + Lisp_Object languages; + /* Numeric width, weight, slant and spacing. */ int width, weight, slant, spacing; @@ -344,7 +351,9 @@ static struct sfnt_style_desc sfnt_width_descriptions[] = }; /* Figure out DESC->width, DESC->weight, DESC->slant and DESC->spacing - based on the style name passed as STYLE_NAME. */ + based on the style name passed as STYLE_NAME. + + Also append any unknown tokens to DESC->adstyle. */ static void sfnt_parse_style (Lisp_Object style_name, struct sfnt_font_desc *desc) @@ -419,16 +428,85 @@ sfnt_parse_style (Lisp_Object style_name, struct sfnt_font_desc *desc) } } - next: + /* This token is extraneous or was not recognized. Capitalize + the first letter and set it as the adstyle. */ - /* Break early if everything has been found. */ - if (desc->slant != 100 && desc->width != 100 && desc->weight != 80) - break; + if (strlen (single)) + { + if (islower (single[0])) + single[0] = toupper (single[0]); + + if (NILP (desc->adstyle)) + desc->adstyle = build_string (single); + else + desc->adstyle = CALLN (Fconcat, desc->adstyle, + build_string (" "), + build_string (single)); + } + next: continue; } } +/* Parse the list of design languages in META, a font metadata table, + and place the results in DESC->languages. Do nothing if there is + no such metadata. */ + +static void +sfnt_parse_languages (struct sfnt_meta_table *meta, + struct sfnt_font_desc *desc) +{ + char *data, *metadata, *tag; + struct sfnt_meta_data_map map; + char *saveptr; + + /* Look up the ``design languages'' metadata. This is a comma (and + possibly space) separated list of scripts that the font was + designed for. Here is an example of one such tag: + + zh-Hans,Jpan,Kore + + for a font that covers Simplified Chinese, along with Japanese + and Korean text. */ + + saveptr = NULL; + data = sfnt_find_metadata (meta, SFNT_META_DATA_TAG_DLNG, + &map); + + if (!data) + return; + + USE_SAFE_ALLOCA; + + /* Now copy metadata and add a trailing NULL byte. */ + + if (map.data_length >= SIZE_MAX) + memory_full (SIZE_MAX); + + metadata = SAFE_ALLOCA ((size_t) map.data_length + 1); + memcpy (metadata, data, map.data_length); + metadata[map.data_length] = '\0'; + + /* Loop through each script-language tag. Note that there may be + extra leading spaces. */ + while ((tag = strtok_r (metadata, ",", &saveptr))) + { + metadata = NULL; + + if (strstr (tag, "Hans") || strstr (tag, "Hant")) + desc->languages = Fcons (Qzh, desc->languages); + + if (strstr (tag, "Japn")) + desc->languages = Fcons (Qja, desc->languages); + + if (strstr (tag, "Kore")) + desc->languages = Fcons (Qko, desc->languages); + } + + SAFE_FREE (); +} + /* Enumerate the offset subtable SUBTABLES in the file FD, whose file name is FILE. OFFSET should be the offset of the subtable within the font file, and is recorded for future use. Value is 1 upon @@ -481,6 +559,10 @@ sfnt_enum_font_1 (int fd, const char *file, /* Parse the style. */ sfnt_parse_style (style, desc); + /* If the meta table exists, parse the list of design languages. */ + if (meta) + sfnt_parse_languages (meta, desc); + /* Figure out the spacing. Some fancy test like what Fontconfig does is probably in order but not really necessary. */ if (!NILP (Fstring_search (Fdowncase (family), @@ -990,11 +1072,10 @@ sfntfont_list_1 (struct sfnt_font_desc *desc, Lisp_Object spec) desc->family))) return false; - /* Check that no adstyle has been specified. That's a relic from - the Postscript era. */ + /* Check that the adstyle specified matches. */ tem = AREF (spec, FONT_ADSTYLE_INDEX); - if (!NILP (tem)) + if (!NILP (tem) && NILP (Fequal (tem, desc->adstyle))) return false; /* Check the style. */ @@ -1069,6 +1150,11 @@ sfntfont_list_1 (struct sfnt_font_desc *desc, Lisp_Object spec) } } + /* Now check that the language is supported. */ + tem = assq_no_quit (QClang, extra); + if (!NILP (tem) && NILP (Fmemq (tem, desc->languages))) + goto fail; + /* Set desc->subtable if cmap was specified. */ if (cmap) desc->subtable = subtable; @@ -2076,9 +2162,9 @@ sfntfont_text_extents (struct font *font, const unsigned int *code, if (pcm.descent > metrics->descent) metrics->descent = pcm.descent; - } - total_width += pcm.width; + total_width += pcm.width; + } } metrics->width = total_width; @@ -2239,6 +2325,9 @@ syms_of_sfntfont (void) DEFSYM (Qapple_roman, "apple-roman"); DEFSYM (Qjisx0208_1983_0, "jisx0208.1983-0"); DEFSYM (Qksc5601_1987_0, "ksc5601.1987-0"); + DEFSYM (Qzh, "zh"); + DEFSYM (Qja, "ja"); + DEFSYM (Qko, "ko"); /* Char-table purpose. */ DEFSYM (Qfont_lookup_cache, "font-lookup-cache"); @@ -2269,6 +2358,8 @@ mark_sfntfont (void) { mark_object (desc->family); mark_object (desc->style); + mark_object (desc->adstyle); + mark_object (desc->languages); mark_object (desc->char_cache); mark_object (desc->designer); } diff --git a/xcompile/lib/faccessat.c b/xcompile/lib/faccessat.c index 807e2669683..ac8977cfd65 100644 --- a/xcompile/lib/faccessat.c +++ b/xcompile/lib/faccessat.c @@ -43,11 +43,7 @@ orig_faccessat (int fd, char const *name, int mode, int flag) /* Write "unistd.h" here, not , otherwise OSF/1 5.1 DTK cc eliminates this include because of the preliminary #include above. */ -#ifdef __ANROID__ -#include -#else #include "unistd.h" -#endif #ifndef HAVE_ACCESS /* Mingw lacks access, but it also lacks real vs. effective ids, so diff --git a/xcompile/lib/getdelim.c b/xcompile/lib/getdelim.c new file mode 100644 index 00000000000..79ec3dd12a3 --- /dev/null +++ b/xcompile/lib/getdelim.c @@ -0,0 +1,147 @@ +/* getdelim.c --- Implementation of replacement getdelim function. + Copyright (C) 1994, 1996-1998, 2001, 2003, 2005-2023 Free Software + Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Ported from glibc by Simon Josefsson. */ + +/* Don't use __attribute__ __nonnull__ in this compilation unit. Otherwise gcc + optimizes away the lineptr == NULL || n == NULL || fp == NULL tests below. */ +#define _GL_ARG_NONNULL(params) + +#include + +#include + +#include +#include +#include +#include + +#ifndef SSIZE_MAX +# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) +#endif + +#if USE_UNLOCKED_IO +# include "unlocked-io.h" +# define getc_maybe_unlocked(fp) getc(fp) +#elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED +# undef flockfile +# undef funlockfile +# define flockfile(x) ((void) 0) +# define funlockfile(x) ((void) 0) +# define getc_maybe_unlocked(fp) getc(fp) +#else +# define getc_maybe_unlocked(fp) getc_unlocked(fp) +#endif + +static void +alloc_failed (void) +{ +#if defined _WIN32 && ! defined __CYGWIN__ + /* Avoid errno problem without using the realloc module; see: + https://lists.gnu.org/r/bug-gnulib/2016-08/msg00025.html */ + errno = ENOMEM; +#endif +} + +/* Read up to (and including) a DELIMITER from FP into *LINEPTR (and + NUL-terminate it). *LINEPTR is a pointer returned from malloc (or + NULL), pointing to *N characters of space. It is realloc'ed as + necessary. Returns the number of characters read (not including + the null terminator), or -1 on error or EOF. */ + +ssize_t +getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp) +{ + ssize_t result; + size_t cur_len = 0; + + if (lineptr == NULL || n == NULL || fp == NULL) + { + errno = EINVAL; + return -1; + } + + flockfile (fp); + + if (*lineptr == NULL || *n == 0) + { + char *new_lineptr; + *n = 120; + new_lineptr = (char *) realloc (*lineptr, *n); + if (new_lineptr == NULL) + { + alloc_failed (); + result = -1; + goto unlock_return; + } + *lineptr = new_lineptr; + } + + for (;;) + { + int i; + + i = getc_maybe_unlocked (fp); + if (i == EOF) + { + result = -1; + break; + } + + /* Make enough space for len+1 (for final NUL) bytes. */ + if (cur_len + 1 >= *n) + { + size_t needed_max = + SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX; + size_t needed = 2 * *n + 1; /* Be generous. */ + char *new_lineptr; + + if (needed_max < needed) + needed = needed_max; + if (cur_len + 1 >= needed) + { + result = -1; + errno = EOVERFLOW; + goto unlock_return; + } + + new_lineptr = (char *) realloc (*lineptr, needed); + if (new_lineptr == NULL) + { + alloc_failed (); + result = -1; + goto unlock_return; + } + + *lineptr = new_lineptr; + *n = needed; + } + + (*lineptr)[cur_len] = i; + cur_len++; + + if (i == delimiter) + break; + } + (*lineptr)[cur_len] = '\0'; + result = cur_len ? cur_len : result; + + unlock_return: + funlockfile (fp); /* doesn't set errno */ + + return result; +} diff --git a/xcompile/lib/getline.c b/xcompile/lib/getline.c new file mode 100644 index 00000000000..85f16ab8bac --- /dev/null +++ b/xcompile/lib/getline.c @@ -0,0 +1,27 @@ +/* getline.c --- Implementation of replacement getline function. + Copyright (C) 2005-2007, 2009-2023 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by Simon Josefsson. */ + +#include + +#include + +ssize_t +getline (char **lineptr, size_t *n, FILE *stream) +{ + return getdelim (lineptr, n, '\n', stream); +} diff --git a/xcompile/lib/gnulib.mk.in b/xcompile/lib/gnulib.mk.in index 2ebf187e867..66ba4a4adf9 100644 --- a/xcompile/lib/gnulib.mk.in +++ b/xcompile/lib/gnulib.mk.in @@ -109,6 +109,7 @@ # fsusage \ # fsync \ # futimens \ +# getline \ # getloadavg \ # getopt-gnu \ # getrandom \ @@ -172,11 +173,19 @@ MOSTLYCLEANFILES += core *.stackdump # Start of GNU Make output. +AAPT = @AAPT@ ALLOCA = @ALLOCA@ ALLOCA_H = @ALLOCA_H@ ALSA_CFLAGS = @ALSA_CFLAGS@ ALSA_LIBS = @ALSA_LIBS@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +ANDROID = @ANDROID@ +ANDROID_ABI = @ANDROID_ABI@ +ANDROID_CFLAGS = @ANDROID_CFLAGS@ +ANDROID_JAR = @ANDROID_JAR@ +ANDROID_LIBS = @ANDROID_LIBS@ +ANDROID_MIN_SDK = @ANDROID_MIN_SDK@ +ANDROID_OBJ = @ANDROID_OBJ@ APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@ AR = @AR@ ARFLAGS = @ARFLAGS@ @@ -216,6 +225,7 @@ CYGWIN_OBJ = @CYGWIN_OBJ@ C_SWITCH_MACHINE = @C_SWITCH_MACHINE@ C_SWITCH_SYSTEM = @C_SWITCH_SYSTEM@ C_SWITCH_X_SITE = @C_SWITCH_X_SITE@ +D8 = @D8@ DBUS_CFLAGS = @DBUS_CFLAGS@ DBUS_LIBS = @DBUS_LIBS@ DBUS_OBJ = @DBUS_OBJ@ @@ -278,8 +288,10 @@ GL_COND_OBJ_FSTATAT_CONDITION = @GL_COND_OBJ_FSTATAT_CONDITION@ GL_COND_OBJ_FSUSAGE_CONDITION = @GL_COND_OBJ_FSUSAGE_CONDITION@ GL_COND_OBJ_FSYNC_CONDITION = @GL_COND_OBJ_FSYNC_CONDITION@ GL_COND_OBJ_FUTIMENS_CONDITION = @GL_COND_OBJ_FUTIMENS_CONDITION@ +GL_COND_OBJ_GETDELIM_CONDITION = @GL_COND_OBJ_GETDELIM_CONDITION@ GL_COND_OBJ_GETDTABLESIZE_CONDITION = @GL_COND_OBJ_GETDTABLESIZE_CONDITION@ GL_COND_OBJ_GETGROUPS_CONDITION = @GL_COND_OBJ_GETGROUPS_CONDITION@ +GL_COND_OBJ_GETLINE_CONDITION = @GL_COND_OBJ_GETLINE_CONDITION@ GL_COND_OBJ_GETLOADAVG_CONDITION = @GL_COND_OBJ_GETLOADAVG_CONDITION@ GL_COND_OBJ_GETOPT_CONDITION = @GL_COND_OBJ_GETOPT_CONDITION@ GL_COND_OBJ_GETRANDOM_CONDITION = @GL_COND_OBJ_GETRANDOM_CONDITION@ @@ -883,6 +895,8 @@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INT32_MAX_LT_INTMAX_MAX = @INT32_MAX_LT_INTMAX_MAX@ INT64_MAX_EQ_LONG_MAX = @INT64_MAX_EQ_LONG_MAX@ +JARSIGNER = @JARSIGNER@ +JAVAC = @JAVAC@ JSON_CFLAGS = @JSON_CFLAGS@ JSON_LIBS = @JSON_LIBS@ JSON_OBJ = @JSON_OBJ@ @@ -949,6 +963,7 @@ LIB_PTHREAD = @LIB_PTHREAD@ LIB_PTHREAD_SIGMASK = @LIB_PTHREAD_SIGMASK@ LIB_TIMER_TIME = @LIB_TIMER_TIME@ LIB_WSOCK32 = @LIB_WSOCK32@ +LIB_XATTR = @LIB_XATTR@ LIMITS_H = @LIMITS_H@ LN_S_FILEONLY = @LN_S_FILEONLY@ LTLIBGMP = @LTLIBGMP@ @@ -1041,6 +1056,7 @@ PROFILING_CFLAGS = @PROFILING_CFLAGS@ PTHREAD_H_DEFINES_STRUCT_TIMESPEC = @PTHREAD_H_DEFINES_STRUCT_TIMESPEC@ PTHREAD_SIGMASK_LIB = @PTHREAD_SIGMASK_LIB@ PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@ +QCOPY_ACL_LIB = @QCOPY_ACL_LIB@ RALLOC_OBJ = @RALLOC_OBJ@ RANLIB = @RANLIB@ REPLACE_ACCESS = @REPLACE_ACCESS@ @@ -1209,6 +1225,7 @@ REPLACE_WCTOMB = @REPLACE_WCTOMB@ REPLACE_WRITE = @REPLACE_WRITE@ RSVG_CFLAGS = @RSVG_CFLAGS@ RSVG_LIBS = @RSVG_LIBS@ +SDK_BULD_TOOLS = @SDK_BULD_TOOLS@ SEPCHAR = @SEPCHAR@ SETFATTR = @SETFATTR@ SETTINGS_CFLAGS = @SETTINGS_CFLAGS@ @@ -1266,6 +1283,7 @@ XARGS_LIMIT = @XARGS_LIMIT@ XCB_LIBS = @XCB_LIBS@ XCOMPOSITE_CFLAGS = @XCOMPOSITE_CFLAGS@ XCOMPOSITE_LIBS = @XCOMPOSITE_LIBS@ +XCONFIGURE = @XCONFIGURE@ XCRUN = @XCRUN@ XDBE_CFLAGS = @XDBE_CFLAGS@ XDBE_LIBS = @XDBE_LIBS@ @@ -1290,6 +1308,7 @@ XSYNC_CFLAGS = @XSYNC_CFLAGS@ XSYNC_LIBS = @XSYNC_LIBS@ XWIDGETS_OBJ = @XWIDGETS_OBJ@ X_TOOLKIT_TYPE = @X_TOOLKIT_TYPE@ +ZIPALIGN = @ZIPALIGN@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_OBJC = @ac_ct_OBJC@ @@ -1335,6 +1354,7 @@ gl_GNULIB_ENABLED_e80bf6f757095d2e5fc94dafb8f8fc8b_CONDITION = @gl_GNULIB_ENABLE gl_GNULIB_ENABLED_ef455225c00f5049c808c2eda3e76866_CONDITION = @gl_GNULIB_ENABLED_ef455225c00f5049c808c2eda3e76866_CONDITION@ gl_GNULIB_ENABLED_euidaccess_CONDITION = @gl_GNULIB_ENABLED_euidaccess_CONDITION@ gl_GNULIB_ENABLED_fd38c7e463b54744b77b98aeafb4fa7c_CONDITION = @gl_GNULIB_ENABLED_fd38c7e463b54744b77b98aeafb4fa7c_CONDITION@ +gl_GNULIB_ENABLED_getdelim_CONDITION = @gl_GNULIB_ENABLED_getdelim_CONDITION@ gl_GNULIB_ENABLED_getdtablesize_CONDITION = @gl_GNULIB_ENABLED_getdtablesize_CONDITION@ gl_GNULIB_ENABLED_getgroups_CONDITION = @gl_GNULIB_ENABLED_getgroups_CONDITION@ gl_GNULIB_ENABLED_lchmod_CONDITION = @gl_GNULIB_ENABLED_lchmod_CONDITION@ @@ -2091,6 +2111,18 @@ gl_V_at = $(AM_V_GEN) endif ## end gnulib module gen-header +## begin gnulib module getdelim +ifeq (,$(OMIT_GNULIB_MODULE_getdelim)) + +ifneq (,$(gl_GNULIB_ENABLED_getdelim_CONDITION)) +ifneq (,$(GL_COND_OBJ_GETDELIM_CONDITION)) +libgnu_a_SOURCES += getdelim.c +endif + +endif +endif +## end gnulib module getdelim + ## begin gnulib module getdtablesize ifeq (,$(OMIT_GNULIB_MODULE_getdtablesize)) @@ -2115,6 +2147,16 @@ endif endif ## end gnulib module getgroups +## begin gnulib module getline +ifeq (,$(OMIT_GNULIB_MODULE_getline)) + +ifneq (,$(GL_COND_OBJ_GETLINE_CONDITION)) +libgnu_a_SOURCES += getline.c +endif + +endif +## end gnulib module getline + ## begin gnulib module getloadavg ifeq (,$(OMIT_GNULIB_MODULE_getloadavg)) diff --git a/xcompile/lib/qcopy-acl.c b/xcompile/lib/qcopy-acl.c index 883bcf7d588..0f4159b7fd9 100644 --- a/xcompile/lib/qcopy-acl.c +++ b/xcompile/lib/qcopy-acl.c @@ -23,6 +23,20 @@ #include "acl-internal.h" +#if USE_XATTR + +# include + +/* Returns 1 if NAME is the name of an extended attribute that is related + to permissions, i.e. ACLs. Returns 0 otherwise. */ + +static int +is_attr_permissions (const char *name, struct error_context *ctx) +{ + return attr_copy_action (name, ctx) == ATTR_ACTION_PERMISSIONS; +} + +#endif /* USE_XATTR */ /* Copy access control lists from one file to another. If SOURCE_DESC is a valid file descriptor, use file descriptor operations, else use @@ -39,13 +53,33 @@ int qcopy_acl (const char *src_name, int source_desc, const char *dst_name, int dest_desc, mode_t mode) { - struct permission_context ctx; int ret; +#ifdef USE_XATTR + /* in case no ACLs present and also to set higher mode bits + we chmod before setting ACLs as doing it after could overwrite them + (especially true for NFSv4, posix ACL has that ugly "mask" hack that + nobody understands) */ + ret = chmod_or_fchmod (dst_name, dest_desc, mode); + /* Rather than fiddling with acls one by one, we just copy the whole ACL xattrs + (Posix or NFSv4). Of course, that won't address ACLs conversion + (i.e. posix <-> nfs4) but we can't do it anyway, so for now, we don't care + Functions attr_copy_* return 0 in case we copied something OR nothing + to copy */ + if (ret == 0) + ret = source_desc <= 0 || dest_desc <= 0 + ? attr_copy_file (src_name, dst_name, is_attr_permissions, NULL) + : attr_copy_fd (src_name, source_desc, dst_name, dest_desc, + is_attr_permissions, NULL); +#else + /* no XATTR, so we proceed the old dusty way */ + struct permission_context ctx; + ret = get_permissions (src_name, source_desc, mode, &ctx); if (ret != 0) return -2; ret = set_permissions (&ctx, dst_name, dest_desc); free_permission_context (&ctx); +#endif return ret; } diff --git a/xcompile/lib/verify.h b/xcompile/lib/verify.h index 17d6e78c816..b63cb264321 100644 --- a/xcompile/lib/verify.h +++ b/xcompile/lib/verify.h @@ -258,7 +258,9 @@ template /* @assert.h omit start@ */ -#if 3 < __GNUC__ + (3 < __GNUC_MINOR__ + (4 <= __GNUC_PATCHLEVEL__)) +#if defined __clang_major__ && __clang_major__ < 5 +# define _GL_HAS_BUILTIN_TRAP 0 +#elif 3 < __GNUC__ + (3 < __GNUC_MINOR__ + (4 <= __GNUC_PATCHLEVEL__)) # define _GL_HAS_BUILTIN_TRAP 1 #elif defined __has_builtin # define _GL_HAS_BUILTIN_TRAP __has_builtin (__builtin_trap) @@ -266,7 +268,9 @@ template # define _GL_HAS_BUILTIN_TRAP 0 #endif -#if 4 < __GNUC__ + (5 <= __GNUC_MINOR__) +#if defined __clang_major__ && __clang_major__ < 5 +# define _GL_HAS_BUILTIN_UNREACHABLE 0 +#elif 4 < __GNUC__ + (5 <= __GNUC_MINOR__) # define _GL_HAS_BUILTIN_UNREACHABLE 1 #elif defined __has_builtin # define _GL_HAS_BUILTIN_UNREACHABLE __has_builtin (__builtin_unreachable) diff --git a/xcompile/malloc/dynarray-skeleton.c b/xcompile/malloc/dynarray-skeleton.c new file mode 100644 index 00000000000..580c278b7c5 --- /dev/null +++ b/xcompile/malloc/dynarray-skeleton.c @@ -0,0 +1,528 @@ +/* Type-safe arrays which grow dynamically. + Copyright (C) 2017-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* Pre-processor macros which act as parameters: + + DYNARRAY_STRUCT + The struct tag of dynamic array to be defined. + DYNARRAY_ELEMENT + The type name of the element type. Elements are copied + as if by memcpy, and can change address as the dynamic + array grows. + DYNARRAY_PREFIX + The prefix of the functions which are defined. + + The following parameters are optional: + + DYNARRAY_ELEMENT_FREE + DYNARRAY_ELEMENT_FREE (E) is evaluated to deallocate the + contents of elements. E is of type DYNARRAY_ELEMENT *. + DYNARRAY_ELEMENT_INIT + DYNARRAY_ELEMENT_INIT (E) is evaluated to initialize a new + element. E is of type DYNARRAY_ELEMENT *. + If DYNARRAY_ELEMENT_FREE but not DYNARRAY_ELEMENT_INIT is + defined, new elements are automatically zero-initialized. + Otherwise, new elements have undefined contents. + DYNARRAY_INITIAL_SIZE + The size of the statically allocated array (default: + at least 2, more elements if they fit into 128 bytes). + Must be a preprocessor constant. If DYNARRAY_INITIAL_SIZE is 0, + there is no statically allocated array at, and all non-empty + arrays are heap-allocated. + DYNARRAY_FINAL_TYPE + The name of the type which holds the final array. If not + defined, is PREFIX##finalize not provided. DYNARRAY_FINAL_TYPE + must be a struct type, with members of type DYNARRAY_ELEMENT and + size_t at the start (in this order). + + These macros are undefined after this header file has been + included. + + The following types are provided (their members are private to the + dynarray implementation): + + struct DYNARRAY_STRUCT + + The following functions are provided: + + void DYNARRAY_PREFIX##init (struct DYNARRAY_STRUCT *); + void DYNARRAY_PREFIX##free (struct DYNARRAY_STRUCT *); + bool DYNARRAY_PREFIX##has_failed (const struct DYNARRAY_STRUCT *); + void DYNARRAY_PREFIX##mark_failed (struct DYNARRAY_STRUCT *); + size_t DYNARRAY_PREFIX##size (const struct DYNARRAY_STRUCT *); + DYNARRAY_ELEMENT *DYNARRAY_PREFIX##begin (const struct DYNARRAY_STRUCT *); + DYNARRAY_ELEMENT *DYNARRAY_PREFIX##end (const struct DYNARRAY_STRUCT *); + DYNARRAY_ELEMENT *DYNARRAY_PREFIX##at (struct DYNARRAY_STRUCT *, size_t); + void DYNARRAY_PREFIX##add (struct DYNARRAY_STRUCT *, DYNARRAY_ELEMENT); + DYNARRAY_ELEMENT *DYNARRAY_PREFIX##emplace (struct DYNARRAY_STRUCT *); + bool DYNARRAY_PREFIX##resize (struct DYNARRAY_STRUCT *, size_t); + void DYNARRAY_PREFIX##remove_last (struct DYNARRAY_STRUCT *); + void DYNARRAY_PREFIX##clear (struct DYNARRAY_STRUCT *); + + The following functions are provided are provided if the + prerequisites are met: + + bool DYNARRAY_PREFIX##finalize (struct DYNARRAY_STRUCT *, + DYNARRAY_FINAL_TYPE *); + (if DYNARRAY_FINAL_TYPE is defined) + DYNARRAY_ELEMENT *DYNARRAY_PREFIX##finalize (struct DYNARRAY_STRUCT *, + size_t *); + (if DYNARRAY_FINAL_TYPE is not defined) +*/ + +#include + +#include +#include +#include + +#ifndef DYNARRAY_STRUCT +# error "DYNARRAY_STRUCT must be defined" +#endif + +#ifndef DYNARRAY_ELEMENT +# error "DYNARRAY_ELEMENT must be defined" +#endif + +#ifndef DYNARRAY_PREFIX +# error "DYNARRAY_PREFIX must be defined" +#endif + +#ifdef DYNARRAY_INITIAL_SIZE +# if DYNARRAY_INITIAL_SIZE < 0 +# error "DYNARRAY_INITIAL_SIZE must be non-negative" +# endif +# if DYNARRAY_INITIAL_SIZE > 0 +# define DYNARRAY_HAVE_SCRATCH 1 +# else +# define DYNARRAY_HAVE_SCRATCH 0 +# endif +#else +/* Provide a reasonable default which limits the size of + DYNARRAY_STRUCT. */ +# define DYNARRAY_INITIAL_SIZE \ + (sizeof (DYNARRAY_ELEMENT) > 64 ? 2 : 128 / sizeof (DYNARRAY_ELEMENT)) +# define DYNARRAY_HAVE_SCRATCH 1 +#endif + +/* Public type definitions. */ + +/* All fields of this struct are private to the implementation. */ +struct DYNARRAY_STRUCT +{ + union + { + struct dynarray_header dynarray_abstract; + struct + { + /* These fields must match struct dynarray_header. */ + size_t used; + size_t allocated; + DYNARRAY_ELEMENT *array; + } dynarray_header; + } u; + +#if DYNARRAY_HAVE_SCRATCH + /* Initial inline allocation. */ + DYNARRAY_ELEMENT scratch[DYNARRAY_INITIAL_SIZE]; +#endif +}; + +/* Internal use only: Helper macros. */ + +/* Ensure macro-expansion of DYNARRAY_PREFIX. */ +#define DYNARRAY_CONCAT0(prefix, name) prefix##name +#define DYNARRAY_CONCAT1(prefix, name) DYNARRAY_CONCAT0(prefix, name) +#define DYNARRAY_NAME(name) DYNARRAY_CONCAT1(DYNARRAY_PREFIX, name) + +/* Use DYNARRAY_FREE instead of DYNARRAY_NAME (free), + so that Gnulib does not change 'free' to 'rpl_free'. */ +#define DYNARRAY_FREE DYNARRAY_CONCAT1 (DYNARRAY_NAME (f), ree) + +/* Address of the scratch buffer if any. */ +#if DYNARRAY_HAVE_SCRATCH +# define DYNARRAY_SCRATCH(list) (list)->scratch +#else +# define DYNARRAY_SCRATCH(list) NULL +#endif + +/* Internal use only: Helper functions. */ + +/* Internal function. Call DYNARRAY_ELEMENT_FREE with the array + elements. Name mangling needed due to the DYNARRAY_ELEMENT_FREE + macro expansion. */ +static inline void +DYNARRAY_NAME (free__elements__) (DYNARRAY_ELEMENT *__dynarray_array, + size_t __dynarray_used) +{ +#ifdef DYNARRAY_ELEMENT_FREE + for (size_t __dynarray_i = 0; __dynarray_i < __dynarray_used; ++__dynarray_i) + DYNARRAY_ELEMENT_FREE (&__dynarray_array[__dynarray_i]); +#endif /* DYNARRAY_ELEMENT_FREE */ +} + +/* Internal function. Free the non-scratch array allocation. */ +static inline void +DYNARRAY_NAME (free__array__) (struct DYNARRAY_STRUCT *list) +{ +#if DYNARRAY_HAVE_SCRATCH + if (list->u.dynarray_header.array != list->scratch) + free (list->u.dynarray_header.array); +#else + free (list->u.dynarray_header.array); +#endif +} + +/* Public functions. */ + +/* Initialize a dynamic array object. This must be called before any + use of the object. */ +__attribute_nonnull__ ((1)) +static void +DYNARRAY_NAME (init) (struct DYNARRAY_STRUCT *list) +{ + list->u.dynarray_header.used = 0; + list->u.dynarray_header.allocated = DYNARRAY_INITIAL_SIZE; + list->u.dynarray_header.array = DYNARRAY_SCRATCH (list); +} + +/* Deallocate the dynamic array and its elements. */ +__attribute_maybe_unused__ __attribute_nonnull__ ((1)) +static void +DYNARRAY_FREE (struct DYNARRAY_STRUCT *list) +{ + DYNARRAY_NAME (free__elements__) + (list->u.dynarray_header.array, list->u.dynarray_header.used); + DYNARRAY_NAME (free__array__) (list); + DYNARRAY_NAME (init) (list); +} + +/* Return true if the dynamic array is in an error state. */ +__attribute_nonnull__ ((1)) +static inline bool +DYNARRAY_NAME (has_failed) (const struct DYNARRAY_STRUCT *list) +{ + return list->u.dynarray_header.allocated == __dynarray_error_marker (); +} + +/* Mark the dynamic array as failed. All elements are deallocated as + a side effect. */ +__attribute_nonnull__ ((1)) +static void +DYNARRAY_NAME (mark_failed) (struct DYNARRAY_STRUCT *list) +{ + DYNARRAY_NAME (free__elements__) + (list->u.dynarray_header.array, list->u.dynarray_header.used); + DYNARRAY_NAME (free__array__) (list); + list->u.dynarray_header.array = DYNARRAY_SCRATCH (list); + list->u.dynarray_header.used = 0; + list->u.dynarray_header.allocated = __dynarray_error_marker (); +} + +/* Return the number of elements which have been added to the dynamic + array. */ +__attribute_nonnull__ ((1)) +static inline size_t +DYNARRAY_NAME (size) (const struct DYNARRAY_STRUCT *list) +{ + return list->u.dynarray_header.used; +} + +/* Return a pointer to the array element at INDEX. Terminate the + process if INDEX is out of bounds. */ +__attribute_nonnull__ ((1)) +static inline DYNARRAY_ELEMENT * +DYNARRAY_NAME (at) (struct DYNARRAY_STRUCT *list, size_t index) +{ + if (__glibc_unlikely (index >= DYNARRAY_NAME (size) (list))) + __libc_dynarray_at_failure (DYNARRAY_NAME (size) (list), index); + return list->u.dynarray_header.array + index; +} + +/* Return a pointer to the first array element, if any. For a + zero-length array, the pointer can be NULL even though the dynamic + array has not entered the failure state. */ +__attribute_nonnull__ ((1)) +static inline DYNARRAY_ELEMENT * +DYNARRAY_NAME (begin) (struct DYNARRAY_STRUCT *list) +{ + return list->u.dynarray_header.array; +} + +/* Return a pointer one element past the last array element. For a + zero-length array, the pointer can be NULL even though the dynamic + array has not entered the failure state. */ +__attribute_nonnull__ ((1)) +static inline DYNARRAY_ELEMENT * +DYNARRAY_NAME (end) (struct DYNARRAY_STRUCT *list) +{ + return list->u.dynarray_header.array + list->u.dynarray_header.used; +} + +/* Internal function. Slow path for the add function below. */ +static void +DYNARRAY_NAME (add__) (struct DYNARRAY_STRUCT *list, DYNARRAY_ELEMENT item) +{ + if (__glibc_unlikely + (!__libc_dynarray_emplace_enlarge (&list->u.dynarray_abstract, + DYNARRAY_SCRATCH (list), + sizeof (DYNARRAY_ELEMENT)))) + { + DYNARRAY_NAME (mark_failed) (list); + return; + } + + /* Copy the new element and increase the array length. */ + list->u.dynarray_header.array[list->u.dynarray_header.used++] = item; +} + +/* Add ITEM at the end of the array, enlarging it by one element. + Mark *LIST as failed if the dynamic array allocation size cannot be + increased. */ +__attribute_nonnull__ ((1)) +static inline void +DYNARRAY_NAME (add) (struct DYNARRAY_STRUCT *list, DYNARRAY_ELEMENT item) +{ + /* Do nothing in case of previous error. */ + if (DYNARRAY_NAME (has_failed) (list)) + return; + + /* Enlarge the array if necessary. */ + if (__glibc_unlikely (list->u.dynarray_header.used + == list->u.dynarray_header.allocated)) + { + DYNARRAY_NAME (add__) (list, item); + return; + } + + /* Copy the new element and increase the array length. */ + list->u.dynarray_header.array[list->u.dynarray_header.used++] = item; +} + +/* Internal function. Building block for the emplace functions below. + Assumes space for one more element in *LIST. */ +static inline DYNARRAY_ELEMENT * +DYNARRAY_NAME (emplace__tail__) (struct DYNARRAY_STRUCT *list) +{ + DYNARRAY_ELEMENT *result + = &list->u.dynarray_header.array[list->u.dynarray_header.used]; + ++list->u.dynarray_header.used; +#if defined (DYNARRAY_ELEMENT_INIT) + DYNARRAY_ELEMENT_INIT (result); +#elif defined (DYNARRAY_ELEMENT_FREE) + memset (result, 0, sizeof (*result)); +#endif + return result; +} + +/* Internal function. Slow path for the emplace function below. */ +static DYNARRAY_ELEMENT * +DYNARRAY_NAME (emplace__) (struct DYNARRAY_STRUCT *list) +{ + if (__glibc_unlikely + (!__libc_dynarray_emplace_enlarge (&list->u.dynarray_abstract, + DYNARRAY_SCRATCH (list), + sizeof (DYNARRAY_ELEMENT)))) + { + DYNARRAY_NAME (mark_failed) (list); + return NULL; + } + return DYNARRAY_NAME (emplace__tail__) (list); +} + +/* Allocate a place for a new element in *LIST and return a pointer to + it. The pointer can be NULL if the dynamic array cannot be + enlarged due to a memory allocation failure. */ +__attribute_maybe_unused__ __attribute_warn_unused_result__ +__attribute_nonnull__ ((1)) +static +/* Avoid inlining with the larger initialization code. */ +#if !(defined (DYNARRAY_ELEMENT_INIT) || defined (DYNARRAY_ELEMENT_FREE)) +inline +#endif +DYNARRAY_ELEMENT * +DYNARRAY_NAME (emplace) (struct DYNARRAY_STRUCT *list) +{ + /* Do nothing in case of previous error. */ + if (DYNARRAY_NAME (has_failed) (list)) + return NULL; + + /* Enlarge the array if necessary. */ + if (__glibc_unlikely (list->u.dynarray_header.used + == list->u.dynarray_header.allocated)) + return (DYNARRAY_NAME (emplace__) (list)); + return DYNARRAY_NAME (emplace__tail__) (list); +} + +/* Change the size of *LIST to SIZE. If SIZE is larger than the + existing size, new elements are added (which can be initialized). + Otherwise, the list is truncated, and elements are freed. Return + false on memory allocation failure (and mark *LIST as failed). */ +__attribute_maybe_unused__ __attribute_nonnull__ ((1)) +static bool +DYNARRAY_NAME (resize) (struct DYNARRAY_STRUCT *list, size_t size) +{ + if (size > list->u.dynarray_header.used) + { + bool ok; +#if defined (DYNARRAY_ELEMENT_INIT) + /* The new elements have to be initialized. */ + size_t old_size = list->u.dynarray_header.used; + ok = __libc_dynarray_resize (&list->u.dynarray_abstract, + size, DYNARRAY_SCRATCH (list), + sizeof (DYNARRAY_ELEMENT)); + if (ok) + for (size_t i = old_size; i < size; ++i) + { + DYNARRAY_ELEMENT_INIT (&list->u.dynarray_header.array[i]); + } +#elif defined (DYNARRAY_ELEMENT_FREE) + /* Zero initialization is needed so that the elements can be + safely freed. */ + ok = __libc_dynarray_resize_clear + (&list->u.dynarray_abstract, size, + DYNARRAY_SCRATCH (list), sizeof (DYNARRAY_ELEMENT)); +#else + ok = __libc_dynarray_resize (&list->u.dynarray_abstract, + size, DYNARRAY_SCRATCH (list), + sizeof (DYNARRAY_ELEMENT)); +#endif + if (__glibc_unlikely (!ok)) + DYNARRAY_NAME (mark_failed) (list); + return ok; + } + else + { + /* The list has shrunk in size. Free the removed elements. */ + DYNARRAY_NAME (free__elements__) + (list->u.dynarray_header.array + size, + list->u.dynarray_header.used - size); + list->u.dynarray_header.used = size; + return true; + } +} + +/* Remove the last element of LIST if it is present. */ +__attribute_maybe_unused__ __attribute_nonnull__ ((1)) +static void +DYNARRAY_NAME (remove_last) (struct DYNARRAY_STRUCT *list) +{ + /* used > 0 implies that the array is the non-failed state. */ + if (list->u.dynarray_header.used > 0) + { + size_t new_length = list->u.dynarray_header.used - 1; +#ifdef DYNARRAY_ELEMENT_FREE + DYNARRAY_ELEMENT_FREE (&list->u.dynarray_header.array[new_length]); +#endif + list->u.dynarray_header.used = new_length; + } +} + +/* Remove all elements from the list. The elements are freed, but the + list itself is not. */ +__attribute_maybe_unused__ __attribute_nonnull__ ((1)) +static void +DYNARRAY_NAME (clear) (struct DYNARRAY_STRUCT *list) +{ + /* free__elements__ does nothing if the list is in the failed + state. */ + DYNARRAY_NAME (free__elements__) + (list->u.dynarray_header.array, list->u.dynarray_header.used); + list->u.dynarray_header.used = 0; +} + +#ifdef DYNARRAY_FINAL_TYPE +/* Transfer the dynamic array to a permanent location at *RESULT. + Returns true on success on false on allocation failure. In either + case, *LIST is re-initialized and can be reused. A NULL pointer is + stored in *RESULT if LIST refers to an empty list. On success, the + pointer in *RESULT is heap-allocated and must be deallocated using + free. */ +__attribute_maybe_unused__ __attribute_warn_unused_result__ +__attribute_nonnull__ ((1, 2)) +static bool +DYNARRAY_NAME (finalize) (struct DYNARRAY_STRUCT *list, + DYNARRAY_FINAL_TYPE *result) +{ + struct dynarray_finalize_result res; + if (__libc_dynarray_finalize (&list->u.dynarray_abstract, + DYNARRAY_SCRATCH (list), + sizeof (DYNARRAY_ELEMENT), &res)) + { + /* On success, the result owns all the data. */ + DYNARRAY_NAME (init) (list); + *result = (DYNARRAY_FINAL_TYPE) { res.array, res.length }; + return true; + } + else + { + /* On error, we need to free all data. */ + DYNARRAY_FREE (list); + errno = ENOMEM; + return false; + } +} +#else /* !DYNARRAY_FINAL_TYPE */ +/* Transfer the dynamic array to a heap-allocated array and return a + pointer to it. The pointer is NULL if memory allocation fails, or + if the array is empty, so this function should be used only for + arrays which are known not be empty (usually because they always + have a sentinel at the end). If LENGTHP is not NULL, the array + length is written to *LENGTHP. *LIST is re-initialized and can be + reused. */ +__attribute_maybe_unused__ __attribute_warn_unused_result__ +__attribute_nonnull__ ((1)) +static DYNARRAY_ELEMENT * +DYNARRAY_NAME (finalize) (struct DYNARRAY_STRUCT *list, size_t *lengthp) +{ + struct dynarray_finalize_result res; + if (__libc_dynarray_finalize (&list->u.dynarray_abstract, + DYNARRAY_SCRATCH (list), + sizeof (DYNARRAY_ELEMENT), &res)) + { + /* On success, the result owns all the data. */ + DYNARRAY_NAME (init) (list); + if (lengthp != NULL) + *lengthp = res.length; + return res.array; + } + else + { + /* On error, we need to free all data. */ + DYNARRAY_FREE (list); + errno = ENOMEM; + return NULL; + } +} +#endif /* !DYNARRAY_FINAL_TYPE */ + +/* Undo macro definitions. */ + +#undef DYNARRAY_CONCAT0 +#undef DYNARRAY_CONCAT1 +#undef DYNARRAY_NAME +#undef DYNARRAY_SCRATCH +#undef DYNARRAY_HAVE_SCRATCH + +#undef DYNARRAY_STRUCT +#undef DYNARRAY_ELEMENT +#undef DYNARRAY_PREFIX +#undef DYNARRAY_ELEMENT_FREE +#undef DYNARRAY_ELEMENT_INIT +#undef DYNARRAY_INITIAL_SIZE +#undef DYNARRAY_FINAL_TYPE diff --git a/xcompile/malloc/dynarray.h b/xcompile/malloc/dynarray.h new file mode 100644 index 00000000000..a9a3b0859c1 --- /dev/null +++ b/xcompile/malloc/dynarray.h @@ -0,0 +1,177 @@ +/* Type-safe arrays which grow dynamically. Shared definitions. + Copyright (C) 2017-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* To use the dynarray facility, you need to include + and define the parameter macros + documented in that file. + + A minimal example which provides a growing list of integers can be + defined like this: + + struct int_array + { + // Pointer to result array followed by its length, + // as required by DYNARRAY_FINAL_TYPE. + int *array; + size_t length; + }; + + #define DYNARRAY_STRUCT dynarray_int + #define DYNARRAY_ELEMENT int + #define DYNARRAY_PREFIX dynarray_int_ + #define DYNARRAY_FINAL_TYPE struct int_array + #include + + To create a three-element array with elements 1, 2, 3, use this + code: + + struct dynarray_int dyn; + dynarray_int_init (&dyn); + for (int i = 1; i <= 3; ++i) + { + int *place = dynarray_int_emplace (&dyn); + assert (place != NULL); + *place = i; + } + struct int_array result; + bool ok = dynarray_int_finalize (&dyn, &result); + assert (ok); + assert (result.length == 3); + assert (result.array[0] == 1); + assert (result.array[1] == 2); + assert (result.array[2] == 3); + free (result.array); + + If the elements contain resources which must be freed, define + DYNARRAY_ELEMENT_FREE appropriately, like this: + + struct str_array + { + char **array; + size_t length; + }; + + #define DYNARRAY_STRUCT dynarray_str + #define DYNARRAY_ELEMENT char * + #define DYNARRAY_ELEMENT_FREE(ptr) free (*ptr) + #define DYNARRAY_PREFIX dynarray_str_ + #define DYNARRAY_FINAL_TYPE struct str_array + #include + + Compared to scratch buffers, dynamic arrays have the following + features: + + - They have an element type, and are not just an untyped buffer of + bytes. + + - When growing, previously stored elements are preserved. (It is + expected that scratch_buffer_grow_preserve and + scratch_buffer_set_array_size eventually go away because all + current users are moved to dynamic arrays.) + + - Scratch buffers have a more aggressive growth policy because + growing them typically means a retry of an operation (across an + NSS service module boundary), which is expensive. + + - For the same reason, scratch buffers have a much larger initial + stack allocation. */ + +#ifndef _DYNARRAY_H +#define _DYNARRAY_H + +#include +#include + +struct dynarray_header +{ + size_t used; + size_t allocated; + void *array; +}; + +/* Marker used in the allocated member to indicate that an error was + encountered. */ +static inline size_t +__dynarray_error_marker (void) +{ + return -1; +} + +/* Internal function. See the has_failed function in + dynarray-skeleton.c. */ +static inline bool +__dynarray_error (struct dynarray_header *list) +{ + return list->allocated == __dynarray_error_marker (); +} + +/* Internal function. Enlarge the dynamically allocated area of the + array to make room for one more element. SCRATCH is a pointer to + the scratch area (which is not heap-allocated and must not be + freed). ELEMENT_SIZE is the size, in bytes, of one element. + Return false on failure, true on success. */ +bool __libc_dynarray_emplace_enlarge (struct dynarray_header *, + void *scratch, size_t element_size); + +/* Internal function. Enlarge the dynamically allocated area of the + array to make room for at least SIZE elements (which must be larger + than the existing used part of the dynamic array). SCRATCH is a + pointer to the scratch area (which is not heap-allocated and must + not be freed). ELEMENT_SIZE is the size, in bytes, of one element. + Return false on failure, true on success. */ +bool __libc_dynarray_resize (struct dynarray_header *, size_t size, + void *scratch, size_t element_size); + +/* Internal function. Like __libc_dynarray_resize, but clear the new + part of the dynamic array. */ +bool __libc_dynarray_resize_clear (struct dynarray_header *, size_t size, + void *scratch, size_t element_size); + +/* Internal type. */ +struct dynarray_finalize_result +{ + void *array; + size_t length; +}; + +/* Internal function. Copy the dynamically-allocated area to an + explicitly-sized heap allocation. SCRATCH is a pointer to the + embedded scratch space. ELEMENT_SIZE is the size, in bytes, of the + element type. On success, true is returned, and pointer and length + are written to *RESULT. On failure, false is returned. The caller + has to take care of some of the memory management; this function is + expected to be called from dynarray-skeleton.c. */ +bool __libc_dynarray_finalize (struct dynarray_header *list, void *scratch, + size_t element_size, + struct dynarray_finalize_result *result); + + +/* Internal function. Terminate the process after an index error. + SIZE is the number of elements of the dynamic array. INDEX is the + lookup index which triggered the failure. */ +_Noreturn void __libc_dynarray_at_failure (size_t size, size_t index); + +#ifndef _ISOMAC +libc_hidden_proto (__libc_dynarray_emplace_enlarge) +libc_hidden_proto (__libc_dynarray_resize) +libc_hidden_proto (__libc_dynarray_resize_clear) +libc_hidden_proto (__libc_dynarray_finalize) +libc_hidden_proto (__libc_dynarray_at_failure) +#endif + +#endif /* _DYNARRAY_H */ diff --git a/xcompile/malloc/dynarray_at_failure.c b/xcompile/malloc/dynarray_at_failure.c new file mode 100644 index 00000000000..ebc9310982c --- /dev/null +++ b/xcompile/malloc/dynarray_at_failure.c @@ -0,0 +1,40 @@ +/* Report an dynamic array index out of bounds condition. + Copyright (C) 2017-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +# include +#endif + +#include +#include + +void +__libc_dynarray_at_failure (size_t size, size_t index) +{ +#ifdef _LIBC + char buf[200]; + __snprintf (buf, sizeof (buf), "Fatal glibc error: " + "array index %zu not less than array length %zu\n", + index, size); + __libc_fatal (buf); +#else + abort (); +#endif +} +libc_hidden_def (__libc_dynarray_at_failure) diff --git a/xcompile/malloc/dynarray_emplace_enlarge.c b/xcompile/malloc/dynarray_emplace_enlarge.c new file mode 100644 index 00000000000..7da539316c1 --- /dev/null +++ b/xcompile/malloc/dynarray_emplace_enlarge.c @@ -0,0 +1,77 @@ +/* Increase the size of a dynamic array in preparation of an emplace operation. + Copyright (C) 2017-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +#endif + +#include +#include +#include +#include +#include + +bool +__libc_dynarray_emplace_enlarge (struct dynarray_header *list, + void *scratch, size_t element_size) +{ + size_t new_allocated; + if (list->allocated == 0) + { + /* No scratch buffer provided. Choose a reasonable default + size. */ + if (element_size < 4) + new_allocated = 16; + else if (element_size < 8) + new_allocated = 8; + else + new_allocated = 4; + } + else + /* Increase the allocated size, using an exponential growth + policy. */ + { + new_allocated = list->allocated + list->allocated / 2 + 1; + if (new_allocated <= list->allocated) + { + /* Overflow. */ + __set_errno (ENOMEM); + return false; + } + } + + size_t new_size; + if (INT_MULTIPLY_WRAPV (new_allocated, element_size, &new_size)) + return false; + void *new_array; + if (list->array == scratch) + { + /* The previous array was not heap-allocated. */ + new_array = malloc (new_size); + if (new_array != NULL && list->array != NULL) + memcpy (new_array, list->array, list->used * element_size); + } + else + new_array = realloc (list->array, new_size); + if (new_array == NULL) + return false; + list->array = new_array; + list->allocated = new_allocated; + return true; +} +libc_hidden_def (__libc_dynarray_emplace_enlarge) diff --git a/xcompile/malloc/dynarray_finalize.c b/xcompile/malloc/dynarray_finalize.c new file mode 100644 index 00000000000..673595a5fad --- /dev/null +++ b/xcompile/malloc/dynarray_finalize.c @@ -0,0 +1,66 @@ +/* Copy the dynamically-allocated area to an explicitly-sized heap allocation. + Copyright (C) 2017-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +#endif + +#include +#include +#include + +bool +__libc_dynarray_finalize (struct dynarray_header *list, + void *scratch, size_t element_size, + struct dynarray_finalize_result *result) +{ + if (__dynarray_error (list)) + /* The caller will reported the deferred error. */ + return false; + + size_t used = list->used; + + /* Empty list. */ + if (used == 0) + { + /* An empty list could still be backed by a heap-allocated + array. Free it if necessary. */ + if (list->array != scratch) + free (list->array); + *result = (struct dynarray_finalize_result) { NULL, 0 }; + return true; + } + + size_t allocation_size = used * element_size; + void *heap_array = malloc (allocation_size); + if (heap_array != NULL) + { + /* The new array takes ownership of the strings. */ + if (list->array != NULL) + memcpy (heap_array, list->array, allocation_size); + if (list->array != scratch) + free (list->array); + *result = (struct dynarray_finalize_result) + { .array = heap_array, .length = used }; + return true; + } + else + /* The caller will perform the freeing operation. */ + return false; +} +libc_hidden_def (__libc_dynarray_finalize) diff --git a/xcompile/malloc/dynarray_resize.c b/xcompile/malloc/dynarray_resize.c new file mode 100644 index 00000000000..7ecd4de63b9 --- /dev/null +++ b/xcompile/malloc/dynarray_resize.c @@ -0,0 +1,68 @@ +/* Increase the size of a dynamic array. + Copyright (C) 2017-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +#endif + +#include +#include +#include +#include +#include + +bool +__libc_dynarray_resize (struct dynarray_header *list, size_t size, + void *scratch, size_t element_size) +{ + /* The existing allocation provides sufficient room. */ + if (size <= list->allocated) + { + list->used = size; + return true; + } + + /* Otherwise, use size as the new allocation size. The caller is + expected to provide the final size of the array, so there is no + over-allocation here. */ + + size_t new_size_bytes; + if (INT_MULTIPLY_WRAPV (size, element_size, &new_size_bytes)) + { + /* Overflow. */ + __set_errno (ENOMEM); + return false; + } + void *new_array; + if (list->array == scratch) + { + /* The previous array was not heap-allocated. */ + new_array = malloc (new_size_bytes); + if (new_array != NULL && list->array != NULL) + memcpy (new_array, list->array, list->used * element_size); + } + else + new_array = realloc (list->array, new_size_bytes); + if (new_array == NULL) + return false; + list->array = new_array; + list->allocated = size; + list->used = size; + return true; +} +libc_hidden_def (__libc_dynarray_resize) diff --git a/xcompile/malloc/dynarray_resize_clear.c b/xcompile/malloc/dynarray_resize_clear.c new file mode 100644 index 00000000000..bb23c522a14 --- /dev/null +++ b/xcompile/malloc/dynarray_resize_clear.c @@ -0,0 +1,39 @@ +/* Increase the size of a dynamic array and clear the new part. + Copyright (C) 2017-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +#endif + +#include +#include + +bool +__libc_dynarray_resize_clear (struct dynarray_header *list, size_t size, + void *scratch, size_t element_size) +{ + size_t old_size = list->used; + if (!__libc_dynarray_resize (list, size, scratch, element_size)) + return false; + /* __libc_dynarray_resize already checked for overflow. */ + char *array = list->array; + memset (array + (old_size * element_size), 0, + (size - old_size) * element_size); + return true; +} +libc_hidden_def (__libc_dynarray_resize_clear) diff --git a/xcompile/malloc/scratch_buffer.h b/xcompile/malloc/scratch_buffer.h new file mode 100644 index 00000000000..33fd2b29cd5 --- /dev/null +++ b/xcompile/malloc/scratch_buffer.h @@ -0,0 +1,135 @@ +/* Variable-sized buffer with on-stack default allocation. + Copyright (C) 2015-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _SCRATCH_BUFFER_H +#define _SCRATCH_BUFFER_H + +/* Scratch buffers with a default stack allocation and fallback to + heap allocation. It is expected that this function is used in this + way: + + struct scratch_buffer tmpbuf; + scratch_buffer_init (&tmpbuf); + + while (!function_that_uses_buffer (tmpbuf.data, tmpbuf.length)) + if (!scratch_buffer_grow (&tmpbuf)) + return -1; + + scratch_buffer_free (&tmpbuf); + return 0; + + The allocation functions (scratch_buffer_grow, + scratch_buffer_grow_preserve, scratch_buffer_set_array_size) make + sure that the heap allocation, if any, is freed, so that the code + above does not have a memory leak. The buffer still remains in a + state that can be deallocated using scratch_buffer_free, so a loop + like this is valid as well: + + struct scratch_buffer tmpbuf; + scratch_buffer_init (&tmpbuf); + + while (!function_that_uses_buffer (tmpbuf.data, tmpbuf.length)) + if (!scratch_buffer_grow (&tmpbuf)) + break; + + scratch_buffer_free (&tmpbuf); + + scratch_buffer_grow and scratch_buffer_grow_preserve are guaranteed + to grow the buffer by at least 512 bytes. This means that when + using the scratch buffer as a backing store for a non-character + array whose element size, in bytes, is 512 or smaller, the scratch + buffer only has to grow once to make room for at least one more + element. +*/ + +#include +#include +#include + +/* Scratch buffer. Must be initialized with scratch_buffer_init + before its use. */ +struct scratch_buffer { + void *data; /* Pointer to the beginning of the scratch area. */ + size_t length; /* Allocated space at the data pointer, in bytes. */ + union { max_align_t __align; char __c[1024]; } __space; +}; + +/* Initializes *BUFFER so that BUFFER->data points to BUFFER->__space + and BUFFER->length reflects the available space. */ +static inline void +scratch_buffer_init (struct scratch_buffer *buffer) +{ + buffer->data = buffer->__space.__c; + buffer->length = sizeof (buffer->__space); +} + +/* Deallocates *BUFFER (if it was heap-allocated). */ +static inline void +scratch_buffer_free (struct scratch_buffer *buffer) +{ + if (buffer->data != buffer->__space.__c) + free (buffer->data); +} + +/* Grow *BUFFER by some arbitrary amount. The buffer contents is NOT + preserved. Return true on success, false on allocation failure (in + which case the old buffer is freed). On success, the new buffer is + larger than the previous size. On failure, *BUFFER is deallocated, + but remains in a free-able state, and errno is set. */ +bool __libc_scratch_buffer_grow (struct scratch_buffer *buffer); +libc_hidden_proto (__libc_scratch_buffer_grow) + +/* Alias for __libc_scratch_buffer_grow. */ +static __always_inline bool +scratch_buffer_grow (struct scratch_buffer *buffer) +{ + return __glibc_likely (__libc_scratch_buffer_grow (buffer)); +} + +/* Like __libc_scratch_buffer_grow, but preserve the old buffer + contents on success, as a prefix of the new buffer. */ +bool __libc_scratch_buffer_grow_preserve (struct scratch_buffer *buffer); +libc_hidden_proto (__libc_scratch_buffer_grow_preserve) + +/* Alias for __libc_scratch_buffer_grow_preserve. */ +static __always_inline bool +scratch_buffer_grow_preserve (struct scratch_buffer *buffer) +{ + return __glibc_likely (__libc_scratch_buffer_grow_preserve (buffer)); +} + +/* Grow *BUFFER so that it can store at least NELEM elements of SIZE + bytes. The buffer contents are NOT preserved. Both NELEM and SIZE + can be zero. Return true on success, false on allocation failure + (in which case the old buffer is freed, but *BUFFER remains in a + free-able state, and errno is set). It is unspecified whether this + function can reduce the array size. */ +bool __libc_scratch_buffer_set_array_size (struct scratch_buffer *buffer, + size_t nelem, size_t size); +libc_hidden_proto (__libc_scratch_buffer_set_array_size) + +/* Alias for __libc_scratch_set_array_size. */ +static __always_inline bool +scratch_buffer_set_array_size (struct scratch_buffer *buffer, + size_t nelem, size_t size) +{ + return __glibc_likely (__libc_scratch_buffer_set_array_size + (buffer, nelem, size)); +} + +#endif /* _SCRATCH_BUFFER_H */ diff --git a/xcompile/malloc/scratch_buffer_dupfree.c b/xcompile/malloc/scratch_buffer_dupfree.c new file mode 100644 index 00000000000..2f60fbb54e8 --- /dev/null +++ b/xcompile/malloc/scratch_buffer_dupfree.c @@ -0,0 +1,41 @@ +/* Variable-sized buffer with on-stack default allocation. + Copyright (C) 2020-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +#endif + +#include +#include + +void * +__libc_scratch_buffer_dupfree (struct scratch_buffer *buffer, size_t size) +{ + void *data = buffer->data; + if (data == buffer->__space.__c) + { + void *copy = malloc (size); + return copy != NULL ? memcpy (copy, data, size) : NULL; + } + else + { + void *copy = realloc (data, size); + return copy != NULL ? copy : data; + } +} +libc_hidden_def (__libc_scratch_buffer_dupfree) diff --git a/xcompile/malloc/scratch_buffer_grow.c b/xcompile/malloc/scratch_buffer_grow.c new file mode 100644 index 00000000000..a5e8f2f7230 --- /dev/null +++ b/xcompile/malloc/scratch_buffer_grow.c @@ -0,0 +1,56 @@ +/* Variable-sized buffer with on-stack default allocation. + Copyright (C) 2015-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +#endif + +#include +#include + +bool +__libc_scratch_buffer_grow (struct scratch_buffer *buffer) +{ + void *new_ptr; + size_t new_length = buffer->length * 2; + + /* Discard old buffer. */ + scratch_buffer_free (buffer); + + /* Check for overflow. */ + if (__glibc_likely (new_length >= buffer->length)) + new_ptr = malloc (new_length); + else + { + __set_errno (ENOMEM); + new_ptr = NULL; + } + + if (__glibc_unlikely (new_ptr == NULL)) + { + /* Buffer must remain valid to free. */ + scratch_buffer_init (buffer); + return false; + } + + /* Install new heap-based buffer. */ + buffer->data = new_ptr; + buffer->length = new_length; + return true; +} +libc_hidden_def (__libc_scratch_buffer_grow) diff --git a/xcompile/malloc/scratch_buffer_grow_preserve.c b/xcompile/malloc/scratch_buffer_grow_preserve.c new file mode 100644 index 00000000000..c0b5d87b7e4 --- /dev/null +++ b/xcompile/malloc/scratch_buffer_grow_preserve.c @@ -0,0 +1,67 @@ +/* Variable-sized buffer with on-stack default allocation. + Copyright (C) 2015-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +#endif + +#include +#include +#include + +bool +__libc_scratch_buffer_grow_preserve (struct scratch_buffer *buffer) +{ + size_t new_length = 2 * buffer->length; + void *new_ptr; + + if (buffer->data == buffer->__space.__c) + { + /* Move buffer to the heap. No overflow is possible because + buffer->length describes a small buffer on the stack. */ + new_ptr = malloc (new_length); + if (new_ptr == NULL) + return false; + memcpy (new_ptr, buffer->__space.__c, buffer->length); + } + else + { + /* Buffer was already on the heap. Check for overflow. */ + if (__glibc_likely (new_length >= buffer->length)) + new_ptr = realloc (buffer->data, new_length); + else + { + __set_errno (ENOMEM); + new_ptr = NULL; + } + + if (__glibc_unlikely (new_ptr == NULL)) + { + /* Deallocate, but buffer must remain valid to free. */ + free (buffer->data); + scratch_buffer_init (buffer); + return false; + } + } + + /* Install new heap-based buffer. */ + buffer->data = new_ptr; + buffer->length = new_length; + return true; +} +libc_hidden_def (__libc_scratch_buffer_grow_preserve) diff --git a/xcompile/malloc/scratch_buffer_set_array_size.c b/xcompile/malloc/scratch_buffer_set_array_size.c new file mode 100644 index 00000000000..24c39350ade --- /dev/null +++ b/xcompile/malloc/scratch_buffer_set_array_size.c @@ -0,0 +1,64 @@ +/* Variable-sized buffer with on-stack default allocation. + Copyright (C) 2015-2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC +# include +#endif + +#include +#include +#include + +bool +__libc_scratch_buffer_set_array_size (struct scratch_buffer *buffer, + size_t nelem, size_t size) +{ + size_t new_length = nelem * size; + + /* Avoid overflow check if both values are small. */ + if ((nelem | size) >> (sizeof (size_t) * CHAR_BIT / 2) != 0 + && nelem != 0 && size != new_length / nelem) + { + /* Overflow. Discard the old buffer, but it must remain valid + to free. */ + scratch_buffer_free (buffer); + scratch_buffer_init (buffer); + __set_errno (ENOMEM); + return false; + } + + if (new_length <= buffer->length) + return true; + + /* Discard old buffer. */ + scratch_buffer_free (buffer); + + char *new_ptr = malloc (new_length); + if (new_ptr == NULL) + { + /* Buffer must remain valid to free. */ + scratch_buffer_init (buffer); + return false; + } + + /* Install new heap-based buffer. */ + buffer->data = new_ptr; + buffer->length = new_length; + return true; +} +libc_hidden_def (__libc_scratch_buffer_set_array_size) -- cgit v1.3 From d44b60c2f001d57b010f0e9b82f798fbad9a23d6 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 20 Jan 2023 19:06:32 +0800 Subject: Update Android port * .gitignore: Don't ignore verbose.mk.android. * doc/emacs/Makefile.in (EMACSSOURCES): Add android.texi and input.texi. * doc/emacs/android.texi (Android): Document support for the on-screen keyboard. (Android Startup): Document how to start Emacs with -Q on Android. (Android Environment): Document how Emacs works around the system ``task killer''. Document changes to frame deletion behavior. * doc/emacs/emacs.texi (Top): * doc/emacs/input.texi (Other Input Devices, On-Screen Keyboards): Document how to use Emacs with virtual keyboards. * doc/lispref/commands.texi (Touchscreen Events): Document changes to `touch-screen-track-drag'. * doc/lispref/frames.texi (Frames, On-Screen Keyboards): New node. * java/AndroidManifest.xml.in: Add settings activity and appropriate OSK adjustment mode. * java/org/gnu/emacs/EmacsActivity.java (onCreate): Allow creating Emacs with -Q. (onDestroy): Don't remove if killed by the system. * java/org/gnu/emacs/EmacsContextMenu.java (inflateMenuItems): Fix context menus again. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Make all event sending functions return long. * java/org/gnu/emacs/EmacsPreferencesActivity.java (EmacsPreferencesActivity): New class. * java/org/gnu/emacs/EmacsService.java (EmacsService) (onStartCommand, onCreate, startEmacsService): Start as a foreground service if necessary to bypass system restrictions. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): * java/org/gnu/emacs/EmacsThread.java (EmacsThread, run): * java/org/gnu/emacs/EmacsView.java (EmacsView, onLayout) (onDetachedFromWindow): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, viewLayout): Implement frame resize synchronization.. * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (EmacsWindowAttachmentManager, removeWindowConsumer): Adjust accordingly for changes to frame deletion behavior. * lisp/frame.el (android-toggle-on-screen-keyboard) (frame-toggle-on-screen-keyboard): New function. * lisp/minibuffer.el (minibuffer-setup-on-screen-keyboard) (minibuffer-exit-on-screen-keyboard): New functions. (minibuffer-setup-hook, minibuffer-exit-hook): Add new functions to hooks. * lisp/touch-screen.el (touch-screen-relative-xy): Accept new value of window `frame'. Return frame coordinates in that case. (touch-screen-set-point-commands): New variable. (touch-screen-handle-point-up): Respect that variable. (touch-screen-track-drag): Return `no-drag' where appropriate. (touch-screen-drag-mode-line-1, touch-screen-drag-mode-line): Refactor to use `no-drag'. * src/android.c (struct android_emacs_window): New methods. Make all event sending functions return the event serial. (android_toggle_on_screen_keyboard, android_window_updated): New functions. * src/android.h: Update prototypes. * src/androidfns.c (Fandroid_toggle_on_screen_keyboard) (syms_of_androidfns): New function. * src/androidgui.h (struct android_any_event) (struct android_key_event, struct android_configure_event) (struct android_focus_event, struct android_window_action_event) (struct android_crossing_event, struct android_motion_event) (struct android_button_event, struct android_touch_event) (struct android_wheel_event, struct android_iconify_event) (struct android_menu_event): Add `serial' fields. * src/androidterm.c (handle_one_android_event) (android_frame_up_to_date): * src/androidterm.h (struct android_output): Implement frame resize synchronization. --- .gitignore | 1 + doc/emacs/Makefile.in | 2 + doc/emacs/android.texi | 70 +++++-- doc/emacs/emacs.texi | 1 + doc/emacs/input.texi | 36 ++++ doc/lispref/commands.texi | 8 +- doc/lispref/frames.texi | 21 +++ java/AndroidManifest.xml.in | 12 ++ java/org/gnu/emacs/EmacsActivity.java | 20 +- java/org/gnu/emacs/EmacsContextMenu.java | 1 + java/org/gnu/emacs/EmacsNative.java | 39 ++-- java/org/gnu/emacs/EmacsPreferencesActivity.java | 98 ++++++++++ java/org/gnu/emacs/EmacsService.java | 62 ++++++- java/org/gnu/emacs/EmacsSurfaceView.java | 127 +++++++++---- java/org/gnu/emacs/EmacsThread.java | 12 +- java/org/gnu/emacs/EmacsView.java | 55 +++++- java/org/gnu/emacs/EmacsWindow.java | 58 +++++- .../gnu/emacs/EmacsWindowAttachmentManager.java | 4 +- lisp/frame.el | 17 ++ lisp/minibuffer.el | 23 +++ lisp/touch-screen.el | 203 +++++++++++++-------- src/android.c | 147 ++++++++++++--- src/android.h | 2 + src/androidfns.c | 25 +++ src/androidgui.h | 20 ++ src/androidterm.c | 24 +++ src/androidterm.h | 5 + 27 files changed, 894 insertions(+), 199 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsPreferencesActivity.java (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/.gitignore b/.gitignore index 3bc40c67489..6494e4e8f39 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ src/emacs-module.h # Built by recursive call to `configure'. *.android !INSTALL.android +!verbose.mk.android # Built by `java'. java/install_temp/* diff --git a/doc/emacs/Makefile.in b/doc/emacs/Makefile.in index 161bdcb1c59..c7415312753 100644 --- a/doc/emacs/Makefile.in +++ b/doc/emacs/Makefile.in @@ -146,6 +146,8 @@ EMACSSOURCES= \ ${srcdir}/glossary.texi \ ${srcdir}/ack.texi \ ${srcdir}/kmacro.texi \ + ${srcdir}/android.texi \ + ${srcdir}/input.texi \ $(EMACS_XTRA) ## Disable implicit rules. diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 01a2e68a97e..ba5d2ca3e09 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -10,8 +10,8 @@ Alliance. This section describes the peculiarities of using Emacs on an Android device running Android 2.2 or later. Android devices commonly rely on user input through a touch screen -or digitizer device. For more information about using them with -Emacs, @pxref{other Input Devices}. +or digitizer device and on-screen keyboard. For more information +about using such devices with Emacs, @pxref{Other Input Devices}. @menu * What is Android?:: Preamble. @@ -82,6 +82,16 @@ command on that other system: $ adb logcat | grep -E "(android_run_debug_thread|[Ee]macs)" @end example +@cindex emacs -Q, android +Since Android has no command line, there is normally no way to specify +command-line arguments. However, Emacs can be started with the +equivalent of the @code{--quick} option (@pxref{Initial Options}) +through a special preferences screen, which can be accessed through +the Emacs ``app info'' page in the system settings application. + +Consult the manufacturer of your device for more details, as how to do +this varies by device. + @node Android File System @section What files Emacs can access under Android @cindex /assets directory, android @@ -149,7 +159,7 @@ which is the app data directory (@pxref{Android File System}.) directories, and the app data directories of other applications. In recent versions of Android, the system also prohibits, for security reasons, even Emacs itself from running executables inside the app -data directory! +data directory. Emacs comes with several binaries. While being executable files, they are packaged as libraries in the library directory, because @@ -173,9 +183,28 @@ within itself. Application processes are treated as disposable entities by the system. When all Emacs frames move to the background, Emacs is liable to be killed by the system at any time, for the purpose of saving -resources. There is currently no easy way to bypass these -restrictions, aside from keeping Emacs constantly running in the -foreground. +system resources. + + On Android 7.1 and earlier, Emacs tells the system to treat it as a +``background service''. The system will try to avoid killing Emacs +unless the device is under memory stress. + + Android 8.0 removed the ability for background services to receive +such special treatment. However, Emacs applies a workaround: the +system considers applications that create a permanent notification to +be performing active work, and will avoid killing such applications. +Thus, on those systems, Emacs displays a permanant notification for as +long as it is running. Once the notification is displayed, it can be +safely hidden through the system settings without resulting in Emacs +being killed. + + However, it is not guaranteed that the system will not kill Emacs, +even if the notification is being displayed. While the Open Handset +Alliance's sample implementation of Android behaves correctly, many +manufacturers place additional restrictions on program execution in +the background in their proprietary versions of Android. There is a +list of such troublesome manufacturers and sometimes workarounds, at +@url{https://dontkillmyapp.com/}. @section Android permissions @cindex external storage, android @@ -272,14 +301,14 @@ maximized or full-screen, and only one window can be displayed at a time. On larger devices, the system allows up to four windows to be tiled on the screen at any time. -Windows on Android do not continue to exist indefinitely after they + Windows on Android do not continue to exist indefinitely after they are created. Instead, the system may choose to terminate windows that are not on screen in order to save memory, with the assumption that the program will save its contents to disk and restore them later, when the user asks to open it again. As this is obvious not possible with Emacs, Emacs separates a frame from a system window. -Each system window created (including the initial window created + Each system window created (including the initial window created during Emacs startup) is appended to a list of windows that do not have associated frames. When a frame is created, Emacs looks up any window within that list, and displays the contents of the frame @@ -287,11 +316,28 @@ within; if there is no window at all, then one is created. Likewise, when a new window is created by the system, Emacs places the contents of any frame that is not already displayed within a window inside. When a frame is closed, the corresponding system window is also -closed. +closed. Upon startup, the system creates a window itself (within +which Emacs displays the first window system frame shortly +thereafter.) Emacs differentiates between that window and windows +created on behalf of other frames to determine what to do when the +system window associated with a frame is closed: + +@itemize @bullet +@item +When the system closes the window created during application startup +in order to save memory, Emacs retains the frame for when that window +is created later. -This strategy works as long as one window is in the foreground. -Otherwise, Emacs can only run in the background for a limited amount -of time before the process is killed completely. +@item +When the user closes the window created during application startup, +and the window was not previously closed by the system in order to +save resources, Emacs deletes any frame displayed within that window. + +@item +When the user or the system closes any window created by Emacs on +behalf of a specific frame, Emacs deletes the frame displayed within +that window. +@end itemize @cindex windowing limitations, android @cindex frame parameters, android diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index 9fd169cd5cc..0cb454e5294 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -1269,6 +1269,7 @@ Emacs and Android Emacs and unconventional input devices * Touchscreens:: Using Emacs on touchscreens. +* On-Screen Keyboards:: Using Emacs with virtual keyboards. Emacs and Microsoft Windows/MS-DOS diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi index 0e029c401e3..1a58d1ca0ac 100644 --- a/doc/emacs/input.texi +++ b/doc/emacs/input.texi @@ -16,6 +16,7 @@ input devices, which is detailed here. @menu * Touchscreens:: Using Emacs on touchscreens. +* On-Screen Keyboards:: Using Emacs with virtual keyboards. @end menu @node Touchscreens @@ -58,3 +59,38 @@ were to be held down. @xref{Mouse Commands}. By default, Emacs considers a tool as having been left on the display for a while after 0.7 seconds, but this can be changed by customizing the variable @code{touch-screen-delay}. + +@node On-Screen Keyboards +@section Using Emacs with virtual keyboards +@cindex virtual keyboards +@cindex on-screen keyboards + + When there is no physical keyboard attached to a system, the +windowing system typically provides an on-screen keyboard, more often +known as a ``virtual keyboard'', containing rows of clickable buttons +that send keyboard input to the application, much like a real keyboard +would. This virtual keyboard is hidden by default, as it uses up +valuable on-screen real estate, and must be opened once the program +being used is ready to accept keyboard input. + + Under the X Window System, the client that provides the on-screen +keyboard typically detects when the application is ready to accept +keyboard input through a set of complex heuristics, and automatically +displays the keyboard when necessary. + + On other systems such as Android, Emacs must tell the system when it +is ready to accept keyboard input. Typically, this is done in +response to a touchscreen ``tap'' gesture (@pxref{Touchscreens}), or +once to the minibuffer becomes in use (@pxref{Minibuffer}.) + +@vindex touch-screen-set-point-commands + When a ``tap'' gesture results in a command being executed, Emacs +checks to see whether or not the command is supposed to set the point +by looking for it in the list @code{touch-screen-set-point-commands}. +If it is, then Emacs looks up whether or not the text under the point +is read-only; if not, it activates the on-screen keyboard, assuming +that the user is about to enter text in to the current buffer. + + Emacs also provides a set of functions to show or hide the on-screen +keyboard. For more details, @pxref{On-Screen Keyboards,,, elisp, The +Emacs Lisp Reference Manual}. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 59367a2cecc..484c7dc2a06 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2055,9 +2055,11 @@ The caller should not perform any action in that case. @defun touch-screen-track-drag event update &optional data This function is used to track a single ``drag'' gesture originating -from the @code{touchscreen-begin} event @code{event}. Currently, it -behaves identically to @code{touch-screen-track-tap}, but differences -are anticipated in the future. +from the @code{touchscreen-begin} event @code{event}. + +It behaves like @code{touch-screen-track-tap}, except that it returns +@code{no-drag} if the touchpoint in @code{event} did not move far +enough to qualify as an actual drag. @end defun @node Focus Events diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 2ceafab7a6b..497715bdb19 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -104,6 +104,7 @@ window of another Emacs frame. @xref{Child Frames}. * Mouse Tracking:: Getting events that say when the mouse moves. * Mouse Position:: Asking where the mouse is, or moving it. * Pop-Up Menus:: Displaying a menu for the user to select from. +* On-Screen Keyboards:: Displaying the virtual keyboard. * Dialog Boxes:: Displaying a box to ask yes or no. * Pointer Shape:: Specifying the shape of the mouse pointer. * Window System Selections:: Transferring text to and from other X clients. @@ -3812,6 +3813,26 @@ keymap. It won't be called if @code{x-popup-menu} returns for some other reason without displaying a pop-up menu. @end defvar +@node On-Screen Keyboards +@section On-Screen Keyboards + + An on-screen keyboard is a special kind of pop up provided by the +system, with rows of clickable buttons that act as a real keyboard. + + On certain systems (@pxref{On-Screen Keyboards,,,emacs, The Emacs +Manual}), Emacs is supposed to display and hide the on screen keyboard +depending on whether or not the user is about to type something. + +@defun frame-toggle-on-screen-keyboard frame hide +This function displays or hides the on-screen keyboard on behalf of +the frame @var{frame}. If @var{hide} is non-@code{nil}, then the +on-screen keyboard is hidden; otherwise, it is displayed. + +This has no effect if the system automatically detects when to display +the on-screen keyboard, or when it does not provide any on-screen +keyboard. +@end defun + @node Dialog Boxes @section Dialog Boxes @cindex dialog boxes diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index b680137a9d0..74f69d2a8e5 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -62,8 +62,10 @@ along with GNU Emacs. If not, see . --> android:theme="@android:style/Theme" android:debuggable="true" android:extractNativeLibs="true"> + @@ -73,8 +75,18 @@ along with GNU Emacs. If not, see . --> + + + + + + + . */ + +package org.gnu.emacs; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Build; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.LinearLayout; +import android.widget.TextView; + +import android.R; + +/* This module provides a ``preferences'' display for Emacs. It is + supposed to be launched from inside the Settings application to + perform various actions, such as starting Emacs with the ``-Q'' + option, which would not be possible otherwise, as there is no + command line on Android. */ + +public class EmacsPreferencesActivity extends Activity +{ + /* The linear layout associated with the activity. */ + private LinearLayout layout; + + /* Restart Emacs with -Q. Call EmacsThread.exit to kill Emacs now, and + tell the system to EmacsActivity with some parameters later. */ + + private void + startEmacsQ () + { + Intent intent; + + intent = new Intent (this, EmacsActivity.class); + intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra ("org.gnu.emacs.START_DASH_Q", true); + startActivity (intent); + System.exit (0); + } + + @Override + public void + onCreate (Bundle savedInstanceState) + { + LinearLayout layout; + TextView textView; + LinearLayout.LayoutParams params; + int resid; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + setTheme (R.style.Theme_DeviceDefault_Settings); + else if (Build.VERSION.SDK_INT + >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + setTheme (R.style.Theme_DeviceDefault); + + layout = new LinearLayout (this); + layout.setOrientation (LinearLayout.VERTICAL); + setContentView (layout); + + textView = new TextView (this); + textView.setPadding (8, 20, 20, 8); + + params = new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + textView.setLayoutParams (params); + textView.setText ("(Re)start Emacs with -Q"); + textView.setOnClickListener (new View.OnClickListener () { + @Override + public void + onClick (View view) + { + startEmacsQ (); + } + }); + layout.addView (textView); + + super.onCreate (savedInstanceState); + } +}; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index bcf8d9ff6e8..95f21b211a3 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -32,7 +32,13 @@ import android.view.InputDevice; import android.view.KeyEvent; import android.annotation.TargetApi; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.NotificationChannel; +import android.app.PendingIntent; import android.app.Service; + import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; @@ -63,6 +69,7 @@ public class EmacsService extends Service public static final String TAG = "EmacsService"; public static final int MAX_PENDING_REQUESTS = 256; public static volatile EmacsService SERVICE; + public static boolean needDashQ; private EmacsThread thread; private Handler handler; @@ -74,6 +81,31 @@ public class EmacsService extends Service public int onStartCommand (Intent intent, int flags, int startId) { + Notification notification; + NotificationManager manager; + NotificationChannel channel; + String infoBlurb; + Object tem; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + tem = getSystemService (Context.NOTIFICATION_SERVICE); + manager = (NotificationManager) tem; + infoBlurb = ("See (emacs)Android Environment for more" + + " details about this notification."); + channel + = new NotificationChannel ("emacs", "Emacs persistent notification", + NotificationManager.IMPORTANCE_DEFAULT); + manager.createNotificationChannel (channel); + notification = (new Notification.Builder (this, "emacs") + .setContentTitle ("Emacs") + .setContentText (infoBlurb) + .setSmallIcon (android.R.drawable.sym_def_app_icon) + .build ()); + manager.notify (1, notification); + startForeground (1, notification); + } + return START_NOT_STICKY; } @@ -137,7 +169,7 @@ public class EmacsService extends Service this); /* Start the thread that runs Emacs. */ - thread = new EmacsThread (this); + thread = new EmacsThread (this, needDashQ); thread.start (); } catch (IOException exception) @@ -444,4 +476,32 @@ public class EmacsService extends Service } } } + + + + /* Start the Emacs service if necessary. On Android 26 and up, + start Emacs as a foreground service with a notification, to avoid + it being killed by the system. + + On older systems, simply start it as a normal background + service. */ + + public static void + startEmacsService (Context context) + { + PendingIntent intent; + + if (EmacsService.SERVICE == null) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + /* Start the Emacs service now. */ + context.startService (new Intent (context, + EmacsService.class)); + else + /* Display the permanant notification and start Emacs as a + foreground service. */ + context.startForegroundService (new Intent (context, + EmacsService.class)); + } + } }; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index f713818d4bc..2fe9e103b2b 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -32,53 +32,106 @@ import android.util.Log; public class EmacsSurfaceView extends SurfaceView { private static final String TAG = "EmacsSurfaceView"; - public Object surfaceChangeLock; private boolean created; + private EmacsView view; - public - EmacsSurfaceView (final EmacsView view) - { - super (view.getContext ()); - - surfaceChangeLock = new Object (); + /* This is the callback used on Android 8 to 25. */ - getHolder ().addCallback (new SurfaceHolder.Callback () { - @Override - public void - surfaceChanged (SurfaceHolder holder, int format, - int width, int height) + private class Callback implements SurfaceHolder.Callback + { + @Override + public void + surfaceChanged (SurfaceHolder holder, int format, + int width, int height) + { + Log.d (TAG, "surfaceChanged: " + view + ", " + view.pendingConfigure); + + /* Make sure not to swap buffers if there is pending + configuration, because otherwise the redraw callback will not + run correctly. */ + + if (view.pendingConfigure == 0) + view.swapBuffers (); + } + + @Override + public void + surfaceCreated (SurfaceHolder holder) + { + synchronized (surfaceChangeLock) { - Log.d (TAG, "surfaceChanged: " + view); - view.swapBuffers (); + Log.d (TAG, "surfaceCreated: " + view); + created = true; } - @Override - public void - surfaceCreated (SurfaceHolder holder) - { - synchronized (surfaceChangeLock) - { - Log.d (TAG, "surfaceCreated: " + view); - created = true; - } - - /* Drop the lock when doing this, or a deadlock can - result. */ - view.swapBuffers (); - } + /* Drop the lock when doing this, or a deadlock can + result. */ + view.swapBuffers (); + } - @Override - public void - surfaceDestroyed (SurfaceHolder holder) + @Override + public void + surfaceDestroyed (SurfaceHolder holder) + { + synchronized (surfaceChangeLock) { - synchronized (surfaceChangeLock) - { - Log.d (TAG, "surfaceDestroyed: " + view); - created = false; - } + Log.d (TAG, "surfaceDestroyed: " + view); + created = false; } - }); + } + } + + /* And this is the callback used on Android 26 and later. It is + used because it can tell the system when drawing completes. */ + + private class Callback2 extends Callback implements SurfaceHolder.Callback2 + { + @Override + public void + surfaceRedrawNeeded (SurfaceHolder holder) + { + /* This version is not supported. */ + return; + } + + @Override + public void + surfaceRedrawNeededAsync (SurfaceHolder holder, + Runnable drawingFinished) + { + Runnable old; + + Log.d (TAG, "surfaceRedrawNeededAsync: " + view.pendingConfigure); + + /* The system calls this function when it wants to know whether + or not Emacs is still configuring itself in response to a + resize. + + If the view did not send an outstanding ConfigureNotify + event, then call drawingFinish immediately. Else, give it to + the view to execute after drawing completes. */ + + if (view.pendingConfigure == 0) + drawingFinished.run (); + else + /* And set this runnable to run once drawing completes. */ + view.drawingFinished = drawingFinished; + } + } + + public + EmacsSurfaceView (final EmacsView view) + { + super (view.getContext ()); + + this.surfaceChangeLock = new Object (); + this.view = view; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) + getHolder ().addCallback (new Callback ()); + else + getHolder ().addCallback (new Callback2 ()); } public boolean diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 0882753747f..f9bc132f354 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -23,12 +23,13 @@ import java.lang.Thread; public class EmacsThread extends Thread { - EmacsService context; + /* Whether or not Emacs should be started -Q. */ + private boolean startDashQ; public - EmacsThread (EmacsService service) + EmacsThread (EmacsService service, boolean startDashQ) { - context = service; + this.startDashQ = startDashQ; } public void @@ -36,7 +37,10 @@ public class EmacsThread extends Thread { String args[]; - args = new String[] { "libandroid-emacs.so", }; + if (!startDashQ) + args = new String[] { "libandroid-emacs.so", }; + else + args = new String[] { "libandroid-emacs.so", "-Q", }; /* Run the native code now. */ EmacsNative.initEmacs (args); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 82f44acaebe..74bbb7b3ecc 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -19,6 +19,7 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; +import android.content.Context; import android.content.res.ColorStateList; import android.view.ContextMenu; @@ -27,6 +28,8 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; + import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; @@ -86,11 +89,23 @@ public class EmacsView extends ViewGroup /* The serial of the last clip rectangle change. */ private long lastClipSerial; + /* The InputMethodManager for this view's context. */ + private InputMethodManager imManager; + + /* Runnable that will run once drawing completes. */ + public Runnable drawingFinished; + + /* Serial of the last ConfigureNotify event sent that Emacs has not + yet responded to. 0 if there is no such outstanding event. */ + public long pendingConfigure; + public EmacsView (EmacsWindow window) { super (EmacsService.SERVICE); + Object tem; + this.window = window; this.damageRegion = new Region (); this.paint = new Paint (); @@ -111,6 +126,10 @@ public class EmacsView extends ViewGroup /* Get rid of the default focus highlight. */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) setDefaultFocusHighlightEnabled (false); + + /* Obtain the input method manager. */ + tem = getContext ().getSystemService (Context.INPUT_METHOD_SERVICE); + imManager = (InputMethodManager) tem; } private void @@ -259,7 +278,8 @@ public class EmacsView extends ViewGroup if (changed || mustReportLayout) { mustReportLayout = false; - window.viewLayout (left, top, right, bottom); + pendingConfigure + = window.viewLayout (left, top, right, bottom); } measuredWidth = right - left; @@ -538,4 +558,37 @@ public class EmacsView extends ViewGroup Runtime.getRuntime ().gc (); } } + + public void + showOnScreenKeyboard () + { + /* Specifying no flags at all tells the system the user asked for + the input method to be displayed. */ + imManager.showSoftInput (this, 0); + } + + public void + hideOnScreenKeyboard () + { + imManager.hideSoftInputFromWindow (this.getWindowToken (), + 0); + } + + public void + windowUpdated (long serial) + { + Log.d (TAG, "windowUpdated: serial is " + serial); + + if (pendingConfigure <= serial + /* Detect wraparound. */ + || pendingConfigure - serial >= 0x7fffffff) + { + pendingConfigure = 0; + + if (drawingFinished != null) + drawingFinished.run (); + + drawingFinished = null; + } + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index c5b1522086c..8511af9193e 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -233,7 +233,7 @@ public class EmacsWindow extends EmacsHandleObject return attached; } - public void + public long viewLayout (int left, int top, int right, int bottom) { int rectWidth, rectHeight; @@ -249,10 +249,10 @@ public class EmacsWindow extends EmacsHandleObject rectWidth = right - left; rectHeight = bottom - top; - EmacsNative.sendConfigureNotify (this.handle, - System.currentTimeMillis (), - left, top, rectWidth, - rectHeight); + return EmacsNative.sendConfigureNotify (this.handle, + System.currentTimeMillis (), + left, top, rectWidth, + rectHeight); } public void @@ -589,11 +589,20 @@ public class EmacsWindow extends EmacsHandleObject EmacsActivity.invalidateFocus (); } + /* Notice that the activity has been detached or destroyed. + + ISFINISHING is set if the activity is not the main activity, or + if the activity was not destroyed in response to explicit user + action. */ + public void - onActivityDetached () + onActivityDetached (boolean isFinishing) { - /* Destroy the associated frame when the activity is detached. */ - EmacsNative.sendWindowAction (this.handle, 0); + /* Destroy the associated frame when the activity is detached in + response to explicit user action. */ + + if (isFinishing) + EmacsNative.sendWindowAction (this.handle, 0); } /* Look through the button state to determine what button EVENT was @@ -1064,4 +1073,37 @@ public class EmacsWindow extends EmacsHandleObject /* Return the resulting coordinates. */ return array; } + + public void + toggleOnScreenKeyboard (final boolean on) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + if (on) + view.showOnScreenKeyboard (); + else + view.hideOnScreenKeyboard (); + } + }); + } + + /* Notice that outstanding configure events have been processed. + SERIAL is checked in the UI thread to verify that no new + configure events have been generated in the mean time. */ + + public void + windowUpdated (final long serial) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + view.windowUpdated (serial); + } + }); + } }; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 15eb3bb65c2..510300571b8 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -134,7 +134,7 @@ public class EmacsWindowAttachmentManager } public void - removeWindowConsumer (WindowConsumer consumer) + removeWindowConsumer (WindowConsumer consumer, boolean isFinishing) { EmacsWindow window; @@ -147,7 +147,7 @@ public class EmacsWindowAttachmentManager Log.d (TAG, "removeWindowConsumer: detaching " + window); consumer.detachWindow (); - window.onActivityDetached (); + window.onActivityDetached (isFinishing); } Log.d (TAG, "removeWindowConsumer: removing " + consumer); diff --git a/lisp/frame.el b/lisp/frame.el index f21c0c369bd..f8ef17325ec 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2553,6 +2553,23 @@ symbols." ((string= name "Virtual core keyboard") 'core-keyboard)))))) + +;;;; On-screen keyboard management. + +(declare-function android-toggle-on-screen-keyboard "androidfns.c") + +(defun frame-toggle-on-screen-keyboard (frame hide) + "Display or hide the on-screen keyboard. +On systems with an on-screen keyboard, display the on screen +keyboard on behalf of the frame FRAME if HIDE is nil. Else, hide +the on screen keyboard. + +FRAME must already have the input focus for this to work + reliably." + (let ((frame-type (framep-on-display frame))) + (cond ((eq frame-type 'android) + (android-toggle-on-screen-keyboard frame hide))))) + ;;;; Frame geometry values diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 21d4607e7cf..3c42f29cd1f 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -4576,6 +4576,29 @@ is included in the return value." default))) ": ")) + +;;; On screen keyboard support. +;; Try to display the on screen keyboard whenever entering the +;; mini-buffer, and hide it whenever leaving. + +(defun minibuffer-setup-on-screen-keyboard () + "Maybe display the on-screen keyboard in the current frame. +Display the on-screen keyboard in the current frame if the +last device to have sent an input event is not a keyboard. +This is run upon minibuffer setup." + (when (not (memq (device-class last-event-frame + last-event-device) + '(keyboard core-keyboard))) + (frame-toggle-on-screen-keyboard (selected-frame) nil))) + +(defun minibuffer-exit-on-screen-keyboard () + "Hide the on-screen keyboard if it was displayed. +This is run upon minibuffer exit." + (frame-toggle-on-screen-keyboard (selected-frame) t)) + +(add-hook 'minibuffer-setup-hook #'minibuffer-setup-on-screen-keyboard) +(add-hook 'minibuffer-exit-hook #'minibuffer-exit-on-screen-keyboard) + (provide 'minibuffer) ;;; minibuffer.el ends here diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el index bcc4f5e9be3..a1c9e0b4afd 100644 --- a/lisp/touch-screen.el +++ b/lisp/touch-screen.el @@ -38,6 +38,12 @@ touch point, and the initial position of the touchpoint. See `touch-screen-handle-point-update' for the meanings of the fourth element.") +(defvar touch-screen-set-point-commands '(mouse-set-point) + "List of commands known to set the point. +This is used to determine whether or not to display the on-screen +keyboard after a mouse command is executed in response to a +`touchscreen-end' event.") + (defvar touch-screen-current-timer nil "Timer used to track long-presses. This is always cleared upon any significant state change.") @@ -54,20 +60,28 @@ However, return the coordinates relative to WINDOW. If (posn-window posn) is the same as window, simply return the coordinates in POSN. Otherwise, convert them to the frame, and -then back again." - (if (eq (posn-window posn) window) +then back again. + +If WINDOW is the symbol `frame', simply convert the coordinates +to the frame that they belong in." + (if (or (eq (posn-window posn) window) + (and (eq window 'frame) + (framep (posn-window posn)))) (posn-x-y posn) (let ((xy (posn-x-y posn)) - (edges (window-inside-pixel-edges window))) + (edges (and (windowp window) + (window-inside-pixel-edges window)))) ;; Make the X and Y positions frame relative. (when (windowp (posn-window posn)) (let ((edges (window-inside-pixel-edges (posn-window posn)))) (setq xy (cons (+ (car xy) (car edges)) (+ (cdr xy) (cadr edges)))))) - ;; Make the X and Y positions window relative again. - (cons (- (car xy) (car edges)) - (- (cdr xy) (cadr edges)))))) + (if (eq window 'frame) + xy + ;; Make the X and Y positions window relative again. + (cons (- (car xy) (car edges)) + (- (cdr xy) (cadr edges))))))) (defun touch-screen-handle-scroll (dx dy) "Scroll the display assuming that a touch point has moved by DX and DY." @@ -252,7 +266,12 @@ If the fourth argument of `touch-screen-current-tool' is nil, move point to the position of POINT, selecting the window under POINT as well, and deactivate the mark; if there is a button or link at POINT, call the command bound to `mouse-2' there. -Otherwise, call the command bound to `mouse-1'." +Otherwise, call the command bound to `mouse-1'. + +If the command being executed is listed in +`touch-screen-set-point-commands' also display the on-screen +keyboard if the current buffer and the character at the new point +is not read-only." (let ((what (nth 3 touch-screen-current-tool))) (cond ((null what) (when (windowp (posn-window (cdr point))) @@ -283,9 +302,17 @@ Otherwise, call the command bound to `mouse-1'." (deactivate-mark) ;; This is necessary for following links. (goto-char (posn-point (cdr point))) + ;; Figure out if the on screen keyboard needs to be + ;; displayed. (when command (call-interactively command nil - (vector event))))))))) + (vector event)) + (when (memq command touch-screen-set-point-commands) + (if (not (or buffer-read-only + (get-text-property (point) 'read-only))) + (frame-toggle-on-screen-keyboard (selected-frame) nil) + ;; Otherwise, hide the on screen keyboard now. + (frame-toggle-on-screen-keyboard (selected-frame) t)))))))))) (defun touch-screen-handle-touch (event) "Handle a single touch EVENT, and perform associated actions. @@ -379,22 +406,34 @@ touch point with the same ID as in EVENT, call UPDATE with the touch point in event and DATA. Return nil immediately if any other kind of event is received; -otherwise, return t once the `touchscreen-end' event arrives." - (catch 'finish - (while t - (let ((new-event (read-event))) - (cond - ((eq (car-safe new-event) 'touchscreen-update) - (let ((tool (assq (caadr event) (nth 1 new-event)))) - (when (and update tool) - (funcall update new-event data)))) - ((eq (car-safe new-event) 'touchscreen-end) - (throw 'finish - ;; Now determine whether or not the `touchscreen-end' - ;; event has the same ID as EVENT. If it doesn't, - ;; then this is another touch, so return nil. - (eq (caadr event) (caadr new-event)))) - (t (throw 'finish nil))))))) +otherwise, return either t or `no-drag' once the +`touchscreen-end' event arrives; return `no-drag' returned if the +touch point in EVENT did not move significantly, and t otherwise." + (let ((return-value 'no-drag) + (start-xy (touch-screen-relative-xy (cdadr event) + 'frame))) + (catch 'finish + (while t + (let ((new-event (read-event))) + (cond + ((eq (car-safe new-event) 'touchscreen-update) + (when-let* ((tool (assq (caadr event) (nth 1 new-event))) + (xy (touch-screen-relative-xy (cdr tool) 'frame))) + (when (or (> (- (car xy) (car start-xy)) 5) + (< (- (car xy) (car start-xy)) -5) + (> (- (cdr xy) (cdr start-xy)) 5) + (< (- (cdr xy) (cdr start-xy)) -5)) + (setq return-value t)) + (when (and update tool) + (funcall update new-event data)))) + ((eq (car-safe new-event) 'touchscreen-end) + (throw 'finish + ;; Now determine whether or not the `touchscreen-end' + ;; event has the same ID as EVENT. If it doesn't, + ;; then this is another touch, so return nil. + (and (eq (caadr event) (caadr new-event)) + return-value))) + (t (throw 'finish nil)))))))) @@ -402,45 +441,8 @@ otherwise, return t once the `touchscreen-end' event arrives." (defun touch-screen-drag-mode-line-1 (event) "Internal helper for `touch-screen-drag-mode-line'. -This is called when that function determines it need not execute -any keymaps on the mode line at that particular spot." - ;; Find the window that should be dragged and the starting position. - (let* ((window (posn-window (cdadr event))) - (relative-xy (touch-screen-relative-xy - (cdadr event) window)) - (last-position (cdr relative-xy))) - (when (window-resizable window 0) - (touch-screen-track-drag - event (lambda (new-event &optional _data) - ;; Find the position of the touchpoint in NEW-EVENT. - (let* ((touchpoint (assq (caadr event) (cadr new-event))) - (new-relative-xy - (touch-screen-relative-xy (cdr touchpoint) - window)) - (position (cdr new-relative-xy)) - growth) - ;; Now set the new height of the window. - ;; If new-relative-y is above relative-xy, then - ;; make the window that much shorter. Otherwise, - ;; make it bigger. - (unless (or (zerop (setq growth (- position last-position))) - (and (> growth 0) - (< position (+ (window-pixel-top window) - (window-pixel-height window)))) - (and (< growth 0) - (> position (+ (window-pixel-top window) - (window-pixel-height window))))) - (adjust-window-trailing-edge window growth nil t)) - (setq last-position position))))))) - -(defun touch-screen-drag-mode-line (event) - "Begin dragging the mode line in response to a touch EVENT. -If EVENT lies on top of text with a mouse command bound, run -that command instead. - -Change the height of the window based on where the touch point -in EVENT moves." - (interactive "e") +This is called when that function determines that no drag really +happened. EVENT is the same as in `touch-screen-drag-mode-line'." ;; If there is an object at EVENT, then look either a keymap bound ;; to [down-mouse-1] or a command bound to [mouse-1]. Then, if a ;; keymap was found, pop it up as a menu. Otherwise, wait for a tap @@ -457,21 +459,66 @@ in EVENT moves." (keymap (lookup-key object-keymap [mode-line down-mouse-1])) (command (or (lookup-key object-keymap [mode-line mouse-1]) keymap))) - (if (or (keymapp keymap) command) - (if (keymapp keymap) - (when (touch-screen-track-tap event) - (when-let* ((command (x-popup-menu event keymap)) - (tem (lookup-key keymap - (if (consp command) - (apply #'vector command) - (vector command)) - t))) - (call-interactively tem))) - (when (and (commandp command) - (touch-screen-track-tap event)) - (call-interactively command nil - (vector (list 'mouse-1 (cdadr event)))))) - (touch-screen-drag-mode-line-1 event)))) + (when (or (keymapp keymap) command) + (if (keymapp keymap) + (when-let* ((command (x-popup-menu event keymap)) + (tem (lookup-key keymap + (if (consp command) + (apply #'vector command) + (vector command)) + t))) + (call-interactively tem)) + (when (commandp command) + (call-interactively command nil + (vector (list 'mouse-1 (cdadr event))))))))) + +(defun touch-screen-drag-mode-line (event) + "Begin dragging the mode line in response to a touch EVENT. +Change the height of the window based on where the touch point in +EVENT moves. + +If it does not actually move anywhere and the touch point is +removed, and EVENT lies on top of text with a mouse command +bound, run that command instead." + (interactive "e") + ;; Find the window that should be dragged and the starting position. + (let* ((window (posn-window (cdadr event))) + (relative-xy (touch-screen-relative-xy + (cdadr event) window)) + (last-position (cdr relative-xy))) + (when (window-resizable window 0) + (when (eq + (touch-screen-track-drag + event (lambda (new-event &optional _data) + ;; Find the position of the touchpoint in + ;; NEW-EVENT. + (let* ((touchpoint (assq (caadr event) + (cadr new-event))) + (new-relative-xy + (touch-screen-relative-xy (cdr touchpoint) + window)) + (position (cdr new-relative-xy)) + growth) + ;; Now set the new height of the window. If + ;; new-relative-y is above relative-xy, then + ;; make the window that much shorter. + ;; Otherwise, make it bigger. + (unless (or (zerop (setq growth + (- position last-position))) + (and (> growth 0) + (< position + (+ (window-pixel-top window) + (window-pixel-height window)))) + (and (< growth 0) + (> position + (+ (window-pixel-top window) + (window-pixel-height window))))) + (adjust-window-trailing-edge window growth nil t)) + (setq last-position position)))) + 'no-drag) + ;; Dragging did not actually happen, so try to run any command + ;; necessary. + (touch-screen-drag-mode-line-1 event))))) (global-set-key [mode-line touchscreen-begin] #'touch-screen-drag-mode-line) diff --git a/src/android.c b/src/android.c index eb9c404f1a3..43ac3b3f754 100644 --- a/src/android.c +++ b/src/android.c @@ -121,6 +121,8 @@ struct android_emacs_window { jclass class; jmethodID swap_buffers; + jmethodID toggle_on_screen_keyboard; + jmethodID window_updated; }; /* The asset manager being used. */ @@ -193,6 +195,10 @@ static struct android_emacs_drawable drawable_class; /* Various methods associated with the EmacsWindow class. */ static struct android_emacs_window window_class; +/* The last event serial used. This is a 32 bit value, but it is + stored in unsigned long to be consistent with X. */ +static unsigned int event_serial; + /* Event handling functions. Events are stored on a (circular) queue @@ -1435,6 +1441,9 @@ android_init_emacs_window (void) assert (window_class.c_name); FIND_METHOD (swap_buffers, "swapBuffers", "()V"); + FIND_METHOD (toggle_on_screen_keyboard, + "toggleOnScreenKeyboard", "(Z)V"); + FIND_METHOD (window_updated, "windowUpdated", "(J)V"); #undef FIND_METHOD } @@ -1497,7 +1506,7 @@ NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object) emacs_abort (); } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, jshort window, jlong time, jint x, jint y, jint width, @@ -1506,6 +1515,7 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, union android_event event; event.xconfigure.type = ANDROID_CONFIGURE_NOTIFY; + event.xconfigure.serial = ++event_serial; event.xconfigure.window = window; event.xconfigure.time = time; event.xconfigure.x = x; @@ -1514,9 +1524,10 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, event.xconfigure.height = height; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, jshort window, jlong time, jint state, jint keycode, @@ -1525,6 +1536,7 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, union android_event event; event.xkey.type = ANDROID_KEY_PRESS; + event.xkey.serial = ++event_serial; event.xkey.window = window; event.xkey.time = time; event.xkey.state = state; @@ -1532,9 +1544,10 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, event.xkey.unicode_char = unicode_char; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, jshort window, jlong time, jint state, jint keycode, @@ -1543,6 +1556,7 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, union android_event event; event.xkey.type = ANDROID_KEY_RELEASE; + event.xkey.serial = ++event_serial; event.xkey.window = window; event.xkey.time = time; event.xkey.state = state; @@ -1550,48 +1564,55 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, event.xkey.unicode_char = unicode_char; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object, jshort window, jlong time) { union android_event event; - event.xkey.type = ANDROID_FOCUS_IN; - event.xkey.window = window; - event.xkey.time = time; + event.xfocus.type = ANDROID_FOCUS_IN; + event.xfocus.serial = ++event_serial; + event.xfocus.window = window; + event.xfocus.time = time; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object, jshort window, jlong time) { union android_event event; - event.xkey.type = ANDROID_FOCUS_OUT; - event.xkey.window = window; - event.xkey.time = time; + event.xfocus.type = ANDROID_FOCUS_OUT; + event.xfocus.serial = ++event_serial; + event.xfocus.window = window; + event.xfocus.time = time; android_write_event (&event); + return ++event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object, jshort window, jint action) { union android_event event; event.xaction.type = ANDROID_WINDOW_ACTION; + event.xaction.serial = ++event_serial; event.xaction.window = window; event.xaction.action = action; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -1599,15 +1620,17 @@ NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object, union android_event event; event.xcrossing.type = ANDROID_ENTER_NOTIFY; + event.xcrossing.serial = ++event_serial; event.xcrossing.window = window; event.xcrossing.x = x; event.xcrossing.y = y; event.xcrossing.time = time; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -1615,15 +1638,17 @@ NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object, union android_event event; event.xcrossing.type = ANDROID_LEAVE_NOTIFY; + event.xcrossing.serial = ++event_serial; event.xcrossing.window = window; event.xcrossing.x = x; event.xcrossing.y = y; event.xcrossing.time = time; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -1631,15 +1656,17 @@ NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object, union android_event event; event.xmotion.type = ANDROID_MOTION_NOTIFY; + event.xmotion.serial = ++event_serial; event.xmotion.window = window; event.xmotion.x = x; event.xmotion.y = y; event.xmotion.time = time; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -1648,6 +1675,7 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, union android_event event; event.xbutton.type = ANDROID_BUTTON_PRESS; + event.xbutton.serial = ++event_serial; event.xbutton.window = window; event.xbutton.x = x; event.xbutton.y = y; @@ -1656,9 +1684,10 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, event.xbutton.button = button; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -1667,6 +1696,7 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, union android_event event; event.xbutton.type = ANDROID_BUTTON_RELEASE; + event.xbutton.serial = ++event_serial; event.xbutton.window = window; event.xbutton.x = x; event.xbutton.y = y; @@ -1675,9 +1705,10 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, event.xbutton.button = button; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -1685,6 +1716,7 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, union android_event event; event.touch.type = ANDROID_TOUCH_DOWN; + event.touch.serial = ++event_serial; event.touch.window = window; event.touch.x = x; event.touch.y = y; @@ -1692,9 +1724,10 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, event.touch.pointer_id = pointer_id; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -1702,6 +1735,7 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, union android_event event; event.touch.type = ANDROID_TOUCH_UP; + event.touch.serial = ++event_serial; event.touch.window = window; event.touch.x = x; event.touch.y = y; @@ -1709,9 +1743,10 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, event.touch.pointer_id = pointer_id; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -1719,6 +1754,7 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, union android_event event; event.touch.type = ANDROID_TOUCH_MOVE; + event.touch.serial = ++event_serial; event.touch.window = window; event.touch.x = x; event.touch.y = y; @@ -1726,9 +1762,10 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, event.touch.pointer_id = pointer_id; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -1737,6 +1774,7 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, union android_event event; event.wheel.type = ANDROID_WHEEL; + event.wheel.serial = ++event_serial; event.wheel.window = window; event.wheel.x = x; event.wheel.y = y; @@ -1746,43 +1784,50 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, event.wheel.y_delta = y_delta; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object, jshort window) { union android_event event; event.iconified.type = ANDROID_ICONIFIED; + event.iconified.serial = ++event_serial; event.iconified.window = window; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object, jshort window) { union android_event event; event.iconified.type = ANDROID_DEICONIFIED; + event.iconified.serial = ++event_serial; event.iconified.window = window; android_write_event (&event); + return event_serial; } -extern JNIEXPORT void JNICALL +extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object, jshort window, jint menu_event_id) { union android_event event; event.menu.type = ANDROID_CONTEXT_MENU; + event.menu.serial = ++event_serial; event.menu.window = window; event.menu.menu_event_id = menu_event_id; android_write_event (&event); + return event_serial; } #ifdef __clang__ @@ -3599,6 +3644,15 @@ android_translate_coordinates (android_window src, int x, ANDROID_DELETE_LOCAL_REF (coordinates); } +void +android_sync (void) +{ + (*android_java_env)->CallVoidMethod (android_java_env, + emacs_service, + service_class.sync); + android_exception_check (); +} + /* Low level drawing primitives. */ @@ -3795,12 +3849,45 @@ android_get_keysym_name (int keysym, char *name_return, size_t size) ANDROID_DELETE_LOCAL_REF (string); } +/* Display the on screen keyboard on window WINDOW, or hide it if SHOW + is false. Ask the system to bring up or hide the on-screen + keyboard on behalf of WINDOW. The request may be rejected by the + system, especially when the window does not have the input + focus. */ + void -android_sync (void) +android_toggle_on_screen_keyboard (android_window window, bool show) { - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.sync); + jobject object; + jmethodID method; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + method = window_class.toggle_on_screen_keyboard; + + /* Now display the on screen keyboard. */ + (*android_java_env)->CallVoidMethod (android_java_env, object, + method, (jboolean) show); + + /* Check for out of memory errors. */ + android_exception_check (); +} + +/* Tell the window system that all configure events sent to WINDOW + have been fully processed, and that it is now okay to display its + new contents. SERIAL is the serial of the last configure event + processed. */ + +void +android_window_updated (android_window window, unsigned long serial) +{ + jobject object; + jmethodID method; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + method = window_class.window_updated; + + (*android_java_env)->CallVoidMethod (android_java_env, object, + method, (jlong) serial); android_exception_check (); } @@ -3811,7 +3898,7 @@ android_sync (void) #undef faccessat /* Replace the system faccessat with one which understands AT_EACCESS. - Android's faccessat simply fails upon using AT_EACCESS, so repalce + Android's faccessat simply fails upon using AT_EACCESS, so replace it with zero here. This isn't caught during configuration. This replacement is only done when building for Android 17 or @@ -3829,7 +3916,7 @@ faccessat (int dirfd, const char *pathname, int mode, int flags) return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS); } -#endif /* __ANDROID_API__ < 16 */ +#endif /* __ANDROID_API__ >= 17 */ diff --git a/src/android.h b/src/android.h index 240bc90d831..97818ab4911 100644 --- a/src/android.h +++ b/src/android.h @@ -91,6 +91,8 @@ extern void android_exception_check (void); extern void android_get_keysym_name (int, char *, size_t); extern void android_wait_event (void); +extern void android_toggle_on_screen_keyboard (android_window, bool); +extern void android_window_updated (android_window, unsigned long); diff --git a/src/androidfns.c b/src/androidfns.c index bb37c415069..77ee2e8de44 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -2332,6 +2332,30 @@ there is no mouse. */) #endif } +DEFUN ("android-toggle-on-screen-keyboard", + Fandroid_toggle_on_screen_keyboard, + Sandroid_toggle_on_screen_keyboard, 2, 2, 0, + doc: /* Display or hide the on-screen keyboard. +If HIDE is non-nil, hide the on screen keyboard if it is currently +being displayed. Else, request that the system display it on behalf +of FRAME. This request may be rejected if FRAME does not have the +input focus. */) + (Lisp_Object frame, Lisp_Object hide) +{ +#ifndef ANDROID_STUBIFY + struct frame *f; + + f = decode_window_system_frame (frame); + + block_input (); + android_toggle_on_screen_keyboard (FRAME_ANDROID_WINDOW (f), + NILP (hide)); + unblock_input (); +#endif + + return Qnil; +} + #ifndef ANDROID_STUBIFY @@ -2781,6 +2805,7 @@ syms_of_androidfns (void) defsubr (&Sx_show_tip); defsubr (&Sx_hide_tip); defsubr (&Sandroid_detect_mouse); + defsubr (&Sandroid_toggle_on_screen_keyboard); #ifndef ANDROID_STUBIFY tip_timer = Qnil; diff --git a/src/androidgui.h b/src/androidgui.h index 1f28c18ff34..3b9a74dc0b0 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -239,6 +239,7 @@ enum android_event_type struct android_any_event { enum android_event_type type; + unsigned long serial; android_window window; }; @@ -252,6 +253,7 @@ enum android_modifier_mask struct android_key_event { enum android_event_type type; + unsigned long serial; android_window window; android_time time; unsigned int state; @@ -271,6 +273,7 @@ struct android_key_event struct android_configure_event { enum android_event_type type; + unsigned long serial; android_window window; android_time time; int x, y; @@ -280,6 +283,7 @@ struct android_configure_event struct android_focus_event { enum android_event_type type; + unsigned long serial; android_window window; android_time time; }; @@ -287,6 +291,7 @@ struct android_focus_event struct android_window_action_event { enum android_event_type type; + unsigned long serial; /* The window handle. This can be ANDROID_NONE. */ android_window window; @@ -301,6 +306,7 @@ struct android_window_action_event struct android_crossing_event { enum android_event_type type; + unsigned long serial; android_window window; int x, y; unsigned long time; @@ -309,6 +315,7 @@ struct android_crossing_event struct android_motion_event { enum android_event_type type; + unsigned long serial; android_window window; int x, y; unsigned long time; @@ -317,6 +324,7 @@ struct android_motion_event struct android_button_event { enum android_event_type type; + unsigned long serial; android_window window; int x, y; unsigned long time; @@ -329,6 +337,9 @@ struct android_touch_event /* Type of the event. */ enum android_event_type type; + /* Serial identifying the event. */ + unsigned long serial; + /* Window associated with the event. */ android_window window; @@ -347,6 +358,9 @@ struct android_wheel_event /* Type of the event. */ enum android_event_type type; + /* Serial identifying the event. */ + unsigned long serial; + /* Window associated with the event. */ android_window window; @@ -368,6 +382,9 @@ struct android_iconify_event /* Type of the event. */ enum android_event_type type; + /* Serial identifying the event. */ + unsigned long serial; + /* Window associated with the event. */ android_window window; }; @@ -377,6 +394,9 @@ struct android_menu_event /* Type of the event. */ enum android_event_type type; + /* Serial identifying the event. */ + unsigned long serial; + /* Window associated with the event. Always None. */ android_window window; diff --git a/src/androidterm.c b/src/androidterm.c index 3c16b542d91..3024890d789 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -591,7 +591,17 @@ handle_one_android_event (struct android_display_info *dpyinfo, android_clear_under_internal_border (f); SET_FRAME_GARBAGED (f); cancel_mouse_face (f); + + /* Now stash the serial of this configure event somewhere, + and call android_window_updated with it once the redraw + completes. */ + FRAME_OUTPUT_DATA (f)->last_configure_serial + = configureEvent.xconfigure.serial; } + else + /* Reply to this ConfigureNotify event immediately. */ + android_window_updated (FRAME_ANDROID_WINDOW (f), + configureEvent.xconfigure.serial); goto OTHER; @@ -1194,6 +1204,9 @@ handle_one_android_event (struct android_display_info *dpyinfo, /* Iconification. This is vastly simpler than on X. */ case ANDROID_ICONIFIED: + if (!any) + goto OTHER; + if (FRAME_ICONIFIED_P (any)) goto OTHER; @@ -1206,6 +1219,9 @@ handle_one_android_event (struct android_display_info *dpyinfo, case ANDROID_DEICONIFIED: + if (!any) + goto OTHER; + if (!FRAME_ICONIFIED_P (any)) goto OTHER; @@ -1311,6 +1327,14 @@ android_frame_up_to_date (struct frame *f) /* The frame is now complete, as its contents have been drawn. */ FRAME_ANDROID_COMPLETE_P (f) = true; + /* If there was an outstanding configure event, then tell system + that the update has finished and the new contents can now be + displayed. */ + if (FRAME_OUTPUT_DATA (f)->last_configure_serial) + android_window_updated (FRAME_ANDROID_WINDOW (f), + FRAME_OUTPUT_DATA (f)->last_configure_serial); + FRAME_OUTPUT_DATA (f)->last_configure_serial = 0; + /* Shrink the scanline buffer used by the font backend. */ sfntfont_android_shrink_scanline_buffer (); unblock_input (); diff --git a/src/androidterm.h b/src/androidterm.h index c0f862e35fb..11b24d40b73 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -241,6 +241,11 @@ struct android_output /* List of all tools (either styluses or fingers) pressed onto the frame. */ struct android_touch_point *touch_points; + + /* Event serial of the last ConfigureNotify event received that has + not yet been drawn. This is used to synchronize resize with the + window system. 0 if no such outstanding event exists. */ + unsigned long last_configure_serial; }; enum -- cgit v1.3 From b0e7ae6d5b68a56da40256c395141f071172a622 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 26 Jan 2023 22:11:04 +0800 Subject: Update Android port * INSTALL.android: Describe that apksigner is also required. * configure.ac: Correctly add cross/Makefile to SUBDIR_MAKEFILES. * cross/Makefile.in: (config.status): Depend on $(top_srcdir)/config.status. * doc/emacs/input.texi (On-Screen Keyboards): Document how to quit without a physical keyboard. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function `quit'. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): New field `lastVolumeButtonPress'. (onKeyDown): Quit if necessary. * m4/ndk-build.m4 (ndk_where_cc): Fix search if CC is not a single word. * src/android.c (android_open): Remove unused variable. (quit): New function. * src/androidmenu.c (android_process_events_for_menu): Allow quitting the menu. * src/xterm.c (handle_one_xevent, x_term_init, syms_of_xterm): Implement features described above, so they work on free operating systems. * src/xterm.h (struct x_display_info): New fields `quit_keysym', `quit_keysym_time'. --- INSTALL.android | 4 +- configure.ac | 6 +-- cross/Makefile.in | 5 ++- doc/emacs/input.texi | 12 ++++++ java/org/gnu/emacs/EmacsNative.java | 4 ++ java/org/gnu/emacs/EmacsWindow.java | 19 +++++++++ m4/ndk-build.m4 | 2 +- src/android.c | 8 +++- src/androidmenu.c | 6 +++ src/xterm.c | 80 ++++++++++++++++++++++++++++++++++++- src/xterm.h | 7 ++++ 11 files changed, 141 insertions(+), 12 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/INSTALL.android b/INSTALL.android index 06211e5ec93..e5d7162140d 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -40,8 +40,8 @@ Replacing the paths in the command line above with: are building Emacs to run on. - the path to the directory in the Android SDK containing binaries - such as `aapt' and `d8'. These are used to build the application - package. + such as `aapt', `apksigner', and `d8'. These are used to build + the application package. After the configuration process completes, you may run: diff --git a/configure.ac b/configure.ac index 879e4ab74aa..aaee65016b2 100644 --- a/configure.ac +++ b/configure.ac @@ -7411,11 +7411,7 @@ if test -f "$srcdir/$opt_makefile.in"; then fi if test "$ANDROID" = "yes"; then - SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES java/Makefile" -fi - -if test "$XCOMPILE" = "yes"; then - SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES cross/Makefile" + SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES java/Makefile cross/Makefile" fi dnl The admin/ directory used to be excluded from tarfiles. diff --git a/cross/Makefile.in b/cross/Makefile.in index 92f8d068975..a44550c6563 100644 --- a/cross/Makefile.in +++ b/cross/Makefile.in @@ -60,8 +60,9 @@ all: lib/libgnu.a src/libemacs.so src/android-emacs $(LIBSRC_BINARIES) # This Makefile relies on builddir and top_builddir being relative # paths in *.android. -# This file is used to trick lib/gnulib.mk, it is not actually useful. -config.status: +# This file is used to tell lib/gnulib.mk when +# $(top_srcdir)/config.status changes. +config.status: $(top_srcdir)/config.status touch config.status src/verbose.mk: verbose.mk.android diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi index 1a58d1ca0ac..3894d4872e0 100644 --- a/doc/emacs/input.texi +++ b/doc/emacs/input.texi @@ -94,3 +94,15 @@ that the user is about to enter text in to the current buffer. Emacs also provides a set of functions to show or hide the on-screen keyboard. For more details, @pxref{On-Screen Keyboards,,, elisp, The Emacs Lisp Reference Manual}. + +@cindex quitting, without a keyboard + Since it may not be possible for Emacs to display the on screen +keyboard when it is executing a command, Emacs implements a feature on +devices with only an on-screen keyboard, by which two rapid clicks of +a hardware button that is always present on the device results in +Emacs quitting. @xref{Quitting}. + +@defvar x-quit-keysym + The exact button is used to do this varies by system: on X, it is +defined in the variable @code{x-quit-keysym}, and on Android, it is +always the volume down button. diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 7bf8b5f6081..4e91a7be322 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -74,6 +74,10 @@ public class EmacsNative /* Abort and generate a native core dump. */ public static native void emacsAbort (); + /* Set Vquit_flag to t, resulting in Emacs quitting as soon as + possible. */ + public static native void quit (); + /* Send an ANDROID_CONFIGURE_NOTIFY event. The values of all the functions below are the serials of the events sent. */ public static native long sendConfigureNotify (short window, long time, diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 8511af9193e..39eaf2fff80 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -124,6 +124,10 @@ public class EmacsWindow extends EmacsHandleObject there is no such window manager. */ private WindowManager windowManager; + /* The time of the last KEYCODE_VOLUME_DOWN press. This is used to + quit Emacs. */ + private long lastVolumeButtonPress; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height, boolean overrideRedirect) @@ -513,6 +517,7 @@ public class EmacsWindow extends EmacsHandleObject onKeyDown (int keyCode, KeyEvent event) { int state, state_1; + long time; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -544,6 +549,20 @@ public class EmacsWindow extends EmacsHandleObject state, keyCode, event.getUnicodeChar (state_1)); lastModifiers = state; + + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) + { + /* Check if this volume down press should quit Emacs. + Most Android devices have no physical keyboard, so it + is unreasonably hard to press C-g. */ + + time = event.getEventTime (); + + if (lastVolumeButtonPress - time < 350) + EmacsNative.quit (); + + lastVolumeButtonPress = time; + } } public void diff --git a/m4/ndk-build.m4 b/m4/ndk-build.m4 index bcfe0fed6fe..0ab6197d735 100644 --- a/m4/ndk-build.m4 +++ b/m4/ndk-build.m4 @@ -166,7 +166,7 @@ that could not be found in the list of directories specified in \ } # Look for a suitable ar in the same directory as the C compiler. -ndk_where_cc=$(which $CC) +ndk_where_cc=$(which $(echo "$CC" | awk -- "{ print \[$]1 }")) ndk_ar_search_path=$PATH # First, try to find $host_alias-ar in PATH. diff --git a/src/android.c b/src/android.c index 1676cbf9942..379b54a65be 100644 --- a/src/android.c +++ b/src/android.c @@ -1228,7 +1228,7 @@ android_open (const char *filename, int oflag, int mode) { const char *name; AAsset *asset; - int fd, oldfd; + int fd; off_t out_start, out_length; if (asset_manager && (name = android_get_asset_name (filename))) @@ -1889,6 +1889,12 @@ NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object) emacs_abort (); } +extern JNIEXPORT void JNICALL +NATIVE_NAME (quit) (JNIEnv *env, jobject object) +{ + Vquit_flag = Qt; +} + extern JNIEXPORT jlong JNICALL NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, jshort window, jlong time, diff --git a/src/androidmenu.c b/src/androidmenu.c index f65b5d3ffd1..7b27825ad60 100644 --- a/src/androidmenu.c +++ b/src/androidmenu.c @@ -187,6 +187,12 @@ android_process_events_for_menu (int *id) /* Process pending signals. */ process_pending_signals (); + + /* Maybe quit. This is important because the framework (on + Android 4.0.3) can sometimes fail to deliver context menu + closed events if a submenu was opened, and the user still + needs to be able to quit. */ + maybe_quit (); } /* Restore the input block. */ diff --git a/src/xterm.c b/src/xterm.c index 1325d923be9..eeefed34d4b 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -20103,6 +20103,24 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif + /* See if keysym should make Emacs quit. */ + + if (keysym == dpyinfo->quit_keysym + && (xkey.time - dpyinfo->quit_keysym_time + <= 350)) + { + Vquit_flag = Qt; + goto done_keysym; + } + + if (keysym == dpyinfo->quit_keysym) + { + /* Otherwise, set the last time that keysym was + pressed. */ + dpyinfo->quit_keysym_time = xkey.time; + goto done_keysym; + } + /* If not using XIM/XIC, and a compose sequence is in progress, we break here. Otherwise, chars_matched is always 0. */ if (compose_status.chars_matched > 0 && nbytes == 0) @@ -23851,6 +23869,24 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #endif + /* See if keysym should make Emacs quit. */ + + if (keysym == dpyinfo->quit_keysym + && (xev->time - dpyinfo->quit_keysym_time + <= 350)) + { + Vquit_flag = Qt; + goto xi_done_keysym; + } + + if (keysym == dpyinfo->quit_keysym) + { + /* Otherwise, set the last time that keysym was + pressed. */ + dpyinfo->quit_keysym_time = xev->time; + goto xi_done_keysym; + } + /* First deal with keysyms which have defined translations to characters. */ if (keysym >= 32 && keysym < 128) @@ -29855,6 +29891,7 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) struct terminal *terminal; struct x_display_info *dpyinfo; XrmDatabase xrdb; + Lisp_Object tem, quit_keysym; #ifdef USE_XCB xcb_connection_t *xcb_conn; #endif @@ -29865,7 +29902,7 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) GdkScreen *gscr; #endif #ifdef HAVE_XFIXES - Lisp_Object tem, lisp_name; + Lisp_Object lisp_name; int num_fast_selections; Atom selection_name; #ifdef USE_XCB @@ -30142,6 +30179,28 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) terminal->kboard->reference_count++; } + /* Now look through Vx_quit_keysym for the quit keysym associated + with this display. */ + tem = Vx_quit_keysym; + FOR_EACH_TAIL_SAFE (tem) + { + quit_keysym = XCAR (tem); + + /* Check if its car is a string and its cdr a valid keysym. + Skip if it is not. */ + + if (!CONSP (quit_keysym) || !FIXNUMP (XCDR (quit_keysym)) + || !STRINGP (XCAR (quit_keysym))) + continue; + + /* Check if this is the keysym to be used. */ + + if (strcmp (SSDATA (XCAR (quit_keysym)), ServerVendor (dpy))) + continue; + + dpyinfo->quit_keysym = XFIXNUM (XCDR (quit_keysym)); + } + /* Put this display on the chain. */ dpyinfo->next = x_display_list; x_display_list = dpyinfo; @@ -32200,4 +32259,23 @@ frame placement via frame parameters, `set-frame-position', and `set-frame-size', along with the actual state of a frame after `x_make_frame_invisible'. */); Vx_lax_frame_positioning = Qnil; + + DEFVAR_LISP ("x-quit-keysym", Vx_quit_keysym, + doc: /* Keysyms which will cause Emacs to quit if rapidly pressed twice. + +This is used to support quitting on devices that do not have any kind +of physical keyboard, or where the physical keyboard is incapable of +entering `C-g'. It defaults to `XF86XK_AudioLowerVolume' on XFree86 +and X.Org servers, and is unset. + +The value is an alist associating between strings, describing X server +vendor names, and a single number describing the keysym to use. The +keysym to use for each display connection is determined upon +connection setup, and does not reflect further changes to this +variable. */); + Vx_quit_keysym + = list2 (Fcons (build_string ("The X.Org Foundation"), + make_int (269025041)), + Fcons (build_string ("The XFree86 Project, Inc."), + make_int (269025041))); } diff --git a/src/xterm.h b/src/xterm.h index 28ae00ca190..406a7c5c060 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -920,6 +920,13 @@ struct x_display_info server_time_monotonic_p will be true). */ int_fast64_t server_time_offset; #endif + + /* Keysym that will cause Emacs to quit if pressed twice within 150 + ms. */ + KeySym quit_keysym; + + /* The last time that keysym was pressed. */ + Time quit_keysym_time; }; #ifdef HAVE_X_I18N -- cgit v1.3 From e3b50ec8ec1b8d1246aa2496f3fe8bf98d354545 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 28 Jan 2023 14:29:51 +0800 Subject: Update Android port * INSTALL.android: Document support for gnutls and libgmp. * build-aux/ndk-build-helper-1.mk (NDK_SO_NAMES, NDK_INCLUDES) (SYSTEM_LIBRARIES): * build-aux/ndk-build-helper-2.mk: Recursively resolve and add shared library dependencies; even those of static libraries. * build-aux/ndk-module-extract.awk: Fix makefile_imports code. * configure.ac (ANDROID_SDK_18_OR_EARLIER, XCONFIGURE) (LIBGMP_CFLAGS): Enable GMP and gnutls on Android. * cross/ndk-build/Makefile.in (LOCAL_EXPORT_C_INCLUDES): * cross/ndk-build/ndk-build-shared-library.mk: ($(call objname,$(LOCAL_MODULE),$(basename $(1))))::($$(error Unsupported suffix)::($(LOCAL_MODULE_FILENAME)): * cross/ndk-build/ndk-build-static-library.mk: ($(call objname,$(LOCAL_MODULE),$(basename $(1))))::($$(error Unsupported suffix): * cross/ndk-build/ndk-clear-vars.mk: * cross/ndk-build/ndk-resolve.mk (NDK_SYSTEM_LIBRARIES): (NDK_LOCAL_EXPORT_C_INCLUDES_$(LOCAL_MODULE)): (NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)): Implement ``LOCAL_ASM_RULE'' and ``LOCAL_C_ADDITIONAL_FLAGS'' extensions for libgmp. * doc/emacs/input.texi (Touchscreens): Document how to horizontally scroll. * java/org/gnu/emacs/EmacsActivity.java (attachWindow): Give the view focus again if necessary. (onPause): Call right super function. * java/org/gnu/emacs/EmacsPreferencesActivity.java (onClick): Clear dumpFileName lest Emacs try to load a nonexistent dump file. * java/org/gnu/emacs/EmacsView.java (onDetachedFromWindow) (onAttachedToWindow): Call super functions. (onCreateInputConnection): Make sure the IME never obscures Emacs. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, onKeyDown) (onKeyUp): Improve tracking of quit keys. * lisp/isearch.el (isearch-mode): Bring up the onscreen keyboard. * lisp/touch-screen.el (touch-screen-current-tool): Add three fields. (touch-screen-handle-scroll): Allow hscrolling as well. (touch-screen-handle-touch): Add additional fields to `touch-screen-current-tool'. * src/Makefile.in (LIBGMP_CFLAGS, EMACS_CFLAGS): Add new variable. * src/android.c (android_run_select_thread): (android_write_event): Use pthread_cond_broadcast because pthread_cond_signal does nothing on some Android versions/devices? --- INSTALL.android | 19 ++++++ build-aux/ndk-build-helper-1.mk | 18 +++++- build-aux/ndk-build-helper-2.mk | 2 +- build-aux/ndk-module-extract.awk | 2 +- configure.ac | 35 ++++++++++- cross/ndk-build/Makefile.in | 2 + cross/ndk-build/ndk-build-shared-library.mk | 16 +++-- cross/ndk-build/ndk-build-static-library.mk | 10 +++- cross/ndk-build/ndk-clear-vars.mk | 5 ++ cross/ndk-build/ndk-resolve.mk | 12 ++++ doc/emacs/input.texi | 3 + java/org/gnu/emacs/EmacsActivity.java | 6 +- java/org/gnu/emacs/EmacsPreferencesActivity.java | 5 ++ java/org/gnu/emacs/EmacsView.java | 21 +++++++ java/org/gnu/emacs/EmacsWindow.java | 36 ++++++------ lisp/isearch.el | 10 ++++ lisp/touch-screen.el | 75 ++++++++++++++++++++---- src/Makefile.in | 5 +- src/android.c | 7 +-- 19 files changed, 243 insertions(+), 46 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/INSTALL.android b/INSTALL.android index e5d7162140d..267a3910e30 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -182,6 +182,12 @@ themselves: its Android.mk includes $(BUILD_SHARED_LIBRARY), then copy android/jansson_config.h to android/jansson_private_config.h.) +And Emacs developers have ported the following dependencies to arm64 +Android systems: + + gnutls, gmp - https://sourceforge.net/projects/android-ports-for-gnu-emacs + (Please see the section GNUTLS near the end of this file.) + We anticipate that most untested non-trivial ndk-build dependencies will need adjustments in Emacs to work, as the Emacs build system which emulates ndk-build is in an extremely early state. @@ -519,6 +525,19 @@ As a result, all three dependencies will be built and linked to Emacs, instead of just the single ``webpdemux'' dependency that was specified. +GNUTLS + +Modified copies of GnuTLS and its dependencies (such as libgmp, +libtasn1, p11-kit) which can be built with the ndk-build system can be +found at https://sourceforge.net/projects/android-ports-for-gnu-emacs. + +They have only been tested on arm64 Android systems running Android +5.0 or later, so your mileage may vary, especially if you are trying +to build Emacs for another kind of machine. + +To build Emacs with GnuTLS, you must unpack each tar archive in that +site, and add the resulting folder to ``--with-ndk-path''. + This file is part of GNU Emacs. diff --git a/build-aux/ndk-build-helper-1.mk b/build-aux/ndk-build-helper-1.mk index 9035b5ca59a..50def5f1c2d 100644 --- a/build-aux/ndk-build-helper-1.mk +++ b/build-aux/ndk-build-helper-1.mk @@ -46,6 +46,12 @@ else NDK_SO_NAMES = $(LOCAL_MODULE_FILENAME).so endif +define add-so-name-1 +# Now recurse over this module's dependencies. +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module)))) +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-so-name-1,$$(module)))) +endef + define add-so-name ifeq ($(findstring lib,$(1)),lib) NDK_SO_NAME = $(1)_emacs.so @@ -58,6 +64,9 @@ NDK_SO_NAMES := $$(NDK_SO_NAMES) $$(NDK_SO_NAME) # Now recurse over this module's dependencies. $$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module)))) + +# Recurse over static library dependencies of this shared library. +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_STATIC_LIBRARIES) $$(NDK_$(1)_WHOLE_LIBRARIES)),$$(eval $$(call add-so-name-1,$$(module)))) endif endef @@ -69,6 +78,13 @@ ifeq ($$(findstring $$(NDK_$(1)_EXPORT_INCLUDES),$$(NDK_INCLUDES)),) NDK_INCLUDES += $$(NDK_$(1)_EXPORT_INCLUDES) $$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARIES)) $$(NDK_$(1)_STATIC_LIBRARIES),$$(eval $$(call add-includes,$$(module)))) + +# Recurse over shared library dependencies of this static library. +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module)))) + +# Recurse over static or shared library dependencies of this static +# library. +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-so-name-1,$$(module)))) endif endef @@ -80,7 +96,7 @@ endef SYSTEM_LIBRARIES = z libz libc c libdl dl stdc++ libstdc++ log liblog android libandroid $(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES)),$(eval $(call add-so-name,$(module)))) -$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES) $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_LIBRARIES)),$(eval $(call add-includes,$(module)))) +$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES) $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES)),$(eval $(call add-includes,$(module)))) $(info $(foreach dir,$(NDK_INCLUDES),-I$(dir))) $(info $(LOCAL_EXPORT_CFLAGS)) diff --git a/build-aux/ndk-build-helper-2.mk b/build-aux/ndk-build-helper-2.mk index 8f2f397e534..3722437c53e 100644 --- a/build-aux/ndk-build-helper-2.mk +++ b/build-aux/ndk-build-helper-2.mk @@ -96,6 +96,6 @@ $(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES) $(L $(info $(foreach dir,$(NDK_INCLUDES),-I$(dir))) $(info $(LOCAL_EXPORT_CFLAGS)) $(info $(LOCAL_EXPORT_LDFLAGS) $(abspath $(addprefix $(NDK_BUILD_DIR)/,$(NDK_A_NAMES))) $(and $(NDK_SO_NAMES), -L$(abspath $(NDK_BUILD_DIR)) $(foreach soname,$(NDK_SO_NAMES),-l:$(soname)))) -$(info $(NDK_A_NAMES)) +$(info $(NDK_A_NAMES) $(NDK_SO_NAMES)) $(info $(filter %stdc++,$(LOCAL_SHARED_LIBRARIES))) $(info End) diff --git a/build-aux/ndk-module-extract.awk b/build-aux/ndk-module-extract.awk index 681f20237fa..6ff30973d67 100644 --- a/build-aux/ndk-module-extract.awk +++ b/build-aux/ndk-module-extract.awk @@ -10,7 +10,7 @@ if (imports && ++imports > 2) { if (!match ($0, /^End Imports$/)) - makefile_imports = makefile_imports " " $1 + makefile_imports = makefile_imports " " $0 } else if (!match ($0, /^End$/) && !match ($0, /^Building.+$/)) { diff --git a/configure.ac b/configure.ac index aaee65016b2..35e71ecb93c 100644 --- a/configure.ac +++ b/configure.ac @@ -1061,6 +1061,7 @@ package will likely install on older systems but crash on startup.]) passthrough="$passthrough --with-jpeg=$with_jpeg" passthrough="$passthrough --with-xml2=$with_xml2" passthrough="$passthrough --with-sqlite3=$with_sqlite3" + passthrough="$passthrough --with-gnutls=$with_gnutls" AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" \ ANDROID_SDK="$android_sdk" android_abi=$android_abi \ @@ -1131,6 +1132,7 @@ if test "$ANDROID" = "yes"; then with_jpeg=no with_xml2=no with_sqlite3=no + with_gnutls=no fi with_rsvg=no @@ -1146,7 +1148,6 @@ if test "$ANDROID" = "yes"; then with_dbus=no with_gsettings=no with_selinux=no - with_gnutls=no with_modules=no with_threads=no @@ -7029,6 +7030,38 @@ gl_INIT CFLAGS=$SAVE_CFLAGS LIBS=$SAVE_LIBS +# Set up libgmp on Android. Make sure to override what gnulib has +# found. +LIBGMP_CFLAGS= +if test "$REALLY_ANDROID" == "yes" && test "$with_libgmp" != "no"; then + HAVE_LIBGMP=no + ndk_SEARCH_MODULE([libgmp], [LIBGMP], [HAVE_LIBGMP=yes]) + + if test "$HAVE_LIBGMP" = "yes"; then + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $LIBGMP_CFLAGS" + unset ac_cv_header_gmp_h + unset ac_cv_header_gmp_gmp_h + AC_CHECK_HEADERS([gmp.h gmp/gmp.h], [break]) + CFLAGS="$SAVE_CFLAGS" + GL_GENERATE_GMP_H= + GL_GENERATE_GMP_H_CONDITION= + GL_GENERATE_GMP_GMP_H= + GL_GENERATE_GMP_GMP_H_CONDITION= + GL_GENERATE_MINI_GMP_H= + GL_GENERATE_MINI_GMP_H_CONDITION= + + if test "$ac_cv_header_gmp_h" != "no" \ + || test "$ac_cv_header_gmp_gmp_h" != "no"; then + HAVE_LIBGMP=yes + GL_GENERATE_GMP_H=false + LIBGMP="$LIBGMP_LIBS" + GMP_H= + fi + fi +fi +AC_SUBST([LIBGMP_CFLAGS]) + # timer_getoverrun needs the same library as timer_settime OLD_LIBS=$LIBS LIBS="$LIB_TIMER_TIME $LIBS" diff --git a/cross/ndk-build/Makefile.in b/cross/ndk-build/Makefile.in index 125c825e506..774858697b4 100644 --- a/cross/ndk-build/Makefile.in +++ b/cross/ndk-build/Makefile.in @@ -118,6 +118,8 @@ LOCAL_EXPORT_CPPFLAGS := LOCAL_EXPORT_C_INCLUDES := LOCAL_EXPORT_LDFLAGS := LOCAL_EXPORT_LDLIBS := +LOCAL_ASM_RULE_DEFINED := +LOCAL_ASM_RULE := # Now load Android.mk. include $(1) diff --git a/cross/ndk-build/ndk-build-shared-library.mk b/cross/ndk-build/ndk-build-shared-library.mk index b69641ba9b0..f169e89d058 100644 --- a/cross/ndk-build/ndk-build-shared-library.mk +++ b/cross/ndk-build/ndk-build-shared-library.mk @@ -35,7 +35,7 @@ define single-object-target ifeq (x$(suffix $(1)),x.c) $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) - $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) + $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) $(call LOCAL_C_ADDITIONAL_FLAGS,$(1)) else ifeq (x$(suffix $(1)),x.$(or $(LOCAL_CPP_EXTENSION),cpp)) @@ -52,6 +52,13 @@ $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) else ifneq (x$(suffix $(1)),x.asm) $$(error Unsupported suffix: $(suffix $(1))) +else +ifneq (x$(LOCAL_ASM_RULE_DEFINED),x) +# Call this function to define a rule that will generate $(1) from +# $(2), a ``.asm'' file. This is an Emacs extension. + +$(call LOCAL_ASM_RULE,$(call objname,$(LOCAL_MODULE),$(basename $(1))),$(LOCAL_PATH)/$(strip $(1))) + else ifeq ($(findstring x86,$(NDK_BUILD_ARCH)),) $$(error Trying to build nasm file on non-Intel platform!) @@ -65,6 +72,7 @@ endif endif endif endif +endif ALL_OBJECT_FILES$(LOCAL_MODULE) += $(call objname,$(LOCAL_MODULE),$(basename $(1))) @@ -125,11 +133,11 @@ $(foreach source,$(ALL_SOURCE_FILES),$(eval $(call single-object-target,$(source # Now define the rule to build the shared library. Shared libraries # link with all of the archive files from the static libraries on -# which they depend. +# which they depend, and also any shared libraries they depend on. define define-module-rule -$(LOCAL_MODULE_FILENAME): $(ALL_OBJECT_FILES$(LOCAL_MODULE)) $(NDK_LOCAL_A_NAMES_$(LOCAL_MODULE)) $(NDK_WHOLE_A_NAMES_$(LOCAL_MODULE)) - $(NDK_BUILD_CC) $(1) $(2) -o $$@ -shared $(NDK_LDFLAGS$(LOCAL_MODULE)) $(NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)) $(NDK_SO_DEFAULT_LDFLAGS) +$(LOCAL_MODULE_FILENAME): $(ALL_OBJECT_FILES$(LOCAL_MODULE)) $(NDK_LOCAL_A_NAMES_$(LOCAL_MODULE)) $(NDK_WHOLE_A_NAMES_$(LOCAL_MODULE)) $(NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE)) + $(NDK_BUILD_CC) $(1) $(2) -o $$@ -shared $(NDK_LDFLAGS_$(LOCAL_MODULE)) $(NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)) $(NDK_SO_DEFAULT_LDFLAGS) $(foreach so,$(NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE)),-L $(abspath $(CURDIR)) -l:$(so)) endef NDK_WHOLE_ARCHIVE_PREFIX = -Wl,--whole-archive diff --git a/cross/ndk-build/ndk-build-static-library.mk b/cross/ndk-build/ndk-build-static-library.mk index 349b9242b1f..d5a8e93800d 100644 --- a/cross/ndk-build/ndk-build-static-library.mk +++ b/cross/ndk-build/ndk-build-static-library.mk @@ -27,7 +27,7 @@ define single-object-target ifeq (x$(suffix $(1)),x.c) $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) - $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) + $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) $(call LOCAL_C_ADDITIONAL_FLAGS,$(1)) else ifeq (x$(suffix $(1)),x.$(or $(LOCAL_CPP_EXTENSION),cpp)) @@ -44,6 +44,13 @@ $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) else ifneq (x$(suffix $(1)),x.asm) $$(error Unsupported suffix: $(suffix $(1))) +else +ifneq (x$(LOCAL_ASM_RULE_DEFINED),x) +# Call this function to define a rule that will generate $(1) from +# $(2), a ``.asm'' file. This is an Emacs extension. + +$(call LOCAL_ASM_RULE,$(call objname,$(LOCAL_MODULE),$(basename $(1))),$(LOCAL_PATH)/$(strip $(1))) + else ifeq ($(findstring x86,$(NDK_BUILD_ARCH)),) $$(error Trying to build nasm file on non-Intel platform!) @@ -57,6 +64,7 @@ endif endif endif endif +endif ALL_OBJECT_FILES$(LOCAL_MODULE) += $(call objname,$(LOCAL_MODULE),$(basename $(1))) endef diff --git a/cross/ndk-build/ndk-clear-vars.mk b/cross/ndk-build/ndk-clear-vars.mk index df2bf95fd5e..6377f373512 100644 --- a/cross/ndk-build/ndk-clear-vars.mk +++ b/cross/ndk-build/ndk-clear-vars.mk @@ -45,3 +45,8 @@ undefine LOCAL_EXPORT_LDLIBS undefine LOCAL_SRC_FILES_$(NDK_BUILD_ARCH) undefine LOCAL_ASFLAGS_$(NDK_BUILD_ARCH) undefine LOCAL_CFLAGS_$(NDK_BUILD_ARCH) + +# Emacs extensions! +undefine LOCAL_ASM_RULE_DEFINED +undefine LOCAL_ASM_RULE +undefine LOCAL_C_ADDITIONAL_FLAGS diff --git a/cross/ndk-build/ndk-resolve.mk b/cross/ndk-build/ndk-resolve.mk index c2e281c53ba..e2252ebdf8c 100644 --- a/cross/ndk-build/ndk-resolve.mk +++ b/cross/ndk-build/ndk-resolve.mk @@ -19,6 +19,9 @@ # variables, and then having those Makefiles include another makefile # which actually builds targets. +# List of system libraries to ignore. +NDK_SYSTEM_LIBRARIES = z libz libc c libdl dl stdc++ libstdc++ log liblog android libandroid + # Save information. NDK_LOCAL_PATH_$(LOCAL_MODULE) := $(LOCAL_PATH) NDK_LOCAL_STATIC_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES) @@ -28,6 +31,7 @@ NDK_LOCAL_EXPORT_CFLAGS_$(LOCAL_MODULE) := $(LOCAL_EXPORT_CFLAGS) NDK_LOCAL_EXPORT_C_INCLUDES_$(LOCAL_MODULE) := $(LOCAL_EXPORT_C_INCLUDES) $(LOCAL_EXPORT_C_INCLUDE_DIRS) NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) := NDK_WHOLE_A_NAMES_$(LOCAL_MODULE) := +NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE) := NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) := # List of all dependencies resolved for this module thus far. @@ -87,6 +91,14 @@ ifeq ($(strip $(1)),android) NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -landroid endif +ifeq ($(findstring $(1),$(NDK_SYSTEM_LIBRARIES))$(2)$(3),) +ifneq ($(findstring lib,$(1)),) +NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE) += $(1)_emacs.so +else +NDK_LOCAL_SO_NAMES_$(LOCAL_MODULE) += lib$(1)_emacs.so +endif +endif + ifneq ($(2),) ifneq ($(findstring lib,$(1)),) NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) += $(1).a diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi index d7a72e6203e..b306a63b6cb 100644 --- a/doc/emacs/input.texi +++ b/doc/emacs/input.texi @@ -46,6 +46,9 @@ References}), then Emacs will follow the link instead. or down, will result in Emacs scrolling the window contents in the direction where the tool moves. + If the tool is moved left or right, Emacs additionally scrolls the +window horizontally to follow (@pxref{Horizontal Scrolling}.) + @item @cindex dragging, touchscreens ``Dragging'', meaning to place a tool on the display and leave it diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index d377cf982ef..30eaf85805a 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -134,6 +134,10 @@ public class EmacsActivity extends Activity layout.addView (window.view); child.setConsumer (this); + /* If the window isn't no-focus-on-map, focus its view. */ + if (!child.getDontFocusOnMap ()) + window.view.requestFocus (); + /* If the activity is iconified, send that to the window. */ if (isPaused) window.noticeIconified (); @@ -233,7 +237,7 @@ public class EmacsActivity extends Activity isPaused = true; EmacsWindowAttachmentManager.MANAGER.noticeIconified (this); - super.onResume (); + super.onPause (); } @Override diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java index ed1db68f732..6cef7c37516 100644 --- a/java/org/gnu/emacs/EmacsPreferencesActivity.java +++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java @@ -116,6 +116,11 @@ public class EmacsPreferencesActivity extends Activity if (file.exists ()) file.delete (); + + /* Make sure to clear EmacsApplication.dumpFileName, or + starting Emacs without restarting this program will + make Emacs try to load a nonexistent dump file. */ + EmacsApplication.dumpFileName = null; } }); layout.addView (textView); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index a1953f683bd..730ed02d4f1 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -22,12 +22,16 @@ package org.gnu.emacs; import android.content.Context; import android.content.res.ColorStateList; +import android.text.InputType; + import android.view.ContextMenu; import android.view.View; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.graphics.Bitmap; @@ -566,6 +570,8 @@ public class EmacsView extends ViewGroup /* Collect the bitmap storage; it could be large. */ Runtime.getRuntime ().gc (); } + + super.onDetachedFromWindow (); } @Override @@ -581,6 +587,8 @@ public class EmacsView extends ViewGroup /* Now expose the view contents again. */ EmacsNative.sendExpose (this.window.handle, 0, 0, measuredWidth, measuredHeight); + + super.onAttachedToWindow (); } public void @@ -615,4 +623,17 @@ public class EmacsView extends ViewGroup drawingFinished = null; } } + + @Override + public InputConnection + onCreateInputConnection (EditorInfo info) + { + /* Make sure the input method never displays a full screen input + box that obscures Emacs. */ + info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; + + /* But don't return an InputConnection, in order to force the on + screen keyboard to work correctly. */ + return null; + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 39eaf2fff80..5c2b77b0125 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -124,9 +124,9 @@ public class EmacsWindow extends EmacsHandleObject there is no such window manager. */ private WindowManager windowManager; - /* The time of the last KEYCODE_VOLUME_DOWN press. This is used to - quit Emacs. */ - private long lastVolumeButtonPress; + /* The time of the last KEYCODE_VOLUME_DOWN release. This is used + to quit Emacs. */ + private long lastVolumeButtonRelease; public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, @@ -517,7 +517,6 @@ public class EmacsWindow extends EmacsHandleObject onKeyDown (int keyCode, KeyEvent event) { int state, state_1; - long time; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -549,26 +548,13 @@ public class EmacsWindow extends EmacsHandleObject state, keyCode, event.getUnicodeChar (state_1)); lastModifiers = state; - - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) - { - /* Check if this volume down press should quit Emacs. - Most Android devices have no physical keyboard, so it - is unreasonably hard to press C-g. */ - - time = event.getEventTime (); - - if (lastVolumeButtonPress - time < 350) - EmacsNative.quit (); - - lastVolumeButtonPress = time; - } } public void onKeyUp (int keyCode, KeyEvent event) { int state, state_1; + long time; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -600,6 +586,20 @@ public class EmacsWindow extends EmacsHandleObject state, keyCode, event.getUnicodeChar (state_1)); lastModifiers = state; + + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) + { + /* Check if this volume down press should quit Emacs. + Most Android devices have no physical keyboard, so it + is unreasonably hard to press C-g. */ + + time = event.getEventTime (); + + if (time - lastVolumeButtonRelease < 350) + EmacsNative.quit (); + + lastVolumeButtonRelease = time; + } } public void diff --git a/lisp/isearch.el b/lisp/isearch.el index bb46c89ae20..4d2cb5edb47 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -1237,6 +1237,8 @@ does not return to the calling function until the search is completed. To behave this way it enters a recursive edit and exits it when done isearching. +Also display the on-screen keyboard if necessary. + The arg REGEXP-FUNCTION, if non-nil, should be a function. It is used to set the value of `isearch-regexp-function'." @@ -1332,6 +1334,14 @@ used to set the value of `isearch-regexp-function'." (add-hook 'mouse-leave-buffer-hook 'isearch-mouse-leave-buffer) (add-hook 'kbd-macro-termination-hook 'isearch-done) + ;; If the keyboard is not up and the last event did not come from + ;; a keyboard, bring it up so that the user can type. + (when (or (not last-event-frame) + (not (eq (device-class last-event-frame + last-event-device) + 'keyboard))) + (frame-toggle-on-screen-keyboard (selected-frame) nil)) + ;; isearch-mode can be made modal (in the sense of not returning to ;; the calling function until searching is completed) by entering ;; a recursive-edit and exiting it when done isearching. diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el index 855eebcc43f..fa98cbe5db9 100644 --- a/lisp/touch-screen.el +++ b/lisp/touch-screen.el @@ -30,14 +30,14 @@ (defvar touch-screen-current-tool nil "The touch point currently being tracked, or nil. -If non-nil, this is a list of six elements: the ID of the touch +If non-nil, this is a list of nine elements: the ID of the touch point being tracked, the window where the touch began, a cons containing the last known position of the touch point, relative to that window, a field used to store data while tracking the touch point, the initial position of the touchpoint, and another -field to used store data while tracking the touch point. See -`touch-screen-handle-point-update' for the meanings of the fourth -element.") +four fields to used store data while tracking the touch point. +See `touch-screen-handle-point-update' for the meanings of the +fourth element.") (defvar touch-screen-set-point-commands '(mouse-set-point) "List of commands known to set the point. @@ -92,8 +92,11 @@ to the frame that they belong in." (- (cdr xy) (cadr edges))))))) (defun touch-screen-handle-scroll (dx dy) - "Scroll the display assuming that a touch point has moved by DX and DY." - (ignore dx) + "Scroll the display assuming that a touch point has moved by DX and DY. +Perform vertical scrolling by DY, using `pixel-scroll-precision' +if `touch-screen-precision-scroll' is enabled. Next, perform +horizontal scrolling according to the movement in DX." + ;; Perform vertical scrolling first. (if touch-screen-precision-scroll (if (> dy 0) (pixel-scroll-precision-scroll-down-page dy) @@ -102,7 +105,8 @@ to the frame that they belong in." ;; in which the scrolling is taking place. Load the accumulator ;; value. (let ((accumulator (or (nth 5 touch-screen-current-tool) 0)) - (window (cadr touch-screen-current-tool))) + (window (cadr touch-screen-current-tool)) + (lines-vscrolled (or (nth 7 touch-screen-current-tool) 0))) (setq accumulator (+ accumulator dy)) ; Add dy. ;; Figure out how much it has scrolled and how much remains on ;; the top or bottom of the window. @@ -113,6 +117,7 @@ to the frame that they belong in." (progn (setq accumulator (+ accumulator line-height)) (scroll-down 1) + (setq lines-vscrolled (1+ lines-vscrolled)) (when (not (zerop accumulator)) ;; If there is still an outstanding amount to ;; scroll, do this again. @@ -120,15 +125,58 @@ to the frame that they belong in." (when (and (> accumulator 0) (>= accumulator line-height)) (setq accumulator (- accumulator line-height)) - (scroll-up 1) + (scroll-up 1) + (setq lines-vscrolled (1+ lines-vscrolled)) + (when (not (zerop accumulator)) + ;; If there is still an outstanding amount to + ;; scroll, do this again. + (throw 'again t))))) + ;; Scrolling is done. Move the accumulator back to + ;; touch-screen-current-tool and break out of the loop. + (setcar (nthcdr 5 touch-screen-current-tool) accumulator) + (setcar (nthcdr 7 touch-screen-current-tool) + lines-vscrolled) + nil)))) + + ;; Perform horizontal scrolling by DX, as this does not signal at + ;; the beginning of the buffer. + (let ((accumulator (or (nth 6 touch-screen-current-tool) 0)) + (window (cadr touch-screen-current-tool)) + (lines-vscrolled (or (nth 7 touch-screen-current-tool) 0)) + (lines-hscrolled (or (nth 8 touch-screen-current-tool) 0))) + (setq accumulator (+ accumulator dx)) ; Add dx; + ;; Figure out how much it has scrolled and how much remains on the + ;; left or right of the window. If a line has already been + ;; vscrolled but no hscrolling has happened, don't hscroll, as + ;; otherwise it is too easy to hscroll by accident. + (if (or (> lines-hscrolled 0) + (< lines-vscrolled 1)) + (while (catch 'again + (let* ((column-width (frame-char-width (window-frame window)))) + (if (and (< accumulator 0) + (>= (- accumulator) column-width)) + (progn + (setq accumulator (+ accumulator column-width)) + (scroll-right 1) + (setq lines-hscrolled (1+ lines-hscrolled)) + (when (not (zerop accumulator)) + ;; If there is still an outstanding amount to + ;; scroll, do this again. + (throw 'again t))) + (when (and (> accumulator 0) + (>= accumulator column-width)) + (setq accumulator (- accumulator column-width)) + (scroll-left 1) + (setq lines-hscrolled (1+ lines-hscrolled)) (when (not (zerop accumulator)) ;; If there is still an outstanding amount to ;; scroll, do this again. (throw 'again t))))) - ;; Scrolling is done. Move the accumulator back to - ;; touch-screen-current-tool and break out of the loop. - (setcar (nthcdr 5 touch-screen-current-tool) accumulator) - nil))))) + ;; Scrolling is done. Move the accumulator back to + ;; touch-screen-current-tool and break out of the loop. + (setcar (nthcdr 6 touch-screen-current-tool) accumulator) + (setcar (nthcdr 8 touch-screen-current-tool) lines-hscrolled) + nil))))) (defun touch-screen-handle-timeout (arg) "Start the touch screen timeout or handle it depending on ARG. @@ -377,7 +425,8 @@ touchscreen-end event." (list touchpoint (posn-window position) (posn-x-y position) - nil position nil))) + nil position nil nil + nil nil))) ;; Start the long-press timer. (touch-screen-handle-timeout nil))) ((eq (car event) 'touchscreen-update) diff --git a/src/Makefile.in b/src/Makefile.in index 60bb9c8f3a3..0921f6f1f96 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -391,6 +391,8 @@ ANDROID_LIBS = @ANDROID_LIBS@ ANDROID_LDFLAGS = @ANDROID_LDFLAGS@ ANDROID_CFLAGS = @ANDROID_CFLAGS@ +LIBGMP_CFLAGS = @LIBGMP_CFLAGS@ + DUMPING=@DUMPING@ CHECK_STRUCTS = @CHECK_STRUCTS@ HAVE_PDUMPER = @HAVE_PDUMPER@ @@ -433,7 +435,8 @@ EMACS_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \ $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) $(XSYNC_CFLAGS) $(TREE_SITTER_CFLAGS) \ $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \ $(WERROR_CFLAGS) $(HAIKU_CFLAGS) $(XCOMPOSITE_CFLAGS) $(XSHAPE_CFLAGS) \ - $(ANDROID_CFLAGS) $(GIF_CFLAGS) $(JPEG_CFLAGS) $(SQLITE3_CFLAGS) + $(ANDROID_CFLAGS) $(GIF_CFLAGS) $(JPEG_CFLAGS) $(SQLITE3_CFLAGS) \ + $(LIBGMP_CFLAGS) ALL_CFLAGS = $(EMACS_CFLAGS) $(WARN_CFLAGS) $(CFLAGS) ALL_OBJC_CFLAGS = $(EMACS_CFLAGS) \ $(filter-out $(NON_OBJC_CFLAGS),$(WARN_CFLAGS)) $(CFLAGS) \ diff --git a/src/android.c b/src/android.c index 379b54a65be..021bea1fc2f 100644 --- a/src/android.c +++ b/src/android.c @@ -334,12 +334,11 @@ android_run_select_thread (void *data) the event queue lock, because android_select will always wait for this to complete before returning. */ android_pselect_completed = true; - pthread_cond_signal (&event_queue.read_var); + pthread_cond_broadcast (&event_queue.read_var); /* Read a single byte from the select pipe. */ read (select_pipe[0], &byte, 1); - /* Signal the Emacs thread that pselect is done. If read_var was signaled by android_write_event, event_queue.mutex could still be locked, so this must come before. */ @@ -378,7 +377,7 @@ android_run_select_thread (void *data) the event queue lock, because android_select will always wait for this to complete before returning. */ android_pselect_completed = true; - pthread_cond_signal (&event_queue.read_var); + pthread_cond_broadcast (&event_queue.read_var); if (rc != -1 || errno != EINTR) /* Now, wait for SIGUSR1, unless pselect was interrupted and @@ -559,7 +558,7 @@ android_write_event (union android_event *event) container->last->next = container; container->event = *event; event_queue.num_events++; - pthread_cond_signal (&event_queue.read_var); + pthread_cond_broadcast (&event_queue.read_var); pthread_mutex_unlock (&event_queue.mutex); /* Now set pending_signals to true. This allows C-g to be handled -- cgit v1.3 From fc82efc1fe99415d60d9aa06f2ff8e7e92566870 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 6 Feb 2023 22:00:08 +0800 Subject: Update Android port * java/AndroidManifest.xml.in: Prevent the Emacs activity from being overlayed by the emacsclient wrapper. * java/org/gnu/emacs/EmacsOpenActivity.java (run): Likewise. (onCreate): Set an appropriate theme on ICS and up. * java/org/gnu/emacs/EmacsWindow.java (onTouchEvent): Handle ACTION_CANCEL correctly. --- java/AndroidManifest.xml.in | 5 ++++- java/org/gnu/emacs/EmacsOpenActivity.java | 9 ++++++++- java/org/gnu/emacs/EmacsWindow.java | 5 ++++- 3 files changed, 16 insertions(+), 3 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 923c5a005d5..3c9e30713b6 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -72,7 +72,7 @@ along with GNU Emacs. If not, see . --> android:extractNativeLibs="true"> @@ -84,6 +84,8 @@ along with GNU Emacs. If not, see . --> @@ -137,6 +139,7 @@ along with GNU Emacs. If not, see . --> + diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index 268a9abd7b1..e987e067a73 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -184,7 +184,9 @@ public class EmacsOpenActivity extends Activity intent = new Intent (EmacsOpenActivity.this, EmacsActivity.class); - intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); + + /* This means only an existing frame will be displayed. */ + intent.addFlags (Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity (intent); EmacsOpenActivity.this.finish (); @@ -285,6 +287,11 @@ public class EmacsOpenActivity extends Activity return; } + /* Set an appropriate theme. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + setTheme (android.R.style.Theme_DeviceDefault); + /* Now see if the action specified is supported by Emacs. */ if (action.equals ("android.intent.action.VIEW") diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 5c2b77b0125..e921b972c2c 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -794,7 +794,10 @@ public class EmacsWindow extends EmacsHandleObject case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: - /* Touch up event. */ + case MotionEvent.ACTION_CANCEL: + /* Touch up event. Android documentation says ACTION_CANCEL + should be treated as more or less equivalent to ACTION_UP, + so that is what is done here. */ EmacsNative.sendTouchUp (this.handle, (int) event.getX (index), (int) event.getY (index), event.getEventTime (), pointerID); -- cgit v1.3 From a1941cd7a7dc9a6f6b7239ec7d4bd3bdf5d55fc9 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Feb 2023 18:57:51 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Windowing): Remove yet another limitation. * java/debug.sh: Make this work on systems which prohibit attaching to app processes from adbd. * java/org/gnu/emacs/EmacsCopyArea.java (perform): Avoid creating copies whenever possible. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): Remove SurfaceView based implementation and use manual double buffering with invalidate instead. * java/org/gnu/emacs/EmacsView.java (EmacsView, handleDirtyBitmap) (raise, lower, onDetachedFromWindow): Adjust accordingly. * java/org/gnu/emacs/EmacsWindow.java (windowUpdated): Remove function. * src/sfntfont.c (sfntfont_open): Set font->max_width correctly. --- doc/emacs/android.texi | 5 - java/debug.sh | 13 ++- java/org/gnu/emacs/EmacsCopyArea.java | 24 ++++- java/org/gnu/emacs/EmacsSurfaceView.java | 159 ++++++++++++++----------------- java/org/gnu/emacs/EmacsView.java | 93 +++--------------- java/org/gnu/emacs/EmacsWindow.java | 23 ----- src/sfntfont.c | 4 + 7 files changed, 116 insertions(+), 205 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 5fdf5c16b4e..5bbb2245a0c 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -418,11 +418,6 @@ Due to the unusual nature of the Android windowing environment, Emacs only supports a limited subset of GUI features. Here is a list of known limitations, and features which are not implemented: -@itemize @bullet -@item -The functions @code{raise-frame} and @code{lower-frame} are -non-functional, because of bugs in the window system. - @item Scroll bars are not supported, as they are close to useless on Android devices. diff --git a/java/debug.sh b/java/debug.sh index 7008664c049..2e95f9738c7 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -267,10 +267,14 @@ if [ -z "$gdbserver" ]; then gdbserver_bin=/system/bin/gdbserver else gdbserver_bin=/data/local/tmp/gdbserver + gdbserver_cat="cat $gdbserver_bin | run-as $package sh -c \ + \"tee gdbserver > /dev/null\"" # Upload the specified gdbserver binary to the device. adb -s $device push "$gdbserver" "$gdbserver_bin" - adb -s $device shell chmod +x "$gdbserver_bin" + # Copy it to the user directory. + adb -s $device shell "$gdbserver_cat" + adb -s $device shell "run-as $package chmod +x gdbserver" fi # Now start gdbserver on the device asynchronously. @@ -286,10 +290,9 @@ if [ -z "$gdbserver" ]; then else # Normally the program cannot access $gdbserver_bin when it is # placed in /data/local/tmp. - adb -s $device shell $gdbserver_bin --once \ - "+/data/local/tmp/debug.$package.socket" \ - --attach $pid >&5 & - gdb_socket="localfilesystem:/data/local/tmp/debug.$package.socket" + adb -s $device shell run-as $package "./gdbserver" --once \ + "0.0.0.0:7654" --attach $pid >&5 & + gdb_socket="tcp:7654" fi # Wait until gdbserver successfully runs. diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index f8974e17c2e..11dc22e0456 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -110,11 +110,25 @@ public class EmacsCopyArea if (gc.clip_mask == null) { - bitmap = Bitmap.createBitmap (srcBitmap, - src_x, src_y, width, - height); - canvas.drawBitmap (bitmap, null, rect, paint); - bitmap.recycle (); + if (source == destination) + { + /* Create a copy of the bitmap, since Android can't handle + overlapping copies. */ + bitmap = Bitmap.createBitmap (srcBitmap, + src_x, src_y, width, + height); + canvas.drawBitmap (bitmap, null, rect, paint); + bitmap.recycle (); + } + else + { + /* But here the bitmaps are known to not overlap, so avoid + that extra consing overhead. */ + + srcRect = new Rect (src_x, src_y, src_x + width, + src_y + height); + canvas.drawBitmap (srcBitmap, null, rect, paint); + } } else { diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index f6cb77bb2b8..e9bae623930 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -19,127 +19,114 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -import android.view.SurfaceView; -import android.view.SurfaceHolder; +import android.view.View; import android.os.Build; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.Paint; -import android.util.Log; +/* This originally extended SurfaceView. However, doing so proved to + be too slow, and Android's surface view keeps up to three of its + own back buffers, which use too much memory (up to 96 MB for a + single frame.) */ -public class EmacsSurfaceView extends SurfaceView +public class EmacsSurfaceView extends View { private static final String TAG = "EmacsSurfaceView"; - public Object surfaceChangeLock; - private boolean created; private EmacsView view; - - /* This is the callback used on Android 8 to 25. */ - - private class Callback implements SurfaceHolder.Callback - { - @Override - public void - surfaceChanged (SurfaceHolder holder, int format, - int width, int height) - { - Canvas canvas; - - Log.d (TAG, "surfaceChanged: " + view + ", "); - - view.swapBuffers (true); - } - - @Override - public void - surfaceCreated (SurfaceHolder holder) - { - synchronized (surfaceChangeLock) - { - Log.d (TAG, "surfaceCreated: " + view); - created = true; - } - - /* Drop the lock when doing this, or a deadlock can - result. */ - view.swapBuffers (true); - } - - @Override - public void - surfaceDestroyed (SurfaceHolder holder) - { - synchronized (surfaceChangeLock) - { - Log.d (TAG, "surfaceDestroyed: " + view); - created = false; - } - } - } + private Bitmap frontBuffer; + private Canvas bitmapCanvas; + private Bitmap bitmap; + private Paint bitmapPaint; public EmacsSurfaceView (final EmacsView view) { super (view.getContext ()); - this.surfaceChangeLock = new Object (); this.view = view; - - getHolder ().addCallback (new Callback ()); + this.bitmapPaint = new Paint (); } - public boolean - isCreated () + private void + copyToFrontBuffer (Rect damageRect) { - return created; + if (damageRect != null) + bitmapCanvas.drawBitmap (bitmap, damageRect, damageRect, + bitmapPaint); + else + bitmapCanvas.drawBitmap (bitmap, 0f, 0f, bitmapPaint); } - public Canvas - lockCanvas (Rect damage) + private void + reconfigureFrontBuffer (Bitmap bitmap) { - SurfaceHolder holder; - - holder = getHolder (); + /* First, remove the old front buffer. */ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + if (frontBuffer != null) { - damage.setEmpty (); - return holder.lockHardwareCanvas (); + frontBuffer.recycle (); + frontBuffer = null; + bitmapCanvas = null; } - return holder.lockCanvas (damage); - } + this.bitmap = bitmap; - @Override - protected void - onLayout (boolean changed, int left, int top, int right, - int bottom) - { - Log.d (TAG, ("onLayout: " + left + " " + top + " " + right - + " " + bottom + " -- " + changed + " visibility " - + getVisibility ())); - } + /* Next, create the new front buffer if necessary. */ - /* This method is only used during debugging when it seems damage - isn't working correctly. */ + if (bitmap != null && frontBuffer == null) + { + frontBuffer = Bitmap.createBitmap (bitmap.getWidth (), + bitmap.getHeight (), + Bitmap.Config.ARGB_8888, + false); + bitmapCanvas = new Canvas (frontBuffer); + + /* And copy over the bitmap contents. */ + copyToFrontBuffer (null); + } + else if (bitmap != null) + /* Just copy over the bitmap contents. */ + copyToFrontBuffer (null); + } - public Canvas - lockCanvas () + public synchronized void + setBitmap (Bitmap bitmap, Rect damageRect) { - SurfaceHolder holder; + if (bitmap != this.bitmap) + reconfigureFrontBuffer (bitmap); + else if (bitmap != null) + copyToFrontBuffer (damageRect); - holder = getHolder (); - return holder.lockCanvas (); + if (bitmap != null) + { + /* In newer versions of Android, the invalid rectangle is + supposedly internally calculated by the system. How that + is done is unknown, but calling `invalidateRect' is now + deprecated. + + Fortunately, nobody has deprecated the version of + `postInvalidate' that accepts a dirty rectangle. */ + + if (damageRect != null) + postInvalidate (damageRect.left, damageRect.top, + damageRect.right, damageRect.bottom); + else + postInvalidate (); + } } - public void - unlockCanvasAndPost (Canvas canvas) + @Override + public synchronized void + onDraw (Canvas canvas) { - SurfaceHolder holder; + /* Paint the view's bitmap; the bitmap might be recycled right + now. */ - holder = getHolder (); - holder.unlockCanvasAndPost (canvas); + if (frontBuffer != null) + canvas.drawBitmap (frontBuffer, 0f, 0f, bitmapPaint); } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index fac11870ebf..0416301101c 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -103,14 +103,6 @@ public class EmacsView extends ViewGroup displayed whenever possible. */ public boolean isCurrentlyTextEditor; - /* An empty rectangle. */ - public static final Rect emptyRect; - - static - { - emptyRect = new Rect (); - }; - public EmacsView (EmacsWindow window) { @@ -127,14 +119,8 @@ public class EmacsView extends ViewGroup /* Create the surface view. */ this.surfaceView = new EmacsSurfaceView (this); - this.surfaceView.setZOrderMediaOverlay (true); addView (this.surfaceView); - /* Not sure exactly what this does but it makes things magically - work. Why is something as simple as XRaiseWindow so involved - on Android? */ - setChildrenDrawingOrderEnabled (true); - /* Get rid of the default focus highlight. */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) setDefaultFocusHighlightEnabled (false); @@ -191,8 +177,12 @@ public class EmacsView extends ViewGroup bitmapDirty = false; /* Explicitly free the old bitmap's memory. */ + if (oldBitmap != null) - oldBitmap.recycle (); + { + oldBitmap.recycle (); + surfaceView.setBitmap (null, null); + } /* Some Android versions still don't free the bitmap until the next GC. */ @@ -342,67 +332,27 @@ public class EmacsView extends ViewGroup thread. */ public void - swapBuffers (boolean force) + swapBuffers () { Canvas canvas; Rect damageRect; Bitmap bitmap; - /* Code must always take damageRegion, and then surfaceChangeLock, - never the other way around! */ + damageRect = null; synchronized (damageRegion) { - if (!force && damageRegion.isEmpty ()) + if (damageRegion.isEmpty ()) return; bitmap = getBitmap (); - /* Emacs must take the following lock to ensure the access to the - canvas occurs with the surface created. Otherwise, Android - will throttle calls to lockCanvas. */ - - synchronized (surfaceView.surfaceChangeLock) - { - if (!force) - damageRect = damageRegion.getBounds (); - else - damageRect = emptyRect; - - if (!surfaceView.isCreated ()) - return; - - if (bitmap == null) - return; - - /* Lock the canvas with the specified damage. */ - canvas = surfaceView.lockCanvas (damageRect); - - /* Return if locking the canvas failed. */ - if (canvas == null) - return; - - /* 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 (); - } + /* Transfer the bitmap to the surface view, then invalidate + it. */ + surfaceView.setBitmap (bitmap, damageRect); } } - public void - swapBuffers () - { - swapBuffers (false); - } - @Override public boolean onKeyDown (int keyCode, KeyEvent event) @@ -486,16 +436,6 @@ public class EmacsView extends ViewGroup return; parent.bringChildToFront (this); - - /* Yes, all of this is really necessary! */ - parent.requestLayout (); - parent.invalidate (); - requestLayout (); - invalidate (); - - /* The surface view must be destroyed and recreated. */ - removeView (surfaceView); - addView (surfaceView, 0); } public void @@ -511,16 +451,6 @@ public class EmacsView extends ViewGroup return; parent.moveChildToBack (this); - - /* Yes, all of this is really necessary! */ - parent.requestLayout (); - parent.invalidate (); - requestLayout (); - invalidate (); - - /* The surface view must be removed and attached again. */ - removeView (surfaceView); - addView (surfaceView, 0); } @Override @@ -574,6 +504,7 @@ public class EmacsView extends ViewGroup bitmap.recycle (); bitmap = null; canvas = null; + surfaceView.setBitmap (null, null); /* Collect the bitmap storage; it could be large. */ Runtime.getRuntime ().gc (); diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index e921b972c2c..9e2f2f53270 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -57,12 +57,6 @@ import android.os.Build; Views are also drawables, meaning they can accept drawing requests. */ -/* Help wanted. What does not work includes `EmacsView.raise', - `EmacsView.lower', reparenting a window onto another window. - - All three are likely undocumented restrictions within - EmacsSurface. */ - public class EmacsWindow extends EmacsHandleObject implements EmacsDrawable { @@ -1111,21 +1105,4 @@ public class EmacsWindow extends EmacsHandleObject } }); } - - /* Notice that outstanding configure events have been processed. - SERIAL is checked in the UI thread to verify that no new - configure events have been generated in the mean time. */ - - public void - windowUpdated (final long serial) - { - EmacsService.SERVICE.runOnUiThread (new Runnable () { - @Override - public void - run () - { - view.windowUpdated (serial); - } - }); - } }; diff --git a/src/sfntfont.c b/src/sfntfont.c index cc084c79307..a5ed54394a2 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -2152,6 +2152,10 @@ sfntfont_open (struct frame *f, Lisp_Object font_entity, * pixel_size * 1.0 / font_info->head->units_per_em); font->height = font->ascent + font->descent; + /* Set font->max_width to the maximum advance width. */ + font->max_width = (font_info->hhea->advance_width_max + * pixel_size * 1.0 / font_info->head->units_per_em); + /* Set generic attributes such as type and style. */ ASET (font_object, FONT_TYPE_INDEX, sfnt_vendor_name); -- cgit v1.3 From 0198b8cffd82893412c738dae8e50c45a99286f1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 12 Feb 2023 20:32:25 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Environment): Document notifications permission. * java/org/gnu/emacs/EmacsEditable.java (EmacsEditable): * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): New files. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Load library dependencies in a less verbose fashion. * java/org/gnu/emacs/EmacsView.java (EmacsView): Make imManager public. (onCreateInputConnection): Set InputType to TYPE_NULL for now. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, onKeyDown) (onKeyUp, getEventUnicodeChar): Correctly handle key events with strings. * lisp/term/android-win.el (android-clear-preedit-text) (android-preedit-text): New special event handlers. * src/android.c (struct android_emacs_window): Add function lookup_string. (android_init_emacs_window): Adjust accordingly. (android_wc_lookup_string): New function. * src/androidgui.h (struct android_key_event): Improve commentary. (enum android_lookup_status): New enum. * src/androidterm.c (handle_one_android_event): Synchronize IM lookup code with X. * src/coding.c (from_unicode_buffer): Implement on Android. * src/coding.h: * src/sfnt.c: Fix commentary. --- doc/emacs/android.texi | 2 + java/org/gnu/emacs/EmacsEditable.java | 300 +++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsInputConnection.java | 175 ++++++++++++++++ java/org/gnu/emacs/EmacsNative.java | 166 +++------------ java/org/gnu/emacs/EmacsView.java | 7 +- java/org/gnu/emacs/EmacsWindow.java | 96 ++++++++- lisp/term/android-win.el | 36 ++++ src/android.c | 111 ++++++++++ src/androidgui.h | 20 +- src/androidterm.c | 103 +++++++-- src/coding.c | 36 +++- src/coding.h | 4 +- src/sfnt.c | 11 +- 13 files changed, 880 insertions(+), 187 deletions(-) create mode 100644 java/org/gnu/emacs/EmacsEditable.java create mode 100644 java/org/gnu/emacs/EmacsInputConnection.java (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 8df26560f02..308bd70c59c 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -334,6 +334,8 @@ permissions upon installation: @code{android.permission.TRANSMIT_IR} @item @code{android.permission.WAKE_LOCK} +@item +@code{android.permission.POST_NOTIFICATIONS} @end itemize Other permissions must be granted by the user through the system diff --git a/java/org/gnu/emacs/EmacsEditable.java b/java/org/gnu/emacs/EmacsEditable.java new file mode 100644 index 00000000000..79af65a6ccd --- /dev/null +++ b/java/org/gnu/emacs/EmacsEditable.java @@ -0,0 +1,300 @@ +/* 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 . */ + +package org.gnu.emacs; + +import android.text.InputFilter; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.SpanWatcher; +import android.text.Selection; + +import android.content.Context; + +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; + +import android.text.Spannable; + +import android.util.Log; + +import android.os.Build; + +/* Android input methods insist on having access to buffer contents. + Since Emacs is not designed like ``any other Android text editor'', + that is not possible. + + This file provides a fake editing buffer that is designed to weasel + as much information as possible out of an input method, without + actually providing buffer contents to Emacs. + + The basic idea is to have the fake editing buffer be initially + empty. + + When the input method inserts composed text, it sets a flag. + Updates to the buffer while the flag is set are sent to Emacs to be + displayed as ``preedit text''. + + Once some heuristics decide that composition has been completed, + the composed text is sent to Emacs, and the text that was inserted + in this editing buffer is erased. */ + +public class EmacsEditable extends SpannableStringBuilder + implements SpanWatcher +{ + private static final String TAG = "EmacsEditable"; + + /* Whether or not composition is currently in progress. */ + private boolean isComposing; + + /* The associated input connection. */ + private EmacsInputConnection connection; + + /* The associated IM manager. */ + private InputMethodManager imManager; + + /* Any extracted text an input method may be monitoring. */ + private ExtractedText extractedText; + + /* The corresponding text request. */ + private ExtractedTextRequest extractRequest; + + /* The number of nested batch edits. */ + private int batchEditCount; + + /* Whether or not invalidateInput should be called upon batch edits + ending. */ + private boolean pendingInvalidate; + + /* The ``composing span'' indicating the bounds of an ongoing + character composition. */ + private Object composingSpan; + + public + EmacsEditable (EmacsInputConnection connection) + { + /* Initialize the editable with one initial space, so backspace + always works. */ + super (); + + Object tem; + Context context; + + this.connection = connection; + + context = connection.view.getContext (); + tem = context.getSystemService (Context.INPUT_METHOD_SERVICE); + imManager = (InputMethodManager) tem; + + /* To watch for changes to text properties on Android, you + add... a text property. */ + setSpan (this, 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + public void + endBatchEdit () + { + if (batchEditCount < 1) + return; + + if (--batchEditCount == 0 && pendingInvalidate) + invalidateInput (); + } + + public void + beginBatchEdit () + { + ++batchEditCount; + } + + public void + setExtractedTextAndRequest (ExtractedText text, + ExtractedTextRequest request, + boolean monitor) + { + /* Extract the text. If monitor is set, also record it as the + text that is currently being extracted. */ + + text.startOffset = 0; + text.selectionStart = Selection.getSelectionStart (this); + text.selectionEnd = Selection.getSelectionStart (this); + text.text = this; + + if (monitor) + { + extractedText = text; + extractRequest = request; + } + } + + public void + compositionStart () + { + isComposing = true; + } + + public void + compositionEnd () + { + isComposing = false; + sendComposingText (null); + } + + private void + sendComposingText (String string) + { + EmacsWindow window; + long time, serial; + + window = connection.view.window; + + if (window.isDestroyed ()) + return; + + time = System.currentTimeMillis (); + + /* A composition event is simply a special key event with a + keycode of -1. */ + + synchronized (window.eventStrings) + { + serial + = EmacsNative.sendKeyPress (window.handle, time, 0, -1, -1); + + /* Save the string so that android_lookup_string can find + it. */ + if (string != null) + window.saveUnicodeString ((int) serial, string); + } + } + + private void + invalidateInput () + { + int start, end, composingSpanStart, composingSpanEnd; + + if (batchEditCount > 0) + { + Log.d (TAG, "invalidateInput: deferring for batch edit"); + pendingInvalidate = true; + return; + } + + pendingInvalidate = false; + + start = Selection.getSelectionStart (this); + end = Selection.getSelectionEnd (this); + + if (composingSpan != null) + { + composingSpanStart = getSpanStart (composingSpan); + composingSpanEnd = getSpanEnd (composingSpan); + } + else + { + composingSpanStart = -1; + composingSpanEnd = -1; + } + + Log.d (TAG, "invalidateInput: now " + start + ", " + end); + + /* Tell the input method that the cursor changed. */ + imManager.updateSelection (connection.view, start, end, + composingSpanStart, + composingSpanEnd); + + /* If there is any extracted text, tell the IME that it has + changed. */ + if (extractedText != null) + imManager.updateExtractedText (connection.view, + extractRequest.token, + extractedText); + } + + public SpannableStringBuilder + replace (int start, int end, CharSequence tb, int tbstart, + int tbend) + { + super.replace (start, end, tb, tbstart, tbend); + + /* If a change happens during composition, perform the change and + then send the text being composed. */ + + if (isComposing) + sendComposingText (toString ()); + + return this; + } + + private boolean + isSelectionSpan (Object span) + { + return ((Selection.SELECTION_START == span + || Selection.SELECTION_END == span) + && (getSpanFlags (span) + & Spanned.SPAN_INTERMEDIATE) == 0); + } + + @Override + public void + onSpanAdded (Spannable text, Object what, int start, int end) + { + Log.d (TAG, "onSpanAdded: " + text + " " + what + " " + + start + " " + end); + + /* Try to find the composing span. This isn't a public API. */ + + if (what.getClass ().getName ().contains ("ComposingText")) + composingSpan = what; + + if (isSelectionSpan (what)) + invalidateInput (); + } + + @Override + public void + onSpanChanged (Spannable text, Object what, int ostart, + int oend, int nstart, int nend) + { + Log.d (TAG, "onSpanChanged: " + text + " " + what + " " + + nstart + " " + nend); + + if (isSelectionSpan (what)) + invalidateInput (); + } + + @Override + public void + onSpanRemoved (Spannable text, Object what, + int start, int end) + { + Log.d (TAG, "onSpanRemoved: " + text + " " + what + " " + + start + " " + end); + + if (isSelectionSpan (what)) + invalidateInput (); + } + + public boolean + isInBatchEdit () + { + return batchEditCount > 0; + } +} diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java new file mode 100644 index 00000000000..897a393b984 --- /dev/null +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -0,0 +1,175 @@ +/* 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 . */ + +package org.gnu.emacs; + +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.SurroundingText; +import android.view.KeyEvent; + +import android.text.Editable; + +import android.util.Log; + +/* Android input methods, take number six. + + See EmacsEditable for more details. */ + +public class EmacsInputConnection extends BaseInputConnection +{ + private static final String TAG = "EmacsInputConnection"; + public EmacsView view; + private EmacsEditable editable; + + /* The length of the last string to be committed. */ + private int lastCommitLength; + + int currentLargeOffset; + + public + EmacsInputConnection (EmacsView view) + { + super (view, false); + this.view = view; + this.editable = new EmacsEditable (this); + } + + @Override + public Editable + getEditable () + { + return editable; + } + + @Override + public boolean + setComposingText (CharSequence text, int newCursorPosition) + { + editable.compositionStart (); + super.setComposingText (text, newCursorPosition); + return true; + } + + @Override + public boolean + setComposingRegion (int start, int end) + { + int i; + + if (lastCommitLength != 0) + { + Log.d (TAG, "Restarting composition for: " + lastCommitLength); + + for (i = 0; i < lastCommitLength; ++i) + sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_DEL)); + + lastCommitLength = 0; + } + + editable.compositionStart (); + super.setComposingRegion (start, end); + return true; + } + + @Override + public boolean + finishComposingText () + { + editable.compositionEnd (); + return super.finishComposingText (); + } + + @Override + public boolean + beginBatchEdit () + { + editable.beginBatchEdit (); + return super.beginBatchEdit (); + } + + @Override + public boolean + endBatchEdit () + { + editable.endBatchEdit (); + return super.endBatchEdit (); + } + + @Override + public boolean + commitText (CharSequence text, int newCursorPosition) + { + editable.compositionEnd (); + super.commitText (text, newCursorPosition); + + /* An observation is that input methods rarely recompose trailing + spaces. Avoid re-setting the commit length in that case. */ + + if (text.toString ().equals (" ")) + lastCommitLength += 1; + else + /* At this point, the editable is now empty. + + The input method may try to cancel the edit upon a subsequent + backspace by calling setComposingRegion with a region that is + the length of TEXT. + + Record this length in order to be able to send backspace + events to ``delete'' the text in that case. */ + lastCommitLength = text.length (); + + Log.d (TAG, "commitText: \"" + text + "\""); + + return true; + } + + /* Return a large offset, cycling through 100000, 30000, 0. + The offset is typically used to force the input method to update + its notion of ``surrounding text'', bypassing any caching that + it might have in progress. + + There must be another way to do this, but I can't find it. */ + + public int + largeSelectionOffset () + { + switch (currentLargeOffset) + { + case 0: + currentLargeOffset = 100000; + return 100000; + + case 100000: + currentLargeOffset = 30000; + return 30000; + + case 30000: + currentLargeOffset = 0; + return 0; + } + + currentLargeOffset = 0; + return -1; + } +} diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 939348ba420..f0219843d35 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -25,6 +25,10 @@ import android.content.res.AssetManager; public class EmacsNative { + /* List of native libraries that must be loaded during class + initialization. */ + private static final String[] libraryDeps; + /* Obtain the fingerprint of this build of Emacs. The fingerprint can be used to determine the dump file name. */ public static native String getFingerprint (); @@ -167,148 +171,26 @@ public class EmacsNative Every time you add a new shared library dependency to Emacs, please add it here as well. */ - try - { - System.loadLibrary ("png_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("selinux_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("crypto_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("pcre_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("packagelistparser_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("gnutls_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("gmp_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("nettle_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("p11-kit_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("tasn1_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("hogweed_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("jansson_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("jpeg_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("tiff_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("xml2_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ - } - - try - { - System.loadLibrary ("icuuc_emacs"); - } - catch (UnsatisfiedLinkError exception) - { - /* Ignore this exception. */ + libraryDeps = new String[] { "png_emacs", "selinux_emacs", + "crypto_emacs", "pcre_emacs", + "packagelistparser_emacs", + "gnutls_emacs", "gmp_emacs", + "nettle_emacs", "p11-kit_emacs", + "tasn1_emacs", "hogweed_emacs", + "jansson_emacs", "jpeg_emacs", + "tiff_emacs", "xml2_emacs", + "icuuc_emacs", }; + + for (String dependency : libraryDeps) + { + try + { + System.loadLibrary (dependency); + } + catch (UnsatisfiedLinkError exception) + { + /* Ignore this exception. */ + } } System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 4fc8104e31f..bc3716f6da8 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -94,7 +94,7 @@ public class EmacsView extends ViewGroup private long lastClipSerial; /* The InputMethodManager for this view's context. */ - private InputMethodManager imManager; + public InputMethodManager imManager; /* Whether or not this view is attached to a window. */ public boolean isAttachedToWindow; @@ -558,8 +558,9 @@ public class EmacsView extends ViewGroup box that obscures Emacs. */ info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; - /* But don't return an InputConnection, in order to force the on - screen keyboard to work correctly. */ + /* Set a reasonable inputType. */ + info.inputType = InputType.TYPE_NULL; + return null; } diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 9e2f2f53270..0eca35cec61 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -23,6 +23,8 @@ import java.lang.IllegalStateException; import java.util.ArrayList; import java.util.List; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; import android.content.Context; @@ -100,7 +102,7 @@ public class EmacsWindow extends EmacsHandleObject /* The button state and keyboard modifier mask at the time of the last button press or release event. */ - private int lastButtonState, lastModifiers; + public int lastButtonState, lastModifiers; /* Whether or not the window is mapped, and whether or not it is deiconified. */ @@ -122,6 +124,10 @@ public class EmacsWindow extends EmacsHandleObject to quit Emacs. */ private long lastVolumeButtonRelease; + /* Linked list of character strings which were recently sent as + events. */ + public LinkedHashMap eventStrings; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height, boolean overrideRedirect) @@ -155,6 +161,19 @@ public class EmacsWindow extends EmacsHandleObject } scratchGC = new EmacsGC ((short) 0); + + /* Create the map of input method-committed strings. Keep at most + ten strings in the map. */ + + eventStrings + = new LinkedHashMap () { + @Override + protected boolean + removeEldestEntry (Map.Entry entry) + { + return size () > 10; + } + }; } public void @@ -507,10 +526,40 @@ public class EmacsWindow extends EmacsHandleObject return view.getBitmap (); } + /* event.getCharacters is used because older input methods still + require it. */ + @SuppressWarnings ("deprecation") + public int + getEventUnicodeChar (KeyEvent event, int state) + { + String characters; + + if (event.getUnicodeChar (state) != 0) + return event.getUnicodeChar (state); + + characters = event.getCharacters (); + + if (characters != null && characters.length () == 1) + return characters.charAt (0); + + return characters == null ? 0 : -1; + } + + public void + saveUnicodeString (int serial, String string) + { + eventStrings.put (serial, string); + } + + /* event.getCharacters is used because older input methods still + require it. */ + @SuppressWarnings ("deprecation") public void onKeyDown (int keyCode, KeyEvent event) { int state, state_1; + long serial; + String characters; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -537,18 +586,28 @@ public class EmacsWindow extends EmacsHandleObject state_1 = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); - EmacsNative.sendKeyPress (this.handle, - event.getEventTime (), - state, keyCode, - event.getUnicodeChar (state_1)); - lastModifiers = state; + synchronized (eventStrings) + { + serial + = EmacsNative.sendKeyPress (this.handle, + event.getEventTime (), + state, keyCode, + getEventUnicodeChar (event, + state_1)); + lastModifiers = state; + + characters = event.getCharacters (); + + if (characters != null && characters.length () > 1) + saveUnicodeString ((int) serial, characters); + } } public void onKeyUp (int keyCode, KeyEvent event) { int state, state_1; - long time; + long time, serial; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -575,10 +634,12 @@ public class EmacsWindow extends EmacsHandleObject state_1 = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); - EmacsNative.sendKeyRelease (this.handle, - event.getEventTime (), - state, keyCode, - event.getUnicodeChar (state_1)); + serial + = EmacsNative.sendKeyRelease (this.handle, + event.getEventTime (), + state, keyCode, + getEventUnicodeChar (event, + state_1)); lastModifiers = state; if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) @@ -1105,4 +1166,17 @@ public class EmacsWindow extends EmacsHandleObject } }); } + + public String + lookupString (int eventSerial) + { + String any; + + synchronized (eventStrings) + { + any = eventStrings.remove (eventSerial); + } + + return any; + } }; diff --git a/lisp/term/android-win.el b/lisp/term/android-win.el index aa5992a9f09..0984b4d5840 100644 --- a/lisp/term/android-win.el +++ b/lisp/term/android-win.el @@ -166,6 +166,42 @@ VALUE should be something suitable for passing to (android-set-clipboard string)) ((eq type 'PRIMARY) (setq android-primary-selection string))))) + +;;; Character composition display. + +(defvar android-preedit-overlay nil + "The overlay currently used to display preedit text from a compose sequence.") + +;; With some input methods, text gets inserted before Emacs is told to +;; remove any preedit text that was displayed, which causes both the +;; preedit overlay and the text to be visible for a brief period of +;; time. This pre-command-hook clears the overlay before any command +;; and should be set whenever a preedit overlay is visible. +(defun android-clear-preedit-text () + "Clear the pre-edit overlay and remove itself from pre-command-hook. +This function should be installed in `pre-command-hook' whenever +preedit text is displayed." + (when android-preedit-overlay + (delete-overlay android-preedit-overlay) + (setq android-preedit-overlay nil)) + (remove-hook 'pre-command-hook #'android-clear-preedit-text)) + +(defun android-preedit-text (event) + "Display preedit text from a compose sequence in EVENT. +EVENT is a preedit-text event." + (interactive "e") + (when android-preedit-overlay + (delete-overlay android-preedit-overlay) + (setq android-preedit-overlay nil) + (remove-hook 'pre-command-hook #'android-clear-preedit-text)) + (when (nth 1 event) + (let ((string (propertize (nth 1 event) 'face '(:underline t)))) + (setq android-preedit-overlay (make-overlay (point) (point))) + (add-hook 'pre-command-hook #'android-clear-preedit-text) + (overlay-put android-preedit-overlay 'window (selected-window)) + (overlay-put android-preedit-overlay 'before-string string)))) + +(define-key special-event-map [preedit-text] 'android-preedit-text) (provide 'android-win) ;; android-win.el ends here. diff --git a/src/android.c b/src/android.c index ebd374addbf..479eb9b10d4 100644 --- a/src/android.c +++ b/src/android.c @@ -125,6 +125,7 @@ struct android_emacs_window jclass class; jmethodID swap_buffers; jmethodID toggle_on_screen_keyboard; + jmethodID lookup_string; }; /* The API level of the current device. */ @@ -1829,6 +1830,7 @@ android_init_emacs_window (void) FIND_METHOD (swap_buffers, "swapBuffers", "()V"); FIND_METHOD (toggle_on_screen_keyboard, "toggleOnScreenKeyboard", "(Z)V"); + FIND_METHOD (lookup_string, "lookupString", "(I)Ljava/lang/String;"); #undef FIND_METHOD } @@ -4104,6 +4106,115 @@ android_sync (void) android_exception_check (); } +int +android_wc_lookup_string (android_key_pressed_event *event, + wchar_t *buffer_return, int wchars_buffer, + int *keysym_return, + enum android_lookup_status *status_return) +{ + enum android_lookup_status status; + int rc; + jobject window, string; + const jchar *characters; + jsize size; + size_t i; + + status = ANDROID_LOOKUP_NONE; + rc = 0; + + /* See if an actual lookup has to be made. Note that while + BUFFER_RETURN is wchar_t, the returned characters are always in + UCS. */ + + if (event->unicode_char != (uint32_t) -1) + { + if (event->unicode_char) + { + if (wchars_buffer < 1) + { + *status_return = ANDROID_BUFFER_OVERFLOW; + return 0; + } + else + { + buffer_return[0] = event->unicode_char; + status = ANDROID_LOOKUP_CHARS; + rc = 1; + } + } + + *keysym_return = event->keycode; + + if (status == ANDROID_LOOKUP_CHARS) + status = ANDROID_LOOKUP_BOTH; + else + { + status = ANDROID_LOOKUP_KEYSYM; + rc = 0; + } + + *status_return = status; + + return rc; + } + + /* Now look up the window. */ + rc = 0; + + if (!android_handles[event->window].handle + || (android_handles[event->window].type + != ANDROID_HANDLE_WINDOW)) + status = ANDROID_LOOKUP_NONE; + else + { + window = android_handles[event->window].handle; + string + = (*android_java_env)->CallObjectMethod (android_java_env, window, + window_class.lookup_string, + (jint) event->serial); + android_exception_check (); + + if (!string) + status = ANDROID_LOOKUP_NONE; + else + { + /* Now return this input method string. */ + characters = (*android_java_env)->GetStringChars (android_java_env, + string, NULL); + android_exception_check (); + + /* Figure out how big the string is. */ + size = (*android_java_env)->GetStringLength (android_java_env, + string); + + /* Copy over the string data. */ + for (i = 0; i < MIN ((unsigned int) wchars_buffer, size); ++i) + buffer_return[i] = characters[i]; + + if (i < size) + status = ANDROID_BUFFER_OVERFLOW; + else + status = ANDROID_LOOKUP_CHARS; + + /* Return the number of characters that should have been + written. */ + + if (size > INT_MAX) + rc = INT_MAX; + else + rc = size; + + (*android_java_env)->ReleaseStringChars (android_java_env, string, + characters); + android_exception_check (); + ANDROID_DELETE_LOCAL_REF (string); + } + } + + *status_return = status; + return rc; +} + /* Low level drawing primitives. */ diff --git a/src/androidgui.h b/src/androidgui.h index 619c5e44f15..1f0d34e67aa 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -209,7 +209,7 @@ typedef char Emacs_Cursor; #ifndef ANDROID_STUBIFY /* Universal NULL handle. */ -static const int ANDROID_NONE; +static const int ANDROID_NONE, ANDROID_NO_SYMBOL; /* Keep these as conceptually close to X as possible: that makes synchronizing code between the ports much easier. */ @@ -259,9 +259,14 @@ struct android_key_event android_time time; unsigned int state; unsigned int keycode; + + /* If this field is -1, then android_lookup_string should be called + to retrieve the associated individual characters. */ unsigned int unicode_char; }; +typedef struct android_key_event android_key_pressed_event; + /* These hard coded values are Android modifier keycodes derived through experimentation. */ @@ -449,6 +454,15 @@ enum ANDROID_CURRENT_TIME = 0L, }; +enum android_lookup_status + { + ANDROID_BUFFER_OVERFLOW, + ANDROID_LOOKUP_NONE, + ANDROID_LOOKUP_CHARS, + ANDROID_LOOKUP_KEYSYM, + ANDROID_LOOKUP_BOTH, + }; + extern int android_pending (void); extern void android_next_event (union android_event *); @@ -537,6 +551,10 @@ extern void android_translate_coordinates (android_window, int, int, int *, int *); extern void android_sync (void); +extern int android_wc_lookup_string (android_key_pressed_event *, + wchar_t *, int, int *, + enum android_lookup_status *); + #endif diff --git a/src/androidterm.c b/src/androidterm.c index 4aee1a90b68..a57d5623c66 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -541,6 +541,8 @@ handle_one_android_event (struct android_display_info *dpyinfo, Lisp_Object window; int scroll_height; double scroll_unit; + int keysym; + ptrdiff_t nchars, i; /* It is okay for this to not resemble handle_one_xevent so much. Differences in event handling code are much less nasty than @@ -550,6 +552,7 @@ handle_one_android_event (struct android_display_info *dpyinfo, hlinfo = &dpyinfo->mouse_highlight; *finish = ANDROID_EVENT_NORMAL; any = android_window_to_frame (dpyinfo, event->xany.window); + nchars = 0; if (any && any->wait_event_type == event->type) any->wait_event_type = 0; /* Indicates we got it. */ @@ -616,6 +619,13 @@ handle_one_android_event (struct android_display_info *dpyinfo, android_flush_dirty_back_buffer_on (mouse_frame); } + if (!f) + goto OTHER; + + wchar_t copy_buffer[129]; + wchar_t *copy_bufptr = copy_buffer; + int copy_bufsiz = 128 * sizeof (wchar_t); + event->xkey.state |= android_emacs_to_android_modifiers (dpyinfo, extra_keyboard_modifiers); @@ -627,31 +637,100 @@ handle_one_android_event (struct android_display_info *dpyinfo, = android_android_to_emacs_modifiers (dpyinfo, modifiers); inev.ie.timestamp = event->xkey.time; - /* First deal with keysyms which have defined translations to - characters. */ + keysym = event->xkey.keycode; + + { + enum android_lookup_status status_return; + + nchars = android_wc_lookup_string (&event->xkey, copy_bufptr, + copy_bufsiz, &keysym, + &status_return); + + /* android_lookup_string can't be called twice, so there's no + way to recover from buffer overflow. */ + if (status_return == ANDROID_BUFFER_OVERFLOW) + goto done_keysym; + else if (status_return == ANDROID_LOOKUP_NONE) + { + /* Don't skip preedit text events. */ + if (event->xkey.keycode != (uint32_t) -1) + goto done_keysym; + } + else if (status_return == ANDROID_LOOKUP_CHARS) + keysym = ANDROID_NO_SYMBOL; + else if (status_return != ANDROID_LOOKUP_KEYSYM + && status_return != ANDROID_LOOKUP_BOTH) + emacs_abort (); + + /* Deal with pre-edit text events. On Android, these are + simply encoded as events with associated strings and a + keycode set to ``-1''. */ + + if (event->xkey.keycode == (uint32_t) -1) + { + inev.ie.kind = PREEDIT_TEXT_EVENT; + inev.ie.arg = Qnil; + + /* If text was looked up, decode it and make it the + preedit text. */ + + if (status_return == ANDROID_LOOKUP_CHARS && nchars) + { + copy_bufptr[nchars] = 0; + inev.ie.arg = from_unicode_buffer (copy_bufptr); + } - if (event->xkey.unicode_char >= 32 - && event->xkey.unicode_char < 128) + goto done_keysym; + } + } + + if (nchars == 1 && copy_bufptr[0] >= 32) { - inev.ie.kind = ASCII_KEYSTROKE_EVENT; - inev.ie.code = event->xkey.unicode_char; + /* Deal with characters. */ + + if (copy_bufptr[0] < 128) + inev.ie.kind = ASCII_KEYSTROKE_EVENT; + else + inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT; + + inev.ie.code = copy_bufptr[0]; } - else if (event->xkey.unicode_char < 32) + else if (nchars < 2 && keysym) { /* If the key is a modifier key, just return. */ - if (ANDROID_IS_MODIFIER_KEY (event->xkey.keycode)) + if (ANDROID_IS_MODIFIER_KEY (keysym)) 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; + inev.ie.code = keysym; } else { - /* Finally, deal with Unicode characters. */ - inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT; - inev.ie.code = event->xkey.unicode_char; + /* Finally, deal with strings. */ + + for (i = 0; i < nchars; ++i) + { + inev.ie.kind = (SINGLE_BYTE_CHAR_P (copy_bufptr[i]) + ? ASCII_KEYSTROKE_EVENT + : MULTIBYTE_CHAR_KEYSTROKE_EVENT); + inev.ie.code = copy_bufptr[i]; + + /* If the character is actually '\n', then change this + to RET. */ + + if (copy_bufptr[i] == '\n') + { + inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT; + inev.ie.code = 66; + } + + kbd_buffer_store_buffered_event (&inev, hold_quit); + } + + count += nchars; + inev.ie.kind = NO_EVENT; /* Already stored above. */ } goto done_keysym; diff --git a/src/coding.c b/src/coding.c index a2e0d7040f8..be5a0252ede 100644 --- a/src/coding.c +++ b/src/coding.c @@ -8495,7 +8495,7 @@ preferred_coding_system (void) return CODING_ID_NAME (id); } -#if defined (WINDOWSNT) || defined (CYGWIN) +#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID Lisp_Object from_unicode (Lisp_Object str) @@ -8513,10 +8513,34 @@ from_unicode (Lisp_Object str) Lisp_Object from_unicode_buffer (const wchar_t *wstr) { - /* We get one of the two final null bytes for free. */ - ptrdiff_t len = 1 + sizeof (wchar_t) * wcslen (wstr); - AUTO_STRING_WITH_LEN (str, (char *) wstr, len); - return from_unicode (str); + if (sizeof (wchar_t) == 2) + { + /* We get one of the two final null bytes for free. */ + ptrdiff_t len = 1 + sizeof (wchar_t) * wcslen (wstr); + AUTO_STRING_WITH_LEN (str, (char *) wstr, len); + return from_unicode (str); + } + else + { + /* This code is used only on Android, where little endian UTF-16 + strings are extended to 32-bit wchar_t. */ + + uint16_t *words; + size_t length, i; + + length = wcslen (wstr) + 1; + + USE_SAFE_ALLOCA; + SAFE_NALLOCA (words, sizeof *words, length); + + for (i = 0; i < length - 1; ++i) + words[i] = wstr[i]; + + words[i] = '\0'; + AUTO_STRING_WITH_LEN (str, (char *) words, + (length - 1) * sizeof *words); + return unbind_to (sa_count, from_unicode (str)); + } } wchar_t * @@ -8536,7 +8560,7 @@ to_unicode (Lisp_Object str, Lisp_Object *buf) return WCSDATA (*buf); } -#endif /* WINDOWSNT || CYGWIN */ +#endif /* WINDOWSNT || CYGWIN || HAVE_ANDROID */ /*** 8. Emacs Lisp library functions ***/ diff --git a/src/coding.h b/src/coding.h index b8d4f5f27e1..08c29c884a5 100644 --- a/src/coding.h +++ b/src/coding.h @@ -709,7 +709,7 @@ extern void encode_coding_object (struct coding_system *, /* Defined in this file. */ INLINE int surrogates_to_codepoint (int, int); -#if defined (WINDOWSNT) || defined (CYGWIN) +#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID /* These functions use Lisp string objects to store the UTF-16LE strings that modern versions of Windows expect. These strings are @@ -732,7 +732,7 @@ extern Lisp_Object from_unicode (Lisp_Object str); /* Convert WSTR to an Emacs string. */ extern Lisp_Object from_unicode_buffer (const wchar_t *wstr); -#endif /* WINDOWSNT || CYGWIN */ +#endif /* WINDOWSNT || CYGWIN || HAVE_ANDROID */ /* Macros for backward compatibility. */ diff --git a/src/sfnt.c b/src/sfnt.c index cf36f388819..38f6984b93c 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -3758,16 +3758,7 @@ sfnt_edge_sort (struct sfnt_edge *edges, size_t size) coordinates, and incrementing the Y axis by SFNT_POLY_STEP instead of 1. SFNT_POLY_STEP is chosen to always keep Y aligned to a grid placed such that there are always 1 << SFNT_POLY_SHIFT positions - available for each integral pixel coordinate. - - Moving upwards is performed using Bresenham's algorithm. Prior to - an edge being created, a single slope error is computed. This is - how much X should increase for each increase in Y. - - Upon each increase in Y, X initially does not move. Instead, the - ``slope error'' is accumulated; once it exceeds the sample size, - one sample is subtracted from the accumulator, and X is increased - by one step. */ + available for each integral pixel coordinate. */ static void sfnt_poly_edges (struct sfnt_edge *edges, size_t size, -- cgit v1.3 From c8f49c9276d34741bfbe7752dd38391c0b8d782b Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 13:17:43 +0800 Subject: Implement `fullscreen' on Android 4.0 and later * doc/emacs/android.texi (Android Windowing): Document what new frame parameters are now supported. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field `isFullscreen'. (detachWindow, attachWindow): Sync fullscreen state. (onWindowFocusChanged): Add more logging. (onResume): Restore previous fullscreen state. (syncFullscreen): New function. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow) (setFullscreen): New function. * src/android.c (struct android_emacs_window): Add new method. (android_init_emacs_window): Look up new method. (android_set_fullscreen): New function. * src/androidgui.h: * src/androidterm.c (android_fullscreen_hook): Implement accordingly. --- doc/emacs/android.texi | 18 +++--- java/org/gnu/emacs/EmacsActivity.java | 107 +++++++++++++++++++++++++++++++++- java/org/gnu/emacs/EmacsWindow.java | 26 +++++++++ src/android.c | 32 ++++++++++ src/androidgui.h | 1 + src/androidterm.c | 24 +++++++- 6 files changed, 193 insertions(+), 15 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 35f0ba55cf4..4e5402f5f40 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -355,8 +355,8 @@ tiled on the screen at any time. are created. Instead, the system may choose to terminate windows that are not on screen in order to save memory, with the assumption that the program will save its contents to disk and restore them later, -when the user asks to open it again. As this is obvious not possible -with Emacs, Emacs separates a frame from a system window. +when the user asks to open it again. As this is obviously not +possible with Emacs, Emacs separates a frame from a system window. Each system window created (including the initial window created during Emacs startup) is appended to a list of windows that do not @@ -402,15 +402,15 @@ devices. @item The @code{alpha}, @code{alpha-background}, @code{z-group}, -@code{override-redirect}, @code{mouse-color}, @code{cursor-color}, -@code{cursor-type}, @code{title}, @code{wait-for-wm}, @code{sticky}, -@code{undecorated} and @code{tool-bar-position} frame parameters -(@pxref{Frame Parameters,,, elisp, the Emacs Lisp Reference Manual}) -are unsupported. +@code{override-redirect}, @code{mouse-color}, @code{title}, +@code{wait-for-wm}, @code{sticky}, @code{undecorated} and +@code{tool-bar-position} frame parameters (@pxref{Frame Parameters,,, +elisp, the Emacs Lisp Reference Manual}) are unsupported. @item -The @code{fullscreen} frame parameter is always @code{maximized} for -top-level frames. +On Android 4.0 and earlier, the @code{fullscreen} frame parameter is +always @code{maximized} for top-level frames; on later versions of +Android, it can also be @code{fullscreen}. @end itemize @cindex selections, android diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 3156a144a0f..7e09e608984 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -26,12 +26,16 @@ import java.util.ArrayList; import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.os.Bundle; import android.os.Build; +import android.os.Bundle; import android.util.Log; -import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; import android.view.Menu; +import android.view.View; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; +import android.widget.FrameLayout.LayoutParams; +import android.widget.FrameLayout; public class EmacsActivity extends Activity implements EmacsWindowAttachmentManager.WindowConsumer @@ -56,6 +60,9 @@ public class EmacsActivity extends Activity /* Whether or not this activity is paused. */ private boolean isPaused; + /* Whether or not this activity is fullscreen. */ + private boolean isFullscreen; + static { focusedActivities = new ArrayList (); @@ -104,6 +111,8 @@ public class EmacsActivity extends Activity public void detachWindow () { + syncFullscreenWith (null); + if (window == null) Log.w (TAG, "detachWindow called, but there is no window"); else @@ -131,6 +140,8 @@ public class EmacsActivity extends Activity throw new IllegalStateException ("trying to attach window when one" + " already exists"); + syncFullscreenWith (child); + /* Record and attach the view. */ window = child; @@ -230,6 +241,9 @@ public class EmacsActivity extends Activity public void onWindowFocusChanged (boolean isFocused) { + Log.d (TAG, ("onWindowFocusChanged: " + + (isFocused ? "YES" : "NO"))); + if (isFocused && !focusedActivities.contains (this)) { focusedActivities.add (this); @@ -257,6 +271,9 @@ public class EmacsActivity extends Activity { isPaused = false; + /* Update the window insets. */ + syncFullscreenWith (window); + EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); super.onResume (); } @@ -279,4 +296,88 @@ public class EmacsActivity extends Activity super.onContextMenuClosed (menu); } + + @SuppressWarnings ("deprecation") + public void + syncFullscreenWith (EmacsWindow emacsWindow) + { + WindowInsetsController controller; + Window window; + int behavior, flags; + View view; + + if (emacsWindow != null) + isFullscreen = emacsWindow.fullscreen; + else + isFullscreen = false; + + /* On Android 11 or later, use the window insets controller to + control whether or not the view is fullscreen. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + { + window = getWindow (); + + /* If there is no attached window, return immediately. */ + if (window == null) + return; + + behavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + controller = window.getInsetsController (); + controller.setSystemBarsBehavior (behavior); + + if (isFullscreen) + controller.hide (WindowInsets.Type.statusBars () + | WindowInsets.Type.navigationBars ()); + else + controller.show (WindowInsets.Type.statusBars () + | WindowInsets.Type.navigationBars ()); + + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + { + /* On Android 4.1 or later, use `setSystemUiVisibility'. */ + + window = getWindow (); + + if (window == null) + return; + + view = window.getDecorView (); + + if (isFullscreen) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + /* This flag means that Emacs will be full screen, but + the system will cancel the full screen state upon + switching to another program. */ + view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_FULLSCREEN); + else + { + /* These flags means that Emacs will be full screen as + long as the state flag is set. */ + flags = 0; + flags |= View.SYSTEM_UI_FLAG_FULLSCREEN; + flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + flags |= View.SYSTEM_UI_FLAG_IMMERSIVE; + flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + view.setSystemUiVisibility (flags); + } + } + else + view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_VISIBLE); + } + } + + @Override + public void + onAttachedToWindow () + { + super.onAttachedToWindow (); + + /* Update the window insets. */ + syncFullscreenWith (window); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 0eca35cec61..90fc4c44198 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -128,6 +128,9 @@ public class EmacsWindow extends EmacsHandleObject events. */ public LinkedHashMap eventStrings; + /* Whether or not this window is fullscreen. */ + public boolean fullscreen; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height, boolean overrideRedirect) @@ -1179,4 +1182,27 @@ public class EmacsWindow extends EmacsHandleObject return any; } + + public void + setFullscreen (final boolean isFullscreen) + { + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + EmacsActivity activity; + Object tem; + + fullscreen = isFullscreen; + tem = getAttachedConsumer (); + + if (tem != null) + { + activity = (EmacsActivity) getAttachedConsumer (); + activity.syncFullscreenWith (EmacsWindow.this); + } + } + }); + } }; diff --git a/src/android.c b/src/android.c index d21aaabc1ac..fc5e6d278ed 100644 --- a/src/android.c +++ b/src/android.c @@ -136,6 +136,7 @@ struct android_emacs_window jmethodID swap_buffers; jmethodID toggle_on_screen_keyboard; jmethodID lookup_string; + jmethodID set_fullscreen; }; /* The API level of the current device. */ @@ -1920,6 +1921,7 @@ android_init_emacs_window (void) FIND_METHOD (toggle_on_screen_keyboard, "toggleOnScreenKeyboard", "(Z)V"); FIND_METHOD (lookup_string, "lookupString", "(I)Ljava/lang/String;"); + FIND_METHOD (set_fullscreen, "setFullscreen", "(Z)V"); #undef FIND_METHOD } @@ -5475,6 +5477,36 @@ android_reset_ic (android_window window, enum android_ic_mode mode) +/* Window decoration management functions. */ + +/* Make the specified WINDOW fullscreen, i.e. obscure all of the + system navigation and status bars. If not FULLSCREEN, make it + maximized instead. + + Value is 1 if the system does not support this, else 0. */ + +int +android_set_fullscreen (android_window window, bool fullscreen) +{ + jobject object; + + /* Android 4.0 and earlier don't support fullscreen windows. */ + + if (android_api_level < 16) + return 1; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + + (*android_java_env)->CallVoidMethod (android_java_env, + object, + window_class.set_fullscreen, + (jboolean) fullscreen); + android_exception_check (); + return 0; +} + + + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ diff --git a/src/androidgui.h b/src/androidgui.h index 25dc6754fff..84419457a8a 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -614,6 +614,7 @@ extern int android_wc_lookup_string (android_key_pressed_event *, extern void android_update_ic (android_window, ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t); extern void android_reset_ic (android_window, enum android_ic_mode); +extern int android_set_fullscreen (android_window, bool); #endif diff --git a/src/androidterm.c b/src/androidterm.c index 0fbee1e9867..62a8d5d12b3 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -1792,12 +1792,30 @@ android_make_frame_visible_invisible (struct frame *f, bool visible) static void android_fullscreen_hook (struct frame *f) { - /* Explicitly setting fullscreen is not supported on Android. */ + Lisp_Object wanted; if (!FRAME_PARENT_FRAME (f)) - store_frame_param (f, Qfullscreen, Qmaximized); + { + /* Explicitly setting fullscreen is not supported on older + Android versions. */ + + wanted = (f->want_fullscreen == FULLSCREEN_BOTH + ? Qfullscreen : Qmaximized); + + if (android_set_fullscreen (FRAME_ANDROID_WINDOW (f), + EQ (wanted, Qfullscreen))) + store_frame_param (f, Qfullscreen, Qmaximized); + else + store_frame_param (f, Qfullscreen, wanted); + } else - store_frame_param (f, Qfullscreen, Qnil); + { + store_frame_param (f, Qfullscreen, Qnil); + + /* If this is a child frame, don't keep it fullscreen + anymore. */ + android_set_fullscreen (FRAME_ANDROID_WINDOW (f), false); + } } void -- cgit v1.3 From 15bcb446be2f2f5b85a1b9585ec3abaabcbf04d9 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 1 Mar 2023 12:00:46 +0800 Subject: Update Android port * java/Makefile.in (ETAGS, clean): New rules to generate tags. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): * java/org/gnu/emacs/EmacsApplication.java (EmacsApplication): * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): * java/org/gnu/emacs/EmacsCopyArea.java (EmacsCopyArea): * java/org/gnu/emacs/EmacsDialog.java (EmacsDialog)::(dialog. Then): * java/org/gnu/emacs/EmacsDocumentsProvider.java (EmacsDocumentsProvider): * java/org/gnu/emacs/EmacsDrawLine.java (EmacsDrawLine): * java/org/gnu/emacs/EmacsDrawPoint.java (EmacsDrawPoint): * java/org/gnu/emacs/EmacsDrawRectangle.java (EmacsDrawRectangle): * java/org/gnu/emacs/EmacsFillPolygon.java (EmacsFillPolygon): * java/org/gnu/emacs/EmacsFillRectangle.java (EmacsFillRectangle): * java/org/gnu/emacs/EmacsGC.java (EmacsGC): * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): * java/org/gnu/emacs/EmacsNative.java (EmacsNative): * java/org/gnu/emacs/EmacsNoninteractive.java (EmacsNoninteractive): * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity): * java/org/gnu/emacs/EmacsPixmap.java (EmacsPixmap): * java/org/gnu/emacs/EmacsPreferencesActivity.java (EmacsPreferencesActivity): * java/org/gnu/emacs/EmacsSdk11Clipboard.java (EmacsSdk11Clipboard): * java/org/gnu/emacs/EmacsSdk23FontDriver.java (EmacsSdk23FontDriver): * java/org/gnu/emacs/EmacsSdk8Clipboard.java (EmacsSdk8Clipboard): * java/org/gnu/emacs/EmacsService.java (EmacsService): * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView) (buffers): * java/org/gnu/emacs/EmacsView.java (EmacsView, ViewGroup): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, drawables): * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (EmacsWindowAttachmentManager): Make classes final where appropriate. --- java/Makefile.in | 16 +++++++++++++++- java/org/gnu/emacs/EmacsActivity.java | 22 +++++++++++----------- java/org/gnu/emacs/EmacsApplication.java | 2 +- java/org/gnu/emacs/EmacsContextMenu.java | 2 +- java/org/gnu/emacs/EmacsCopyArea.java | 2 +- java/org/gnu/emacs/EmacsDialog.java | 2 +- java/org/gnu/emacs/EmacsDocumentsProvider.java | 2 +- java/org/gnu/emacs/EmacsDrawLine.java | 2 +- java/org/gnu/emacs/EmacsDrawPoint.java | 2 +- java/org/gnu/emacs/EmacsDrawRectangle.java | 2 +- java/org/gnu/emacs/EmacsFillPolygon.java | 2 +- java/org/gnu/emacs/EmacsFillRectangle.java | 2 +- java/org/gnu/emacs/EmacsGC.java | 2 +- java/org/gnu/emacs/EmacsInputConnection.java | 11 ++++++++++- java/org/gnu/emacs/EmacsNative.java | 2 +- java/org/gnu/emacs/EmacsNoninteractive.java | 2 +- java/org/gnu/emacs/EmacsOpenActivity.java | 2 +- java/org/gnu/emacs/EmacsPixmap.java | 2 +- java/org/gnu/emacs/EmacsPreferencesActivity.java | 2 +- java/org/gnu/emacs/EmacsSdk11Clipboard.java | 2 +- java/org/gnu/emacs/EmacsSdk23FontDriver.java | 2 +- java/org/gnu/emacs/EmacsSdk8Clipboard.java | 2 +- java/org/gnu/emacs/EmacsService.java | 2 +- java/org/gnu/emacs/EmacsSurfaceView.java | 2 +- java/org/gnu/emacs/EmacsView.java | 2 +- java/org/gnu/emacs/EmacsWindow.java | 2 +- .../gnu/emacs/EmacsWindowAttachmentManager.java | 2 +- 27 files changed, 60 insertions(+), 37 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/Makefile.in b/java/Makefile.in index 7ba05f6c9a3..bff021752c7 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -281,9 +281,23 @@ $(APK_NAME): classes.dex emacs.apk-in emacs.keystore $(AM_V_SILENT) $(APKSIGNER) $(SIGN_EMACS_V2) $@ $(AM_V_SILENT) rm -f $@.unaligned *.idsig +# TAGS generation. + +ETAGS = $(top_builddir)/lib-src/etags + +$(ETAGS): FORCE + $(MAKE) -C ../lib-src $(notdir $@) + +tagsfiles = $(JAVA_FILES) $(RESOURCE_FILE) + +.PHONY: tags FORCE +tags: TAGS +TAGS: $(ETAGS) $(tagsfiles) + $(AM_V_GEN) $(ETAGS) $(tagsfiles) + clean: rm -f *.apk emacs.apk-in *.dex *.unaligned *.class *.idsig - rm -rf install-temp $(RESOURCE_FILE) + rm -rf install-temp $(RESOURCE_FILE) TAGS find . -name '*.class' -delete maintainer-clean distclean bootstrap-clean: clean diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 7e09e608984..0ee8c239899 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -108,7 +108,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void detachWindow () { syncFullscreenWith (null); @@ -131,7 +131,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void attachWindow (EmacsWindow child) { Log.d (TAG, "attachWindow: " + child); @@ -161,21 +161,21 @@ public class EmacsActivity extends Activity } @Override - public void + public final void destroy () { finish (); } @Override - public EmacsWindow + public final EmacsWindow getAttachedWindow () { return window; } @Override - public void + public final void onCreate (Bundle savedInstanceState) { FrameLayout.LayoutParams params; @@ -238,7 +238,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onWindowFocusChanged (boolean isFocused) { Log.d (TAG, ("onWindowFocusChanged: " @@ -256,7 +256,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onPause () { isPaused = true; @@ -266,7 +266,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onResume () { isPaused = false; @@ -279,7 +279,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onContextMenuClosed (Menu menu) { Log.d (TAG, "onContextMenuClosed: " + menu); @@ -298,7 +298,7 @@ public class EmacsActivity extends Activity } @SuppressWarnings ("deprecation") - public void + public final void syncFullscreenWith (EmacsWindow emacsWindow) { WindowInsetsController controller; @@ -372,7 +372,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onAttachedToWindow () { super.onAttachedToWindow (); diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java index 6a065165eb1..10099721744 100644 --- a/java/org/gnu/emacs/EmacsApplication.java +++ b/java/org/gnu/emacs/EmacsApplication.java @@ -27,7 +27,7 @@ import android.content.Context; import android.app.Application; import android.util.Log; -public class EmacsApplication extends Application +public final class EmacsApplication extends Application { private static final String TAG = "EmacsApplication"; diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 6b3ae0c6de9..0de292af21a 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -42,7 +42,7 @@ import android.widget.PopupMenu; Android menu, which can be turned into a popup (or other kind of) menu. */ -public class EmacsContextMenu +public final class EmacsContextMenu { private static final String TAG = "EmacsContextMenu"; diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index 1daa2190542..f69b0cde866 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -27,7 +27,7 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Xfermode; -public class EmacsCopyArea +public final class EmacsCopyArea { private static Xfermode overAlu; diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index 9f9124ce99c..80a5e5f7369 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -38,7 +38,7 @@ import android.view.ViewGroup; describes a single alert dialog. Then, `inflate' turns it into AlertDialog. */ -public class EmacsDialog implements DialogInterface.OnDismissListener +public final class EmacsDialog implements DialogInterface.OnDismissListener { private static final String TAG = "EmacsDialog"; diff --git a/java/org/gnu/emacs/EmacsDocumentsProvider.java b/java/org/gnu/emacs/EmacsDocumentsProvider.java index 3c3c7ead3c5..901c3b909e0 100644 --- a/java/org/gnu/emacs/EmacsDocumentsProvider.java +++ b/java/org/gnu/emacs/EmacsDocumentsProvider.java @@ -48,7 +48,7 @@ import java.io.IOException; This functionality is only available on Android 19 and later. */ -public class EmacsDocumentsProvider extends DocumentsProvider +public final class EmacsDocumentsProvider extends DocumentsProvider { /* Home directory. This is the directory whose contents are initially returned to requesting applications. */ diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index c6e5123bfca..0b23138a36c 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -29,7 +29,7 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Xfermode; -public class EmacsDrawLine +public final class EmacsDrawLine { public static void perform (EmacsDrawable drawable, EmacsGC gc, diff --git a/java/org/gnu/emacs/EmacsDrawPoint.java b/java/org/gnu/emacs/EmacsDrawPoint.java index 3bc7be17961..de8ddf09cc4 100644 --- a/java/org/gnu/emacs/EmacsDrawPoint.java +++ b/java/org/gnu/emacs/EmacsDrawPoint.java @@ -19,7 +19,7 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -public class EmacsDrawPoint +public final class EmacsDrawPoint { public static void perform (EmacsDrawable drawable, diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index 695a8c6ea44..ce5e94e4a76 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -27,7 +27,7 @@ import android.graphics.RectF; import android.util.Log; -public class EmacsDrawRectangle +public final class EmacsDrawRectangle { public static void perform (EmacsDrawable drawable, EmacsGC gc, diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java b/java/org/gnu/emacs/EmacsFillPolygon.java index 22e2dd0d8a9..d55a0b3aca8 100644 --- a/java/org/gnu/emacs/EmacsFillPolygon.java +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -29,7 +29,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; -public class EmacsFillPolygon +public final class EmacsFillPolygon { public static void perform (EmacsDrawable drawable, EmacsGC gc, Point points[]) diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index aed0a540c8f..4a0478b446f 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -26,7 +26,7 @@ import android.graphics.Rect; import android.util.Log; -public class EmacsFillRectangle +public final class EmacsFillRectangle { public static void perform (EmacsDrawable drawable, EmacsGC gc, diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index bdc27a1ca5b..a7467cb9bd0 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -29,7 +29,7 @@ import android.graphics.Xfermode; /* X like graphics context structures. Keep the enums in synch with androidgui.h! */ -public class EmacsGC extends EmacsHandleObject +public final class EmacsGC extends EmacsHandleObject { public static final int GC_COPY = 0; public static final int GC_XOR = 1; diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 834c2226c82..ed64c368857 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -36,7 +36,7 @@ import android.util.Log; See EmacsEditable for more details. */ -public class EmacsInputConnection extends BaseInputConnection +public final class EmacsInputConnection extends BaseInputConnection { private static final String TAG = "EmacsInputConnection"; private EmacsView view; @@ -243,6 +243,15 @@ public class EmacsInputConnection extends BaseInputConnection return super.sendKeyEvent (key); } + @Override + public boolean + deleteSurroundingTextInCodePoints (int beforeLength, int afterLength) + { + /* This can be implemented the same way as + deleteSurroundingText. */ + return this.deleteSurroundingText (beforeLength, afterLength); + } + /* Override functions which are not implemented. */ diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index f0917a68120..b1205353090 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -25,7 +25,7 @@ import android.content.res.AssetManager; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; -public class EmacsNative +public final class EmacsNative { /* List of native libraries that must be loaded during class initialization. */ diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java index 30901edb75f..f365037b311 100644 --- a/java/org/gnu/emacs/EmacsNoninteractive.java +++ b/java/org/gnu/emacs/EmacsNoninteractive.java @@ -44,7 +44,7 @@ import java.lang.reflect.Method; initializes Emacs. */ @SuppressWarnings ("unchecked") -public class EmacsNoninteractive +public final class EmacsNoninteractive { private static String getLibraryDirectory (Context context) diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index 87ce454a816..fddd5331d2f 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -68,7 +68,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; -public class EmacsOpenActivity extends Activity +public final class EmacsOpenActivity extends Activity implements DialogInterface.OnClickListener { private static final String TAG = "EmacsOpenActivity"; diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java index a83d8f25542..eb011bc5e65 100644 --- a/java/org/gnu/emacs/EmacsPixmap.java +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -29,7 +29,7 @@ import android.os.Build; /* Drawable backed by bitmap. */ -public class EmacsPixmap extends EmacsHandleObject +public final class EmacsPixmap extends EmacsHandleObject implements EmacsDrawable { /* The depth of the bitmap. This is not actually used, just defined diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java index 85639fe9236..70934fa4bd4 100644 --- a/java/org/gnu/emacs/EmacsPreferencesActivity.java +++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java @@ -42,7 +42,7 @@ import android.preference.*; Unfortunately, there is no alternative that looks the same way. */ @SuppressWarnings ("deprecation") -public class EmacsPreferencesActivity extends PreferenceActivity +public final class EmacsPreferencesActivity extends PreferenceActivity { /* Restart Emacs with -Q. Call EmacsThread.exit to kill Emacs now, and tell the system to EmacsActivity with some parameters later. */ diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java b/java/org/gnu/emacs/EmacsSdk11Clipboard.java index ea35a463299..a05184513cd 100644 --- a/java/org/gnu/emacs/EmacsSdk11Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java @@ -32,7 +32,7 @@ import java.io.UnsupportedEncodingException; /* This class implements EmacsClipboard for Android 3.0 and later systems. */ -public class EmacsSdk11Clipboard extends EmacsClipboard +public final class EmacsSdk11Clipboard extends EmacsClipboard implements ClipboardManager.OnPrimaryClipChangedListener { private static final String TAG = "EmacsSdk11Clipboard"; diff --git a/java/org/gnu/emacs/EmacsSdk23FontDriver.java b/java/org/gnu/emacs/EmacsSdk23FontDriver.java index 11e128d5769..aaba8dbd166 100644 --- a/java/org/gnu/emacs/EmacsSdk23FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk23FontDriver.java @@ -22,7 +22,7 @@ package org.gnu.emacs; import android.graphics.Paint; import android.graphics.Rect; -public class EmacsSdk23FontDriver extends EmacsSdk7FontDriver +public final class EmacsSdk23FontDriver extends EmacsSdk7FontDriver { private void textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics, diff --git a/java/org/gnu/emacs/EmacsSdk8Clipboard.java b/java/org/gnu/emacs/EmacsSdk8Clipboard.java index 34e66912562..818a722a908 100644 --- a/java/org/gnu/emacs/EmacsSdk8Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk8Clipboard.java @@ -31,7 +31,7 @@ import java.io.UnsupportedEncodingException; similarly old systems. */ @SuppressWarnings ("deprecation") -public class EmacsSdk8Clipboard extends EmacsClipboard +public final class EmacsSdk8Clipboard extends EmacsClipboard { private static final String TAG = "EmacsSdk8Clipboard"; private ClipboardManager manager; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 7f4f75b5147..e61d9487375 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -82,7 +82,7 @@ class Holder /* EmacsService is the service that starts the thread running Emacs and handles requests by that Emacs instance. */ -public class EmacsService extends Service +public final class EmacsService extends Service { public static final String TAG = "EmacsService"; public static final int MAX_PENDING_REQUESTS = 256; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 62e927094e4..e0411f7f8b3 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -35,7 +35,7 @@ import java.lang.ref.WeakReference; own back buffers, which use too much memory (up to 96 MB for a single frame.) */ -public class EmacsSurfaceView extends View +public final class EmacsSurfaceView extends View { private static final String TAG = "EmacsSurfaceView"; private EmacsView view; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 89f526853b2..d2330494bc7 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -51,7 +51,7 @@ import android.util.Log; It is also a ViewGroup, as it also lays out children. */ -public class EmacsView extends ViewGroup +public final class EmacsView extends ViewGroup { public static final String TAG = "EmacsView"; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 90fc4c44198..007a2a86e68 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -59,7 +59,7 @@ import android.os.Build; Views are also drawables, meaning they can accept drawing requests. */ -public class EmacsWindow extends EmacsHandleObject +public final class EmacsWindow extends EmacsHandleObject implements EmacsDrawable { private static final String TAG = "EmacsWindow"; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 510300571b8..1548bf28087 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -50,7 +50,7 @@ import android.util.Log; Finally, every time a window is removed, the consumer is destroyed. */ -public class EmacsWindowAttachmentManager +public final class EmacsWindowAttachmentManager { public static EmacsWindowAttachmentManager MANAGER; private final static String TAG = "EmacsWindowAttachmentManager"; -- cgit v1.3 From ad8e12b9f9937f999a277e2608a1098a1c494532 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 1 Mar 2023 15:49:02 +0800 Subject: Update Android port * doc/emacs/android.texi (Android File System): Document new behavior of starting a subprocess from /assets. * java/org/gnu/emacs/EmacsWindow.java (onSomeKindOfMotionEvent): Don't use isFromSource where not present. * src/androidterm.c (android_scroll_run): Avoid undefined behavior writing to bitfields. * src/callproc.c (get_current_directory): When trying to run a subprocess inside /assets, run it from the home directory instead. --- doc/emacs/android.texi | 4 +++- java/org/gnu/emacs/EmacsWindow.java | 9 ++++++++- src/androidterm.c | 4 +++- src/callproc.c | 19 ++++++++++++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 428cf1049b0..65ebdfa9ab1 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -169,7 +169,9 @@ that result from such an implementation: @itemize @bullet @item Subprocesses (such as @command{ls}) can not run from the -@file{/assets} directory. +@file{/assets} directory; if you try to run a subprocess with +@code{current-directory} set to @file{/assets} or a subdirectory +thereof, it will run from the home directory instead. @item There are no @file{.} and @file{..} directories inside the diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 007a2a86e68..5c481aa3ef4 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -875,7 +875,14 @@ public final class EmacsWindow extends EmacsHandleObject public boolean onSomeKindOfMotionEvent (MotionEvent event) { - if (!event.isFromSource (InputDevice.SOURCE_CLASS_POINTER)) + /* isFromSource is not available until API level 18. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) + { + if (!event.isFromSource (InputDevice.SOURCE_CLASS_POINTER)) + return false; + } + else if (event.getSource () != InputDevice.SOURCE_CLASS_POINTER) return false; switch (event.getAction ()) diff --git a/src/androidterm.c b/src/androidterm.c index 42ce03d4e7d..8a67d128348 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -2124,8 +2124,10 @@ android_scroll_run (struct window *w, struct run *run) /* Cursor off. Will be switched on again in gui_update_window_end. */ gui_clear_cursor (w); + /* To avoid sequence point problems, make sure to only call + FRAME_ANDROID_DRAWABLE once. */ android_copy_area (FRAME_ANDROID_DRAWABLE (f), - FRAME_ANDROID_DRAWABLE (f), + FRAME_ANDROID_WINDOW (f), f->output_data.android->normal_gc, x, from_y, width, height, x, to_y); diff --git a/src/callproc.c b/src/callproc.c index e15eebe23dd..ea9c946f158 100644 --- a/src/callproc.c +++ b/src/callproc.c @@ -144,7 +144,11 @@ static CHILD_SETUP_TYPE child_setup (int, int, int, char **, char **, directory if it's unreachable. If ENCODE is true, return as a string suitable for a system call; otherwise, return a string in its internal representation. Signal an error if the result would not be - an accessible directory. */ + an accessible directory. + + If the default directory lies inside a special directory which + cannot be made the current working directory, and ENCODE is also + set, simply return the home directory. */ Lisp_Object get_current_directory (bool encode) @@ -157,6 +161,19 @@ get_current_directory (bool encode) if (NILP (dir)) dir = build_string ("~"); +#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY + + /* If DIR is an asset directory or a content directory, return + the home directory instead. */ + + if (encode && (!strcmp (SSDATA (dir), "/assets") + || !strncmp (SSDATA (dir), "/assets/", 8) + || !strcmp (SSDATA (dir), "/content") + || !strncmp (SSDATA (dir), "/content/", 9))) + dir = build_string ("~"); + +#endif /* HAVE_ANDROID && ANDROID_STUBIFY */ + dir = expand_and_dir_to_file (dir); Lisp_Object encoded_dir = ENCODE_FILE (remove_slash_colon (dir)); -- cgit v1.3 From 7fb3c0d0397096f643f6239d50cf52eaf96e7b07 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 2 Mar 2023 09:27:37 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Windowing): Reword documentation. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): * java/org/gnu/emacs/EmacsFontDriver.java (EmacsFontDriver): * java/org/gnu/emacs/EmacsSdk7FontDriver.java (EmacsSdk7FontDriver): * java/org/gnu/emacs/EmacsService.java (queryBattery): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): Make functions final and classes static where necessary. * src/android.c (struct android_emacs_service): New method `display_toast'. (android_init_emacs_service): Load new method. (android_display_toast): New function. * src/android.h: Export. * src/androidfns.c (Fandroid_detect_mouse): * src/androidselect.c (Fandroid_clipboard_owner_p) (Fandroid_set_clipboard, Fandroid_get_clipboard) (Fandroid_browse_url): Prevent crashes when called from libandroid-emacs.so. * src/androidterm.c (handle_one_android_event): Fix out of date commentary. --- doc/emacs/android.texi | 5 ++--- java/org/gnu/emacs/EmacsActivity.java | 2 +- java/org/gnu/emacs/EmacsContextMenu.java | 2 +- java/org/gnu/emacs/EmacsFontDriver.java | 6 +++--- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 6 +++--- java/org/gnu/emacs/EmacsService.java | 24 +++++++++++++++++++++++- java/org/gnu/emacs/EmacsWindow.java | 3 ++- src/android.c | 28 ++++++++++++++++++++++++++++ src/android.h | 1 + src/androidfns.c | 5 +++++ src/androidselect.c | 12 ++++++++++++ src/androidterm.c | 7 ++----- 12 files changed, 83 insertions(+), 18 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 65ebdfa9ab1..9852755d649 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -438,9 +438,8 @@ that window. @cindex windowing limitations, android @cindex frame parameters, android -Due to the unusual nature of the Android windowing environment, Emacs -only supports a limited subset of GUI features. Here is a list of -known limitations, and features which are not implemented: +Emacs only supports a limited subset of GUI features on Android; the +limitations are as follows: @itemize @bullet @item diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 1c5d7605caa..c444110de60 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -207,7 +207,7 @@ public class EmacsActivity extends Activity } @Override - public void + public final void onDestroy () { EmacsWindowAttachmentManager manager; diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 0de292af21a..a1bca98daa0 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -52,7 +52,7 @@ public final class EmacsContextMenu /* Whether or not a submenu was selected. */ public static boolean wasSubmenuSelected; - private class Item implements MenuItem.OnMenuItemClickListener + private static class Item implements MenuItem.OnMenuItemClickListener { public int itemID; public String itemName, tooltip; diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java index 39bda5a456d..e142a3121d3 100644 --- a/java/org/gnu/emacs/EmacsFontDriver.java +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -65,7 +65,7 @@ public abstract class EmacsFontDriver public static final int MONO = 100; public static final int CHARCELL = 110; - public class FontSpec + public static class FontSpec { /* The fields below mean the same as they do in enum font_property_index in font.h. */ @@ -99,7 +99,7 @@ public abstract class EmacsFontDriver } }; - public class FontMetrics + public static class FontMetrics { public short lbearing; public short rbearing; @@ -119,7 +119,7 @@ public abstract class EmacsFontDriver } } - public class FontEntity extends FontSpec + public static class FontEntity extends FontSpec { /* No extra fields here. */ }; diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index ba92d4cef49..ae91c299de8 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -40,7 +40,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver private static final String EM_STRING = "m"; private static final String TAG = "EmacsSdk7FontDriver"; - protected class Sdk7Typeface + protected static final class Sdk7Typeface { /* The typeface and paint. */ public Typeface typeface; @@ -164,7 +164,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } }; - protected class Sdk7FontEntity extends FontEntity + protected static final class Sdk7FontEntity extends FontEntity { /* The typeface. */ public Sdk7Typeface typeface; @@ -187,7 +187,7 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver } }; - protected class Sdk7FontObject extends FontObject + protected final class Sdk7FontObject extends FontObject { /* The typeface. */ public Sdk7Typeface typeface; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index e61d9487375..67de5d26f53 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -54,6 +54,8 @@ import android.content.res.AssetManager; import android.database.Cursor; import android.database.MatrixCursor; +import android.hardware.input.InputManager; + import android.net.Uri; import android.os.BatteryManager; @@ -72,7 +74,7 @@ import android.provider.DocumentsContract.Document; import android.util.Log; import android.util.DisplayMetrics; -import android.hardware.input.InputManager; +import android.widget.Toast; class Holder { @@ -821,4 +823,24 @@ public final class EmacsService extends Service return new long[] { capacity, chargeCounter, currentAvg, currentNow, remaining, status, }; } + + /* Display the specified STRING in a small dialog box on the main + thread. */ + + public void + displayToast (final String string) + { + runOnUiThread (new Runnable () { + @Override + public void + run () + { + Toast toast; + + toast = Toast.makeText (getApplicationContext (), + string, Toast.LENGTH_SHORT); + toast.show (); + } + }); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 5c481aa3ef4..ea4cf48090d 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -64,11 +64,12 @@ public final class EmacsWindow extends EmacsHandleObject { private static final String TAG = "EmacsWindow"; - private class Coordinate + private static class Coordinate { /* Integral coordinate. */ int x, y; + public Coordinate (int x, int y) { this.x = x; diff --git a/src/android.c b/src/android.c index 3bccaab041a..daeb7ab70f6 100644 --- a/src/android.c +++ b/src/android.c @@ -112,6 +112,7 @@ struct android_emacs_service jmethodID open_content_uri; jmethodID check_content_uri; jmethodID query_battery; + jmethodID display_toast; }; struct android_emacs_pixmap @@ -2124,6 +2125,8 @@ android_init_emacs_service (void) FIND_METHOD (check_content_uri, "checkContentUri", "([BZZ)Z"); FIND_METHOD (query_battery, "queryBattery", "()[J"); + FIND_METHOD (display_toast, "displayToast", + "(Ljava/lang/String;)V"); #undef FIND_METHOD } @@ -5696,6 +5699,31 @@ android_query_battery (struct android_battery_state *status) return 0; } +/* Display a small momentary notification on screen containing + TEXT, which must be in the modified UTF encoding used by the + JVM. */ + +void +android_display_toast (const char *text) +{ + jstring string; + + /* Make the string. */ + string = (*android_java_env)->NewStringUTF (android_java_env, + text); + android_exception_check (); + + /* Display the toast. */ + (*android_java_env)->CallVoidMethod (android_java_env, + emacs_service, + service_class.display_toast, + string); + android_exception_check_1 (string); + + /* Delete the local reference to the string. */ + ANDROID_DELETE_LOCAL_REF (string); +} + /* Whether or not a query is currently being made. */ diff --git a/src/android.h b/src/android.h index 806be4f9954..95206b77979 100644 --- a/src/android.h +++ b/src/android.h @@ -140,6 +140,7 @@ struct android_battery_state extern Lisp_Object android_browse_url (Lisp_Object); extern int android_query_battery (struct android_battery_state *); +extern void android_display_toast (const char *); diff --git a/src/androidfns.c b/src/androidfns.c index dc68cef8a02..4837b00a21e 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -2357,6 +2357,11 @@ there is no mouse. */) (void) { #ifndef ANDROID_STUBIFY + /* If no display connection is present, just return nil. */ + + if (!android_init_gui) + return Qnil; + return android_detect_mouse () ? Qt : Qnil; #else return Qnil; diff --git a/src/androidselect.c b/src/androidselect.c index 3947ab99166..f54a6d6f52c 100644 --- a/src/androidselect.c +++ b/src/androidselect.c @@ -110,6 +110,9 @@ determined. */) { jint rc; + if (!android_init_gui) + error ("Accessing clipboard without display connection"); + block_input (); rc = (*android_java_env)->CallIntMethod (android_java_env, clipboard, @@ -133,6 +136,9 @@ DEFUN ("android-set-clipboard", Fandroid_set_clipboard, { jarray bytes; + if (!android_init_gui) + error ("Accessing clipboard without display connection"); + CHECK_STRING (string); string = ENCODE_UTF_8 (string); @@ -167,6 +173,9 @@ Alternatively, return nil if the clipboard is empty. */) size_t length; jbyte *data; + if (!android_init_gui) + error ("No Android display connection!"); + method = clipboard_class.get_clipboard; bytes = (*android_java_env)->CallObjectMethod (android_java_env, @@ -217,6 +226,9 @@ URL with a scheme specified. Signal an error upon failure. */) { Lisp_Object value; + if (!android_init_gui) + error ("No Android display connection!"); + CHECK_STRING (url); value = android_browse_url (url); diff --git a/src/androidterm.c b/src/androidterm.c index 8a67d128348..814af87819b 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -866,9 +866,8 @@ handle_one_android_event (struct android_display_info *dpyinfo, 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. */ + /* Action 0 either means that a window has been destroyed + and its associated frame should be as well. */ if (event->xaction.window) { @@ -878,8 +877,6 @@ handle_one_android_event (struct android_display_info *dpyinfo, inev.ie.kind = DELETE_WINDOW_EVENT; XSETFRAME (inev.ie.frame_or_window, f); } - else - ((void) 0) /* A new frame must be created. */; } case ANDROID_ENTER_NOTIFY: -- cgit v1.3 From 1cae464859341bfd5fc7d7ed4b503ef959af81dd Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 5 Mar 2023 19:58:28 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsActivity.java (onCreate): * java/org/gnu/emacs/EmacsContextMenu.java: * java/org/gnu/emacs/EmacsDocumentsProvider.java (getMimeType): * java/org/gnu/emacs/EmacsDrawLine.java (perform): * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): * java/org/gnu/emacs/EmacsFillPolygon.java: * java/org/gnu/emacs/EmacsFontDriver.java: * java/org/gnu/emacs/EmacsHandleObject.java: * java/org/gnu/emacs/EmacsInputConnection.java: * java/org/gnu/emacs/EmacsMultitaskActivity.java (EmacsMultitaskActivity): * java/org/gnu/emacs/EmacsNative.java: * java/org/gnu/emacs/EmacsNoninteractive.java (EmacsNoninteractive, main): * java/org/gnu/emacs/EmacsOpenActivity.java (EmacsOpenActivity) (startEmacsClient): * java/org/gnu/emacs/EmacsSdk7FontDriver.java: * java/org/gnu/emacs/EmacsSdk8Clipboard.java: * java/org/gnu/emacs/EmacsService.java (EmacsService, onCreate): * java/org/gnu/emacs/EmacsView.java (EmacsView, onLayout): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow): * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (EmacsWindowAttachmentManager): Remove redundant includes. Reorganize some functions around, remove duplicate `getLibDir' functions, and remove unused local variables. --- java/org/gnu/emacs/EmacsActivity.java | 13 ++++-- java/org/gnu/emacs/EmacsContextMenu.java | 3 -- java/org/gnu/emacs/EmacsDocumentsProvider.java | 4 +- java/org/gnu/emacs/EmacsDrawLine.java | 5 --- java/org/gnu/emacs/EmacsDrawRectangle.java | 1 - java/org/gnu/emacs/EmacsFillPolygon.java | 1 - java/org/gnu/emacs/EmacsFontDriver.java | 2 - java/org/gnu/emacs/EmacsHandleObject.java | 3 -- java/org/gnu/emacs/EmacsInputConnection.java | 9 +--- java/org/gnu/emacs/EmacsMultitaskActivity.java | 6 ++- java/org/gnu/emacs/EmacsNative.java | 2 - java/org/gnu/emacs/EmacsNoninteractive.java | 18 +------- java/org/gnu/emacs/EmacsOpenActivity.java | 20 +-------- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 3 -- java/org/gnu/emacs/EmacsSdk8Clipboard.java | 4 +- java/org/gnu/emacs/EmacsService.java | 49 ++++++++-------------- java/org/gnu/emacs/EmacsView.java | 7 ++-- java/org/gnu/emacs/EmacsWindow.java | 15 +++---- .../gnu/emacs/EmacsWindowAttachmentManager.java | 5 +-- 19 files changed, 53 insertions(+), 117 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 13002d5d0e5..692d8a14e22 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -24,18 +24,22 @@ import java.util.List; import java.util.ArrayList; import android.app.Activity; + import android.content.Context; import android.content.Intent; + import android.os.Build; import android.os.Bundle; + import android.util.Log; + import android.view.Menu; import android.view.View; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsets; import android.view.WindowInsetsController; -import android.widget.FrameLayout.LayoutParams; + import android.widget.FrameLayout; public class EmacsActivity extends Activity @@ -187,6 +191,7 @@ public class EmacsActivity extends Activity Intent intent; View decorView; ViewTreeObserver observer; + int matchParent; /* See if Emacs should be started with -Q. */ intent = getIntent (); @@ -194,8 +199,10 @@ public class EmacsActivity extends Activity = intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q", false); - params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); + matchParent = FrameLayout.LayoutParams.MATCH_PARENT; + params + = new FrameLayout.LayoutParams (matchParent, + matchParent); /* Make the frame layout. */ layout = new FrameLayout (this); diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index d1a624e68d9..dec5e148a8e 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import android.content.Context; import android.content.Intent; -import android.os.Bundle; import android.os.Build; import android.view.Menu; @@ -35,8 +34,6 @@ import android.view.SubMenu; import android.util.Log; -import android.widget.PopupMenu; - /* Context menu implementation. This object is built from JNI and describes a menu hiearchy. Then, `inflate' can turn it into an Android menu, which can be turned into a popup (or other kind of) diff --git a/java/org/gnu/emacs/EmacsDocumentsProvider.java b/java/org/gnu/emacs/EmacsDocumentsProvider.java index 901c3b909e0..f70da6040ff 100644 --- a/java/org/gnu/emacs/EmacsDocumentsProvider.java +++ b/java/org/gnu/emacs/EmacsDocumentsProvider.java @@ -160,6 +160,7 @@ public final class EmacsDocumentsProvider extends DocumentsProvider { String name, extension, mime; int extensionSeparator; + MimeTypeMap singleton; if (file.isDirectory ()) return Document.MIME_TYPE_DIR; @@ -170,8 +171,9 @@ public final class EmacsDocumentsProvider extends DocumentsProvider if (extensionSeparator > 0) { + singleton = MimeTypeMap.getSingleton (); extension = name.substring (extensionSeparator + 1); - mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension (extension); + mime = singleton.getMimeTypeFromExtension (extension); if (mime != null) return mime; diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java index 0b23138a36c..92e03c48e26 100644 --- a/java/org/gnu/emacs/EmacsDrawLine.java +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -21,13 +21,9 @@ package org.gnu.emacs; import java.lang.Math; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.Xfermode; public final class EmacsDrawLine { @@ -38,7 +34,6 @@ public final class EmacsDrawLine Rect rect; Canvas canvas; Paint paint; - int i; /* TODO implement stippling. */ if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index ce5e94e4a76..3bd5779c54e 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -33,7 +33,6 @@ public final class EmacsDrawRectangle perform (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { - int i; Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; diff --git a/java/org/gnu/emacs/EmacsFillPolygon.java b/java/org/gnu/emacs/EmacsFillPolygon.java index d55a0b3aca8..ea8324c543c 100644 --- a/java/org/gnu/emacs/EmacsFillPolygon.java +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -21,7 +21,6 @@ package org.gnu.emacs; import java.lang.Math; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java index e142a3121d3..ff52899a897 100644 --- a/java/org/gnu/emacs/EmacsFontDriver.java +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -19,8 +19,6 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -import java.util.List; - import android.os.Build; /* This code is mostly unused. See sfntfont-android.c for the code diff --git a/java/org/gnu/emacs/EmacsHandleObject.java b/java/org/gnu/emacs/EmacsHandleObject.java index a57a3bbdfa9..5b889895337 100644 --- a/java/org/gnu/emacs/EmacsHandleObject.java +++ b/java/org/gnu/emacs/EmacsHandleObject.java @@ -19,9 +19,6 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -import java.util.List; -import java.util.ArrayList; -import java.lang.Object; import java.lang.IllegalStateException; /* This defines something that is a so-called ``handle''. Handles diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index ed64c368857..7b40fcff99e 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -23,18 +23,13 @@ import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextSnapshot; import android.view.KeyEvent; -import android.text.Editable; - import android.util.Log; -/* Android input methods, take number six. - - See EmacsEditable for more details. */ +/* Android input methods, take number six. See textconv.c for more + details; this is more-or-less a thin wrapper around that file. */ public final class EmacsInputConnection extends BaseInputConnection { diff --git a/java/org/gnu/emacs/EmacsMultitaskActivity.java b/java/org/gnu/emacs/EmacsMultitaskActivity.java index dbdc99a3559..b1c48f03fba 100644 --- a/java/org/gnu/emacs/EmacsMultitaskActivity.java +++ b/java/org/gnu/emacs/EmacsMultitaskActivity.java @@ -19,7 +19,11 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -public class EmacsMultitaskActivity extends EmacsActivity +/* This class only exists because EmacsActivity is already defined as + an activity, and the system wants a new class in order to define a + new activity. */ + +public final class EmacsMultitaskActivity extends EmacsActivity { } diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index b1205353090..38370d60727 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -19,8 +19,6 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -import java.lang.System; - import android.content.res.AssetManager; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java index f365037b311..aaba74d877c 100644 --- a/java/org/gnu/emacs/EmacsNoninteractive.java +++ b/java/org/gnu/emacs/EmacsNoninteractive.java @@ -46,21 +46,6 @@ import java.lang.reflect.Method; @SuppressWarnings ("unchecked") public final class EmacsNoninteractive { - private static String - getLibraryDirectory (Context context) - { - int apiLevel; - - apiLevel = Build.VERSION.SDK_INT; - - if (apiLevel >= Build.VERSION_CODES.GINGERBREAD) - return context.getApplicationInfo().nativeLibraryDir; - else if (apiLevel >= Build.VERSION_CODES.DONUT) - return context.getApplicationInfo().dataDir + "/lib"; - - return "/data/data/" + context.getPackageName() + "/lib"; - } - public static void main (String[] args) { @@ -188,7 +173,7 @@ public final class EmacsNoninteractive /* Now configure Emacs. The class path should already be set. */ filesDir = context.getFilesDir ().getCanonicalPath (); - libDir = getLibraryDirectory (context); + libDir = EmacsService.getLibraryDirectory (context); cacheDir = context.getCacheDir ().getCanonicalPath (); } catch (Exception e) @@ -198,6 +183,7 @@ public final class EmacsNoninteractive System.err.println ("and that Emacs needs adjustments in order to"); System.err.println ("obtain required system internal resources."); System.err.println ("Please report this bug to bug-gnu-emacs@gnu.org."); + e.printStackTrace (); System.exit (1); } diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index ac643ae8a13..51335ddb2dd 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -46,7 +46,6 @@ package org.gnu.emacs; import android.app.AlertDialog; import android.app.Activity; -import android.content.Context; import android.content.ContentResolver; import android.content.DialogInterface; import android.content.Intent; @@ -301,23 +300,6 @@ public final class EmacsOpenActivity extends Activity }); } - public String - getLibraryDirectory () - { - int apiLevel; - Context context; - - context = getApplicationContext (); - apiLevel = Build.VERSION.SDK_INT; - - if (apiLevel >= Build.VERSION_CODES.GINGERBREAD) - return context.getApplicationInfo().nativeLibraryDir; - else if (apiLevel >= Build.VERSION_CODES.DONUT) - return context.getApplicationInfo().dataDir + "/lib"; - - return "/data/data/" + context.getPackageName() + "/lib"; - } - public void startEmacsClient (String fileName) { @@ -327,7 +309,7 @@ public final class EmacsOpenActivity extends Activity EmacsClientThread thread; File file; - libDir = getLibraryDirectory (); + libDir = EmacsService.getLibraryDirectory (this); builder = new ProcessBuilder (libDir + "/libemacsclient.so", fileName, "--reuse-frame", "--timeout=10", "--no-wait"); diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index ae91c299de8..6df102f18a2 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -20,7 +20,6 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; import java.io.File; -import java.io.IOException; import java.util.LinkedList; import java.util.List; @@ -32,8 +31,6 @@ 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"; diff --git a/java/org/gnu/emacs/EmacsSdk8Clipboard.java b/java/org/gnu/emacs/EmacsSdk8Clipboard.java index 818a722a908..5a40128b0ac 100644 --- a/java/org/gnu/emacs/EmacsSdk8Clipboard.java +++ b/java/org/gnu/emacs/EmacsSdk8Clipboard.java @@ -19,7 +19,9 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; -/* Importing the entire package avoids the deprecation warning. */ +/* Importing the entire package instead of just the legacy + ClipboardManager class avoids the deprecation warning. */ + import android.text.*; import android.content.Context; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index d9cb25f3e9c..d05ebce75dc 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -24,22 +24,15 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.List; -import java.util.ArrayList; -import android.graphics.Canvas; -import android.graphics.Bitmap; import android.graphics.Point; -import android.view.View; import android.view.InputDevice; import android.view.KeyEvent; -import android.annotation.TargetApi; - import android.app.Notification; import android.app.NotificationManager; import android.app.NotificationChannel; -import android.app.PendingIntent; import android.app.Service; import android.content.ClipboardManager; @@ -51,9 +44,6 @@ import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.PackageManager; import android.content.res.AssetManager; -import android.database.Cursor; -import android.database.MatrixCursor; - import android.hardware.input.InputManager; import android.net.Uri; @@ -68,9 +58,6 @@ import android.os.Vibrator; import android.os.VibratorManager; import android.os.VibrationEffect; -import android.provider.DocumentsContract; -import android.provider.DocumentsContract.Document; - import android.util.Log; import android.util.DisplayMetrics; @@ -87,7 +74,6 @@ class Holder public final class EmacsService extends Service { public static final String TAG = "EmacsService"; - public static final int MAX_PENDING_REQUESTS = 256; public static volatile EmacsService SERVICE; public static boolean needDashQ; @@ -107,6 +93,22 @@ public final class EmacsService extends Service information. */ public static final boolean DEBUG_IC = false; + /* Return the directory leading to the directory in which native + library files are stored on behalf of CONTEXT. */ + + public static String + getLibraryDirectory (Context context) + { + int apiLevel; + + apiLevel = Build.VERSION.SDK_INT; + + if (apiLevel >= Build.VERSION_CODES.GINGERBREAD) + return context.getApplicationInfo ().nativeLibraryDir; + + return context.getApplicationInfo ().dataDir + "/lib"; + } + @Override public int onStartCommand (Intent intent, int flags, int startId) @@ -178,23 +180,6 @@ public final class EmacsService extends Service } } - private String - getLibraryDirectory () - { - int apiLevel; - Context context; - - context = getApplicationContext (); - apiLevel = Build.VERSION.SDK_INT; - - if (apiLevel >= Build.VERSION_CODES.GINGERBREAD) - return context.getApplicationInfo().nativeLibraryDir; - else if (apiLevel >= Build.VERSION_CODES.DONUT) - return context.getApplicationInfo().dataDir + "/lib"; - - return "/data/data/" + context.getPackageName() + "/lib"; - } - @Override public void onCreate () @@ -219,7 +204,7 @@ public final class EmacsService extends Service /* Configure Emacs with the asset manager and other necessary parameters. */ filesDir = app_context.getFilesDir ().getCanonicalPath (); - libDir = getLibraryDirectory (); + libDir = getLibraryDirectory (this); cacheDir = app_context.getCacheDir ().getCanonicalPath (); /* Now provide this application's apk file, so a recursive diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 617836d8811..aefc79c4fb7 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -20,7 +20,6 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; import android.content.Context; -import android.content.res.ColorStateList; import android.text.InputType; @@ -116,6 +115,7 @@ public final class EmacsView extends ViewGroup super (EmacsService.SERVICE); Object tem; + Context context; this.window = window; this.damageRegion = new Region (); @@ -133,7 +133,8 @@ public final class EmacsView extends ViewGroup setDefaultFocusHighlightEnabled (false); /* Obtain the input method manager. */ - tem = getContext ().getSystemService (Context.INPUT_METHOD_SERVICE); + context = getContext (); + tem = context.getSystemService (Context.INPUT_METHOD_SERVICE); imManager = (InputMethodManager) tem; } @@ -281,7 +282,6 @@ public final class EmacsView extends ViewGroup int count, i; View child; Rect windowRect; - int wantedWidth, wantedHeight; count = getChildCount (); @@ -422,7 +422,6 @@ public final class EmacsView extends ViewGroup } } - /* The following two functions must not be called if the view has no parent, or is parented to an activity. */ diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index ea4cf48090d..ffc7476f010 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -31,19 +31,16 @@ import android.content.Context; import android.graphics.Rect; import android.graphics.Canvas; import android.graphics.Bitmap; -import android.graphics.Point; import android.graphics.PixelFormat; import android.view.View; import android.view.ViewManager; -import android.view.ViewGroup; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.InputDevice; import android.view.WindowManager; -import android.content.Intent; import android.util.Log; import android.os.Build; @@ -87,7 +84,7 @@ public final class EmacsWindow extends EmacsHandleObject public EmacsWindow parent; /* List of all children in stacking order. This must be kept - consistent! */ + consistent with their Z order! */ public ArrayList children; /* Map between pointer identifiers and last known position. Used to @@ -105,9 +102,8 @@ public final class EmacsWindow extends EmacsHandleObject last button press or release event. */ public int lastButtonState, lastModifiers; - /* Whether or not the window is mapped, and whether or not it is - deiconified. */ - private boolean isMapped, isIconified; + /* Whether or not the window is mapped. */ + private boolean isMapped; /* Whether or not to ask for focus upon being mapped, and whether or not the window should be focusable. */ @@ -122,7 +118,8 @@ public final class EmacsWindow extends EmacsHandleObject private WindowManager windowManager; /* The time of the last KEYCODE_VOLUME_DOWN release. This is used - to quit Emacs. */ + to quit Emacs upon two rapid clicks of the volume down + button. */ private long lastVolumeButtonRelease; /* Linked list of character strings which were recently sent as @@ -1103,14 +1100,12 @@ public final class EmacsWindow extends EmacsHandleObject public void noticeIconified () { - isIconified = true; EmacsNative.sendIconified (this.handle); } public void noticeDeiconified () { - isIconified = false; EmacsNative.sendDeiconified (this.handle); } diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 1548bf28087..30f29250970 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -20,7 +20,6 @@ along with GNU Emacs. If not, see . */ package org.gnu.emacs; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import android.content.Intent; @@ -74,8 +73,8 @@ public final class EmacsWindowAttachmentManager public EmacsWindowAttachmentManager () { - consumers = new LinkedList (); - windows = new LinkedList (); + consumers = new ArrayList (); + windows = new ArrayList (); } public void -- cgit v1.3 From 97ca0a855116797779450bfb758ea6c706348df3 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 6 Mar 2023 11:25:51 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsService.java (sync): Delete function. * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap): Erase with window background. (onDetachedFromWindow): Only recycle bitmap if non-NULL. * java/org/gnu/emacs/EmacsWindow.java (background): New field. (changeWindowBackground): Set it. * src/android.c (struct android_emacs_service): Remove `sync'. (android_init_emacs_service): Likewise. (android_sync): Delete function. * src/androidfns.c (android_create_tip_frame): Set frame background color correctly. (Fx_show_tip): Make the tip frame visible. * src/androidgui.h: Update prototypes. * src/androidterm.c (handle_one_android_event): Handle tooltip movement correctly. --- java/org/gnu/emacs/EmacsService.java | 19 ------------------- java/org/gnu/emacs/EmacsView.java | 7 +++++-- java/org/gnu/emacs/EmacsWindow.java | 7 +++++++ src/android.c | 11 ----------- src/androidfns.c | 12 +++++++----- src/androidgui.h | 2 -- src/androidterm.c | 10 ++++++++++ 7 files changed, 29 insertions(+), 39 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index d05ebce75dc..f99d7a40067 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -481,25 +481,6 @@ public final class EmacsService extends Service return String.valueOf (keysym); } - public void - sync () - { - Runnable runnable; - - runnable = new Runnable () { - public void - run () - { - synchronized (this) - { - notify (); - } - } - }; - - syncRunnable (runnable); - } - /* Start the Emacs service if necessary. On Android 26 and up, diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index aefc79c4fb7..f751eaaa3e4 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -168,7 +168,7 @@ public final class EmacsView extends ViewGroup = Bitmap.createBitmap (measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888); - bitmap.eraseColor (0xffffffff); + bitmap.eraseColor (window.background | 0xff000000); /* And canvases. */ canvas = new Canvas (bitmap); @@ -507,7 +507,10 @@ public final class EmacsView extends ViewGroup synchronized (this) { /* Recycle the bitmap and call GC. */ - bitmap.recycle (); + + if (bitmap != null) + bitmap.recycle (); + bitmap = null; canvas = null; surfaceView.setBitmap (null, null); diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index ffc7476f010..9d907d2b481 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -129,6 +129,10 @@ public final class EmacsWindow extends EmacsHandleObject /* Whether or not this window is fullscreen. */ public boolean fullscreen; + /* The window background pixel. This is used by EmacsView when + creating new bitmaps. */ + public volatile int background; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height, boolean overrideRedirect) @@ -183,6 +187,9 @@ public final class EmacsWindow extends EmacsHandleObject /* scratchGC is used as the argument to a FillRectangles req. */ scratchGC.foreground = pixel; scratchGC.markDirty (false); + + /* Make the background known to the view as well. */ + background = pixel; } public Rect diff --git a/src/android.c b/src/android.c index 656971e154f..9fc4143602c 100644 --- a/src/android.c +++ b/src/android.c @@ -104,7 +104,6 @@ struct android_emacs_service jmethodID get_screen_height; jmethodID detect_mouse; jmethodID name_keysym; - jmethodID sync; jmethodID browse_url; jmethodID restart_emacs; jmethodID update_ic; @@ -2122,7 +2121,6 @@ android_init_emacs_service (void) FIND_METHOD (get_screen_height, "getScreenHeight", "(Z)I"); FIND_METHOD (detect_mouse, "detectMouse", "()Z"); FIND_METHOD (name_keysym, "nameKeysym", "(I)Ljava/lang/String;"); - FIND_METHOD (sync, "sync", "()V"); FIND_METHOD (browse_url, "browseUrl", "(Ljava/lang/String;)" "Ljava/lang/String;"); FIND_METHOD (restart_emacs, "restartEmacs", "()V"); @@ -4514,15 +4512,6 @@ android_translate_coordinates (android_window src, int x, ANDROID_DELETE_LOCAL_REF (coordinates); } -void -android_sync (void) -{ - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.sync); - android_exception_check (); -} - int android_wc_lookup_string (android_key_pressed_event *event, wchar_t *buffer_return, int wchars_buffer, diff --git a/src/androidfns.c b/src/androidfns.c index 4837b00a21e..5a23e8bd196 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -1883,9 +1883,10 @@ android_create_tip_frame (struct android_display_info *dpyinfo, unsigned long mask; block_input (); - mask = ANDROID_CW_OVERRIDE_REDIRECT; + mask = ANDROID_CW_OVERRIDE_REDIRECT | ANDROID_CW_BACK_PIXEL; attrs.override_redirect = true; + attrs.background_pixel = FRAME_BACKGROUND_PIXEL (f); tip_window = FRAME_ANDROID_WINDOW (f) = android_create_window (FRAME_DISPLAY_INFO (f)->root_window, @@ -2314,10 +2315,6 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, android_map_raised (FRAME_ANDROID_WINDOW (tip_f)); unblock_input (); - /* Synchronize with the UI thread. This is required to prevent ugly - black splotches. */ - android_sync (); - /* Garbage the tip frame too. */ SET_FRAME_GARBAGED (tip_f); @@ -2328,6 +2325,11 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, unbind_to (count_1, Qnil); windows_or_buffers_changed = old_windows_or_buffers_changed; + /* MapNotify events are not sent on Android, so make the frame + visible. */ + + SET_FRAME_VISIBLE (tip_f, true); + start_timer: /* Let the tip disappear after timeout seconds. */ tip_timer = call3 (Qrun_at_time, timeout, Qnil, diff --git a/src/androidgui.h b/src/androidgui.h index 84419457a8a..6514c78d289 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -606,8 +606,6 @@ extern void android_move_resize_window (android_window, int, int, extern void android_map_raised (android_window); extern void android_translate_coordinates (android_window, int, int, int *, int *); -extern void android_sync (void); - extern int android_wc_lookup_string (android_key_pressed_event *, wchar_t *, int, int *, enum android_lookup_status *); diff --git a/src/androidterm.c b/src/androidterm.c index 814af87819b..a6709ac8169 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -676,6 +676,16 @@ handle_one_android_event (struct android_display_info *dpyinfo, if (!f) goto OTHER; + if (FRAME_TOOLTIP_P (f)) + { + if (FRAME_PIXEL_HEIGHT (f) != configureEvent.xconfigure.height + || FRAME_PIXEL_WIDTH (f) != configureEvent.xconfigure.width) + SET_FRAME_GARBAGED (f); + + FRAME_PIXEL_HEIGHT (f) = configureEvent.xconfigure.height; + FRAME_PIXEL_WIDTH (f) = configureEvent.xconfigure.width; + } + int width = configureEvent.xconfigure.width; int height = configureEvent.xconfigure.height; -- cgit v1.3 From 83c29bd40e0c7e3975f575067a3112a0191b5590 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 7 Mar 2023 16:49:45 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsWindow.java (onSomeKindOfMotionEvent): Dismiss splurious LeaveNotify events from button presses. * src/android.c (android_change_window_attributes) (android_change_gc, android_set_clip_rectangles) (android_reparent_window, android_clear_window, android_map_window) (android_unmap_window, android_resize_window, android_move_window) (android_swap_buffers, android_fill_rectangle, android_copy_area) (android_fill_polygon, android_draw_rectangle, android_draw_point) (android_draw_line, android_clear_area, android_bell) (android_set_input_focus, android_raise_window) (android_lower_window, android_set_dont_focus_on_map) (android_set_dont_accept_focus, android_get_keysym_name) (android_toggle_on_screen_keyboard, android_restart_emacs) (android_display_toast, android_update_ic, android_reset_ic) (android_set_fullscreen): Optimize by specifying the class explicitly when calling a method. --- java/org/gnu/emacs/EmacsWindow.java | 13 +- src/android.c | 285 +++++++++++++++++++++--------------- 2 files changed, 179 insertions(+), 119 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 9d907d2b481..3569d93136b 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -906,9 +906,16 @@ public final class EmacsWindow extends EmacsHandleObject return true; case MotionEvent.ACTION_HOVER_EXIT: - EmacsNative.sendLeaveNotify (this.handle, (int) event.getX (), - (int) event.getY (), - event.getEventTime ()); + + /* If the exit event comes from a button press, its button + state will have extra bits compared to the last known + button state. Since the exit event will interfere with + tool bar button presses, ignore such splurious events. */ + + if ((event.getButtonState () & ~lastButtonState) == 0) + EmacsNative.sendLeaveNotify (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime ()); return true; case MotionEvent.ACTION_BUTTON_PRESS: diff --git a/src/android.c b/src/android.c index f49249de09f..11b0fa5e0f3 100644 --- a/src/android.c +++ b/src/android.c @@ -2966,15 +2966,19 @@ android_change_window_attributes (android_window handle, { jmethodID method; jobject window; + jint pixel; window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); if (value_mask & ANDROID_CW_BACK_PIXEL) { method = window_class.change_window_background; - (*android_java_env)->CallVoidMethod (android_java_env, - window, method, - (jint) attrs->background_pixel); + pixel = (jint) attrs->background_pixel; + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + method, pixel); + android_exception_check (); } } @@ -3352,10 +3356,14 @@ android_change_gc (struct android_gc *gc, values->ts_y_origin); if (mask) - (*android_java_env)->CallVoidMethod (android_java_env, - gcontext, - emacs_gc_mark_dirty, - (jboolean) clip_changed); + { + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + gcontext, + emacs_gc_class, + emacs_gc_mark_dirty, + (jboolean) clip_changed); + android_exception_check (); + } } void @@ -3417,10 +3425,12 @@ android_set_clip_rectangles (struct android_gc *gc, int clip_x_origin, emacs_gc_clip_y_origin, clip_y_origin); - (*android_java_env)->CallVoidMethod (android_java_env, - gcontext, - emacs_gc_mark_dirty, - (jboolean) true); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + gcontext, + emacs_gc_class, + emacs_gc_mark_dirty, + (jboolean) true); + android_exception_check (); /* Cache the clip rectangles on the C side for sfntfont-android.c. */ @@ -3448,9 +3458,10 @@ android_reparent_window (android_window w, android_window parent_handle, ANDROID_HANDLE_WINDOW); method = window_class.reparent_to; - (*android_java_env)->CallVoidMethod (android_java_env, window, - method, - parent, (jint) x, (jint) y); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window, + window_class.class, method, + parent, (jint) x, (jint) y); + android_exception_check (); } void @@ -3460,10 +3471,12 @@ android_clear_window (android_window handle) window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.clear_window, - window); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.clear_window, + window); + android_exception_check (); } void @@ -3475,8 +3488,11 @@ android_map_window (android_window handle) window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); map_window = window_class.map_window; - (*android_java_env)->CallVoidMethod (android_java_env, - window, map_window); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + map_window); + android_exception_check (); } void @@ -3488,8 +3504,11 @@ android_unmap_window (android_window handle) window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); unmap_window = window_class.unmap_window; - (*android_java_env)->CallVoidMethod (android_java_env, - window, unmap_window); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + unmap_window); + android_exception_check (); } void @@ -3502,9 +3521,13 @@ android_resize_window (android_window handle, unsigned int width, window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); resize_window = window_class.resize_window; - (*android_java_env)->CallVoidMethod (android_java_env, - window, resize_window, - (jint) width, (jint) height); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + resize_window, + (jint) width, + (jint) height); + android_exception_check (); } void @@ -3516,9 +3539,12 @@ android_move_window (android_window handle, int x, int y) window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); move_window = window_class.move_window; - (*android_java_env)->CallVoidMethod (android_java_env, - window, move_window, - (jint) x, (jint) y); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + move_window, + (jint) x, (jint) y); + android_exception_check (); } void @@ -3532,9 +3558,11 @@ android_swap_buffers (struct android_swap_info *swap_info, { window = android_resolve_handle (swap_info[i].swap_window, ANDROID_HANDLE_WINDOW); - (*android_java_env)->CallVoidMethod (android_java_env, - window, - window_class.swap_buffers); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + window_class.swap_buffers); + android_exception_check (); } } @@ -3618,14 +3646,15 @@ android_fill_rectangle (android_drawable handle, struct android_gc *gc, gcontext = android_resolve_handle (gc->gcontext, ANDROID_HANDLE_GCONTEXT); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.fill_rectangle, - drawable, - gcontext, - (jint) x, (jint) y, - (jint) width, - (jint) height); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.fill_rectangle, + drawable, + gcontext, + (jint) x, (jint) y, + (jint) width, + (jint) height); } android_pixmap @@ -3752,15 +3781,16 @@ android_copy_area (android_drawable src, android_drawable dest, gcontext = android_resolve_handle (gc->gcontext, ANDROID_HANDLE_GCONTEXT); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.copy_area, - src_object, - dest_object, - gcontext, - (jint) src_x, (jint) src_y, - (jint) width, (jint) height, - (jint) dest_x, (jint) dest_y); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.copy_area, + src_object, + dest_object, + gcontext, + (jint) src_x, (jint) src_y, + (jint) width, (jint) height, + (jint) dest_x, (jint) dest_y); } void @@ -3813,11 +3843,12 @@ android_fill_polygon (android_drawable drawable, struct android_gc *gc, ANDROID_DELETE_LOCAL_REF (point); } - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.fill_polygon, - drawable_object, - gcontext, array); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.fill_polygon, + drawable_object, + gcontext, array); ANDROID_DELETE_LOCAL_REF (array); } @@ -3833,12 +3864,13 @@ android_draw_rectangle (android_drawable handle, struct android_gc *gc, gcontext = android_resolve_handle (gc->gcontext, ANDROID_HANDLE_GCONTEXT); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.draw_rectangle, - drawable, gcontext, - (jint) x, (jint) y, - (jint) width, (jint) height); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.draw_rectangle, + drawable, gcontext, + (jint) x, (jint) y, + (jint) width, (jint) height); } void @@ -3853,11 +3885,12 @@ android_draw_point (android_drawable handle, struct android_gc *gc, gcontext = android_resolve_handle (gc->gcontext, ANDROID_HANDLE_GCONTEXT); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.draw_point, - drawable, gcontext, - (jint) x, (jint) y); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.draw_point, + drawable, gcontext, + (jint) x, (jint) y); } void @@ -3872,12 +3905,13 @@ android_draw_line (android_drawable handle, struct android_gc *gc, gcontext = android_resolve_handle (gc->gcontext, ANDROID_HANDLE_GCONTEXT); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.draw_line, - drawable, gcontext, - (jint) x, (jint) y, - (jint) x2, (jint) y2); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.draw_line, + drawable, gcontext, + (jint) x, (jint) y, + (jint) x2, (jint) y2); } android_pixmap @@ -3941,11 +3975,12 @@ android_clear_area (android_window handle, int x, int y, window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.clear_area, - window, (jint) x, (jint) y, - (jint) width, (jint) height); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.clear_area, + window, (jint) x, (jint) y, + (jint) width, (jint) height); } android_pixmap @@ -4307,9 +4342,10 @@ android_put_image (android_pixmap handle, struct android_image *image) void android_bell (void) { - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.ring_bell); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.ring_bell); android_exception_check (); } @@ -4322,8 +4358,11 @@ android_set_input_focus (android_window handle, unsigned long time) window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); make_input_focus = window_class.make_input_focus; - (*android_java_env)->CallVoidMethod (android_java_env, window, - make_input_focus, (jlong) time); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + service_class.class, + make_input_focus, + (jlong) time); android_exception_check (); } @@ -4336,8 +4375,10 @@ android_raise_window (android_window handle) window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); raise = window_class.raise; - (*android_java_env)->CallVoidMethod (android_java_env, window, - raise); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + raise); android_exception_check (); } @@ -4350,8 +4391,10 @@ android_lower_window (android_window handle) window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); lower = window_class.lower; - (*android_java_env)->CallVoidMethod (android_java_env, window, - lower); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window, + window_class.class, + lower); android_exception_check (); } @@ -4767,9 +4810,11 @@ android_set_dont_focus_on_map (android_window handle, window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); method = window_class.set_dont_focus_on_map; - (*android_java_env)->CallVoidMethod (android_java_env, window, - method, - (jboolean) no_focus_on_map); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window, + window_class.class, + method, + (jboolean) no_focus_on_map); + android_exception_check (); } void @@ -4782,9 +4827,11 @@ android_set_dont_accept_focus (android_window handle, window = android_resolve_handle (handle, ANDROID_HANDLE_WINDOW); method = window_class.set_dont_accept_focus; - (*android_java_env)->CallVoidMethod (android_java_env, window, - method, - (jboolean) no_accept_focus); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window, + window_class.class, + method, + (jboolean) no_accept_focus); + android_exception_check (); } void @@ -4802,7 +4849,7 @@ android_get_keysym_name (int keysym, char *name_return, size_t size) buffer = (*android_java_env)->GetStringUTFChars (android_java_env, (jstring) string, NULL); - android_exception_check (); + android_exception_check_nonnull ((void *) buffer, string); strncpy (name_return, buffer, size - 1); (*android_java_env)->ReleaseStringUTFChars (android_java_env, @@ -4827,8 +4874,9 @@ android_toggle_on_screen_keyboard (android_window window, bool show) method = window_class.toggle_on_screen_keyboard; /* Now display the on screen keyboard. */ - (*android_java_env)->CallVoidMethod (android_java_env, object, - method, (jboolean) show); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, object, + window_class.class, + method, (jboolean) show); /* Check for out of memory errors. */ android_exception_check (); @@ -5645,9 +5693,10 @@ android_restart_emacs (void) { /* Try to call the Java side function. Normally, this should call System.exit to terminate this process. */ - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.restart_emacs); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.restart_emacs); /* Exit anyway, in case EmacsService did not do so. */ exit (0); @@ -5720,10 +5769,11 @@ android_display_toast (const char *text) android_exception_check (); /* Display the toast. */ - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.display_toast, - string); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.display_toast, + string); android_exception_check_1 (string); /* Delete the local reference to the string. */ @@ -5896,14 +5946,15 @@ android_update_ic (android_window window, ptrdiff_t selection_start, object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.update_ic, - object, - (jint) selection_start, - (jint) selection_end, - (jint) composing_region_start, - (jint) composing_region_end); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.update_ic, + object, + (jint) selection_start, + (jint) selection_end, + (jint) composing_region_start, + (jint) composing_region_end); android_exception_check (); } @@ -5932,10 +5983,11 @@ android_reset_ic (android_window window, enum android_ic_mode mode) object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); - (*android_java_env)->CallVoidMethod (android_java_env, - emacs_service, - service_class.reset_ic, - object, (jint) mode); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + service_class.reset_ic, + object, (jint) mode); android_exception_check (); } @@ -5961,10 +6013,11 @@ android_set_fullscreen (android_window window, bool fullscreen) object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); - (*android_java_env)->CallVoidMethod (android_java_env, - object, - window_class.set_fullscreen, - (jboolean) fullscreen); + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + object, + window_class.class, + window_class.set_fullscreen, + (jboolean) fullscreen); android_exception_check (); return 0; } -- cgit v1.3 From ae5513ede52536df2cd823699d6168985915ce0f Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Mar 2023 15:16:05 +0800 Subject: Implement mouse cursors on Android 7.0 and later * java/org/gnu/emacs/EmacsWindow.java (defineCursor): New function. * src/android.c (struct android_emacs_cursor): New struct. (android_init_emacs_cursor): New function. (JNICALL): Call it. (android_create_font_cursor, android_define_cursor) (android_free_cursor): New functions. * src/android.h (enum android_handle_type): Add cursor handle type. * src/androidfns.c (Fx_create_frame, android_create_tip_frame) (enum mouse_cursor, struct mouse_cursor_types, mouse_cursor_types) (struct mouse_cursor_data, android_set_mouse_color) (syms_of_androidfns): * src/androidgui.h (enum android_cursor_shape): * src/androidterm.c (make_invisible_cursor) (android_toggle_invisible_pointer, android_free_frame_resources) (android_define_frame_cursor): * src/androidterm.h (struct android_display_info) (struct android_output): Port mouse cursor code over from X. --- java/org/gnu/emacs/EmacsWindow.java | 16 +++ src/android.c | 118 +++++++++++++++++++ src/android.h | 1 + src/androidfns.c | 221 +++++++++++++++++++++++++++++++++++- src/androidgui.h | 28 ++++- src/androidterm.c | 68 ++++++++++- src/androidterm.h | 6 +- 7 files changed, 448 insertions(+), 10 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 3569d93136b..6be609edcfe 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -1222,4 +1222,20 @@ public final class EmacsWindow extends EmacsHandleObject } }); } + + public void + defineCursor (final EmacsCursor cursor) + { + /* Don't post this message if pointer icons aren't supported. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + view.post (new Runnable () { + @Override + public void + run () + { + view.setPointerIcon (cursor.icon); + } + }); + } }; diff --git a/src/android.c b/src/android.c index 763e17e9430..e39c34136de 100644 --- a/src/android.c +++ b/src/android.c @@ -155,6 +155,13 @@ struct android_emacs_window jmethodID translate_coordinates; jmethodID set_dont_accept_focus; jmethodID set_dont_focus_on_map; + jmethodID define_cursor; +}; + +struct android_emacs_cursor +{ + jclass class; + jmethodID constructor; }; /* The API level of the current device. */ @@ -234,6 +241,9 @@ static struct android_emacs_drawable drawable_class; /* Various methods associated with the EmacsWindow class. */ static struct android_emacs_window window_class; +/* Various methods associated with the EmacsCursor class. */ +static struct android_emacs_cursor cursor_class; + /* The last event serial used. This is a 32 bit value, but it is stored in unsigned long to be consistent with X. */ unsigned int event_serial; @@ -2288,6 +2298,38 @@ android_init_emacs_window (void) "(II)[I"); FIND_METHOD (set_dont_focus_on_map, "setDontFocusOnMap", "(Z)V"); FIND_METHOD (set_dont_accept_focus, "setDontAcceptFocus", "(Z)V"); + FIND_METHOD (define_cursor, "defineCursor", + "(Lorg/gnu/emacs/EmacsCursor;)V"); +#undef FIND_METHOD +} + +static void +android_init_emacs_cursor (void) +{ + jclass old; + + cursor_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsCursor"); + eassert (cursor_class.class); + + old = cursor_class.class; + cursor_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + (jobject) old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!cursor_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + cursor_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + cursor_class.class, \ + name, signature); \ + assert (cursor_class.c_name); + + FIND_METHOD (constructor, "", "(SI)V"); #undef FIND_METHOD } @@ -2339,6 +2381,7 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, android_init_graphics_point (); android_init_emacs_drawable (); android_init_emacs_window (); + android_init_emacs_cursor (); /* Set HOME to the app data directory. */ setenv ("HOME", android_files_dir, 1); @@ -6243,6 +6286,81 @@ android_asset_fstat (struct android_fd_or_asset asset, return 0; } + + +/* Window cursor support. */ + +android_cursor +android_create_font_cursor (enum android_cursor_shape shape) +{ + android_cursor id; + short prev_max_handle; + jobject object; + + /* First, allocate the cursor handle. */ + prev_max_handle = max_handle; + id = android_alloc_id (); + + if (!id) + error ("Out of cursor handles!"); + + /* Next, create the cursor. */ + object = (*android_java_env)->NewObject (android_java_env, + cursor_class.class, + cursor_class.constructor, + (jshort) id, + (jint) shape); + if (!object) + { + (*android_java_env)->ExceptionClear (android_java_env); + max_handle = prev_max_handle; + memory_full (0); + } + + android_handles[id].type = ANDROID_HANDLE_CURSOR; + android_handles[id].handle + = (*android_java_env)->NewGlobalRef (android_java_env, object); + (*android_java_env)->ExceptionClear (android_java_env); + ANDROID_DELETE_LOCAL_REF (object); + + if (!android_handles[id].handle) + memory_full (0); + + return id; +} + +void +android_define_cursor (android_window window, android_cursor cursor) +{ + jobject window1, cursor1; + jmethodID method; + + window1 = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + cursor1 = (cursor + ? android_resolve_handle (cursor, ANDROID_HANDLE_CURSOR) + : NULL); + method = window_class.define_cursor; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + window1, + window_class.class, + method, cursor1); + android_exception_check (); +} + +void +android_free_cursor (android_cursor cursor) +{ + if (android_handles[cursor].type != ANDROID_HANDLE_CURSOR) + { + __android_log_print (ANDROID_LOG_ERROR, __func__, + "Trying to destroy something not a CURSOR!"); + emacs_abort (); + } + + android_destroy_handle (cursor); +} + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ diff --git a/src/android.h b/src/android.h index 450f3859df9..03592bd955d 100644 --- a/src/android.h +++ b/src/android.h @@ -63,6 +63,7 @@ enum android_handle_type ANDROID_HANDLE_WINDOW, ANDROID_HANDLE_GCONTEXT, ANDROID_HANDLE_PIXMAP, + ANDROID_HANDLE_CURSOR, }; extern jobject android_resolve_handle (android_handle, diff --git a/src/androidfns.c b/src/androidfns.c index 2724b9595c1..589ae4331cb 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -802,6 +802,7 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame, FRAME_BACKGROUND_PIXEL (f) = -1; f->output_data.android->cursor_pixel = -1; f->output_data.android->cursor_foreground_pixel = -1; + f->output_data.android->mouse_pixel = -1; black = build_string ("black"); FRAME_FOREGROUND_PIXEL (f) @@ -812,6 +813,8 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame, = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); f->output_data.android->cursor_foreground_pixel = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); + f->output_data.android->mouse_pixel + = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); } /* Set the name; the functions to which we pass f expect the name to @@ -1798,6 +1801,7 @@ android_create_tip_frame (struct android_display_info *dpyinfo, FRAME_BACKGROUND_PIXEL (f) = -1; f->output_data.android->cursor_pixel = -1; f->output_data.android->cursor_foreground_pixel = -1; + f->output_data.android->mouse_pixel = -1; black = build_string ("black"); FRAME_FOREGROUND_PIXEL (f) @@ -1808,6 +1812,8 @@ android_create_tip_frame (struct android_display_info *dpyinfo, = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); f->output_data.android->cursor_foreground_pixel = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); + f->output_data.android->mouse_pixel + = android_decode_color (f, black, BLACK_PIX_DEFAULT (f)); } /* Set the name; the functions to which we pass f expect the name to @@ -2632,13 +2638,147 @@ android_set_menu_bar_lines (struct frame *f, Lisp_Object value, adjust_frame_glyphs (f); } + + +/* These enums must stay in sync with the mouse_cursor_types array + below! */ + +enum mouse_cursor + { + mouse_cursor_text, + mouse_cursor_nontext, + mouse_cursor_hourglass, + mouse_cursor_mode, + mouse_cursor_hand, + mouse_cursor_horizontal_drag, + mouse_cursor_vertical_drag, + mouse_cursor_left_edge, + mouse_cursor_top_left_corner, + mouse_cursor_top_edge, + mouse_cursor_top_right_corner, + mouse_cursor_right_edge, + mouse_cursor_bottom_right_corner, + mouse_cursor_bottom_edge, + mouse_cursor_bottom_left_corner, + mouse_cursor_max + }; + +struct mouse_cursor_types +{ + /* Printable name for error messages (optional). */ + const char *name; + + /* Lisp variable controlling the cursor shape. */ + /* FIXME: A couple of these variables are defined in the C code but + are not actually accessible from Lisp. They should probably be + made accessible or removed. */ + Lisp_Object *shape_var_ptr; + + /* The default shape. */ + int default_shape; +}; + +/* This array must stay in sync with enum mouse_cursor above! */ + +static const struct mouse_cursor_types mouse_cursor_types[] = + { + {"text", &Vx_pointer_shape, ANDROID_XC_XTERM, }, + {"nontext", &Vx_nontext_pointer_shape, ANDROID_XC_LEFT_PTR, }, + {"hourglass", &Vx_hourglass_pointer_shape, ANDROID_XC_WATCH, }, + {"modeline", &Vx_mode_pointer_shape, ANDROID_XC_XTERM, }, + {NULL, &Vx_sensitive_text_pointer_shape, ANDROID_XC_HAND2, }, + {NULL, &Vx_window_horizontal_drag_shape, ANDROID_XC_SB_H_DOUBLE_ARROW, }, + {NULL, &Vx_window_vertical_drag_shape, ANDROID_XC_SB_V_DOUBLE_ARROW, }, + {NULL, &Vx_window_left_edge_shape, ANDROID_XC_LEFT_SIDE, }, + {NULL, &Vx_window_top_left_corner_shape, ANDROID_XC_TOP_LEFT_CORNER, }, + {NULL, &Vx_window_top_edge_shape, ANDROID_XC_TOP_SIDE, }, + {NULL, &Vx_window_top_right_corner_shape, ANDROID_XC_TOP_RIGHT_CORNER, }, + {NULL, &Vx_window_right_edge_shape, ANDROID_XC_RIGHT_SIDE, }, + {NULL, &Vx_window_bottom_right_corner_shape, + ANDROID_XC_BOTTOM_RIGHT_CORNER, }, + {NULL, &Vx_window_bottom_edge_shape, ANDROID_XC_BOTTOM_SIDE, }, + {NULL, &Vx_window_bottom_left_corner_shape, + ANDROID_XC_BOTTOM_LEFT_CORNER, }, + }; + +struct mouse_cursor_data +{ + /* Cursor numbers chosen. */ + unsigned int cursor_num[mouse_cursor_max]; + + /* Allocated Cursor values, or zero for failed attempts. */ + android_cursor cursor[mouse_cursor_max]; +}; + + + static void android_set_mouse_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval) { - /* Changing the mouse color is unsupported under Android, so this is - left intact. */ - android_decode_color (f, arg, BLACK_PIX_DEFAULT (f)); + struct android_output *x = f->output_data.android; + struct mouse_cursor_data cursor_data = { -1, -1 }; + unsigned long pixel = android_decode_color (f, arg, BLACK_PIX_DEFAULT (f)); + unsigned long mask_color = FRAME_BACKGROUND_PIXEL (f); + int i; + + /* Don't let pointers be invisible. */ + if (mask_color == pixel) + pixel = FRAME_FOREGROUND_PIXEL (f); + + x->mouse_pixel = pixel; + + for (i = 0; i < mouse_cursor_max; i++) + { + Lisp_Object shape_var = *mouse_cursor_types[i].shape_var_ptr; + cursor_data.cursor_num[i] + = (!NILP (shape_var) + ? check_uinteger_max (shape_var, UINT_MAX) + : mouse_cursor_types[i].default_shape); + } + + block_input (); + + for (i = 0; i < mouse_cursor_max; i++) + cursor_data.cursor[i] + = android_create_font_cursor (cursor_data.cursor_num[i]); + + if (FRAME_ANDROID_WINDOW (f)) + { + f->output_data.android->current_cursor + = cursor_data.cursor[mouse_cursor_text]; + android_define_cursor (FRAME_ANDROID_WINDOW (f), + f->output_data.android->current_cursor); + } + +#define INSTALL_CURSOR(FIELD, SHORT_INDEX) \ + eassert (x->FIELD \ + != cursor_data.cursor[mouse_cursor_ ## SHORT_INDEX]); \ + if (x->FIELD != 0) \ + android_free_cursor (x->FIELD); \ + x->FIELD = cursor_data.cursor[mouse_cursor_ ## SHORT_INDEX]; + + INSTALL_CURSOR (text_cursor, text); + INSTALL_CURSOR (nontext_cursor, nontext); + INSTALL_CURSOR (hourglass_cursor, hourglass); + INSTALL_CURSOR (modeline_cursor, mode); + INSTALL_CURSOR (hand_cursor, hand); + INSTALL_CURSOR (horizontal_drag_cursor, horizontal_drag); + INSTALL_CURSOR (vertical_drag_cursor, vertical_drag); + INSTALL_CURSOR (left_edge_cursor, left_edge); + INSTALL_CURSOR (top_left_corner_cursor, top_left_corner); + INSTALL_CURSOR (top_edge_cursor, top_edge); + INSTALL_CURSOR (top_right_corner_cursor, top_right_corner); + INSTALL_CURSOR (right_edge_cursor, right_edge); + INSTALL_CURSOR (bottom_right_corner_cursor, bottom_right_corner); + INSTALL_CURSOR (bottom_edge_cursor, bottom_edge); + INSTALL_CURSOR (bottom_left_corner_cursor, bottom_left_corner); + +#undef INSTALL_CURSOR + + unblock_input (); + + update_face_from_frame_parameter (f, Qmouse_color, arg); } static void @@ -2845,6 +2985,81 @@ syms_of_androidfns (void) DEFSYM (Qtrue_color, "true-color"); DEFSYM (Qalways, "always"); + DEFVAR_LISP ("x-pointer-shape", Vx_pointer_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_pointer_shape = Qnil; + +#if false /* This doesn't really do anything. */ + DEFVAR_LISP ("x-nontext-pointer-shape", Vx_nontext_pointer_shape, + doc: /* SKIP: real doc in xfns.c. */); +#endif + Vx_nontext_pointer_shape = Qnil; + + DEFVAR_LISP ("x-hourglass-pointer-shape", Vx_hourglass_pointer_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_hourglass_pointer_shape = Qnil; + + DEFVAR_LISP ("x-sensitive-text-pointer-shape", + Vx_sensitive_text_pointer_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_sensitive_text_pointer_shape = Qnil; + + DEFVAR_LISP ("x-window-horizontal-drag-cursor", + Vx_window_horizontal_drag_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_horizontal_drag_shape = Qnil; + + DEFVAR_LISP ("x-window-vertical-drag-cursor", + Vx_window_vertical_drag_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_vertical_drag_shape = Qnil; + + DEFVAR_LISP ("x-window-left-edge-cursor", + Vx_window_left_edge_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_left_edge_shape = Qnil; + + DEFVAR_LISP ("x-window-top-left-corner-cursor", + Vx_window_top_left_corner_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_top_left_corner_shape = Qnil; + + DEFVAR_LISP ("x-window-top-edge-cursor", + Vx_window_top_edge_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_top_edge_shape = Qnil; + + DEFVAR_LISP ("x-window-top-right-corner-cursor", + Vx_window_top_right_corner_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_top_right_corner_shape = Qnil; + + DEFVAR_LISP ("x-window-right-edge-cursor", + Vx_window_right_edge_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_right_edge_shape = Qnil; + + DEFVAR_LISP ("x-window-bottom-right-corner-cursor", + Vx_window_bottom_right_corner_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_bottom_right_corner_shape = Qnil; + + DEFVAR_LISP ("x-window-bottom-edge-cursor", + Vx_window_bottom_edge_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_bottom_edge_shape = Qnil; + +#if false /* This doesn't really do anything. */ + DEFVAR_LISP ("x-mode-pointer-shape", Vx_mode_pointer_shape, + doc: /* SKIP: real doc in xfns.c. */); +#endif + Vx_mode_pointer_shape = Qnil; + + DEFVAR_LISP ("x-window-bottom-left-corner-cursor", + Vx_window_bottom_left_corner_shape, + doc: /* SKIP: real text in xfns.c. */); + Vx_window_bottom_left_corner_shape = Qnil; + DEFVAR_LISP ("x-cursor-fore-pixel", Vx_cursor_fore_pixel, doc: /* SKIP: real doc in xfns.c. */); Vx_cursor_fore_pixel = Qnil; diff --git a/src/androidgui.h b/src/androidgui.h index 5858a168080..b918d03ceca 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -36,6 +36,7 @@ typedef android_handle android_pixmap, Emacs_Pixmap; typedef android_handle android_window, Emacs_Window; typedef android_handle android_gcontext, GContext; typedef android_handle android_drawable, Drawable; +typedef android_handle android_cursor, Emacs_Cursor; typedef unsigned int android_time; @@ -162,10 +163,6 @@ struct android_swap_info enum android_swap_action swap_action; }; -/* Android doesn't support cursors, so define this to something - unused. */ -typedef char Emacs_Cursor; - #define NativeRectangle Emacs_Rectangle #define CONVERT_TO_NATIVE_RECT(xr, nr) ((xr) = (nr)) #define CONVERT_FROM_EMACS_RECT(xr, nr) ((nr) = (xr)) @@ -620,6 +617,29 @@ extern void android_update_extracted_text (android_window, void *, int); extern int android_set_fullscreen (android_window, bool); +enum android_cursor_shape + { + ANDROID_XC_XTERM = 1008, + ANDROID_XC_LEFT_PTR = 1000, + ANDROID_XC_WATCH = 1004, + ANDROID_XC_HAND2 = 1002, + ANDROID_XC_SB_H_DOUBLE_ARROW = 1014, + ANDROID_XC_SB_V_DOUBLE_ARROW = 1015, + ANDROID_XC_LEFT_SIDE = 1020, + ANDROID_XC_TOP_LEFT_CORNER = 1020, + ANDROID_XC_TOP_SIDE = 1020, + ANDROID_XC_TOP_RIGHT_CORNER = 1020, + ANDROID_XC_RIGHT_SIDE = 1020, + ANDROID_XC_BOTTOM_RIGHT_CORNER = 1020, + ANDROID_XC_BOTTOM_SIDE = 1020, + ANDROID_XC_BOTTOM_LEFT_CORNER = 1020, + ANDROID_XC_NULL = 0, + }; + +extern android_cursor android_create_font_cursor (enum android_cursor_shape); +extern void android_define_cursor (android_window, android_cursor); +extern void android_free_cursor (android_cursor); + #endif diff --git a/src/androidterm.c b/src/androidterm.c index 2e2bf86706c..3a0f1ccc463 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -224,10 +224,38 @@ android_ring_bell (struct frame *f) } } +static android_cursor +make_invisible_cursor (struct android_display_info *dpyinfo) +{ + return android_create_font_cursor (ANDROID_XC_NULL); +} + static void -android_toggle_invisible_pointer (struct frame *f, bool invisible) +android_toggle_visible_pointer (struct frame *f, bool invisible) { + struct android_display_info *dpyinfo; + + dpyinfo = FRAME_DISPLAY_INFO (f); + + if (!dpyinfo->invisible_cursor) + dpyinfo->invisible_cursor = make_invisible_cursor (dpyinfo); + + if (invisible) + android_define_cursor (FRAME_ANDROID_WINDOW (f), + dpyinfo->invisible_cursor); + else + android_define_cursor (FRAME_ANDROID_WINDOW (f), + f->output_data.android->current_cursor); + f->pointer_invisible = invisible; +} + +static void +android_toggle_invisible_pointer (struct frame *f, bool invisible) +{ + block_input (); + android_toggle_visible_pointer (f, invisible); + unblock_input (); } /* Start an update of frame F. This function is installed as a hook @@ -2040,6 +2068,38 @@ android_free_frame_resources (struct frame *f) android_free_gcs (f); + /* Free cursors. */ + if (f->output_data.android->text_cursor) + android_free_cursor (f->output_data.android->text_cursor); + if (f->output_data.android->nontext_cursor) + android_free_cursor (f->output_data.android->nontext_cursor); + if (f->output_data.android->modeline_cursor) + android_free_cursor (f->output_data.android->modeline_cursor); + if (f->output_data.android->hand_cursor) + android_free_cursor (f->output_data.android->hand_cursor); + if (f->output_data.android->hourglass_cursor) + android_free_cursor (f->output_data.android->hourglass_cursor); + if (f->output_data.android->horizontal_drag_cursor) + android_free_cursor (f->output_data.android->horizontal_drag_cursor); + if (f->output_data.android->vertical_drag_cursor) + android_free_cursor (f->output_data.android->vertical_drag_cursor); + if (f->output_data.android->left_edge_cursor) + android_free_cursor (f->output_data.android->left_edge_cursor); + if (f->output_data.android->top_left_corner_cursor) + android_free_cursor (f->output_data.android->top_left_corner_cursor); + if (f->output_data.android->top_edge_cursor) + android_free_cursor (f->output_data.android->top_edge_cursor); + if (f->output_data.android->top_right_corner_cursor) + android_free_cursor (f->output_data.android->top_right_corner_cursor); + if (f->output_data.android->right_edge_cursor) + android_free_cursor (f->output_data.android->right_edge_cursor); + if (f->output_data.android->bottom_right_corner_cursor) + android_free_cursor (f->output_data.android->bottom_right_corner_cursor); + if (f->output_data.android->bottom_edge_cursor) + android_free_cursor (f->output_data.android->bottom_edge_cursor); + if (f->output_data.android->bottom_left_corner_cursor) + android_free_cursor (f->output_data.android->bottom_left_corner_cursor); + /* Free extra GCs allocated by android_setup_relief_colors. */ if (f->output_data.android->white_relief.gc) { @@ -3971,7 +4031,11 @@ android_draw_glyph_string (struct glyph_string *s) static void android_define_frame_cursor (struct frame *f, Emacs_Cursor cursor) { - /* Not supported, because cursors are not supported on Android. */ + if (!f->pointer_invisible + && f->output_data.android->current_cursor != cursor) + android_define_cursor (FRAME_ANDROID_WINDOW (f), cursor); + + f->output_data.android->current_cursor = cursor; } static void diff --git a/src/androidterm.h b/src/androidterm.h index 9964eb54880..2e59365b56d 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -136,6 +136,9 @@ struct android_display_info /* ID of the last menu event received. -1 means Emacs is waiting for a context menu event. */ int menu_event_id; + + /* The invisible cursor used for pointer blanking. */ + android_cursor invisible_cursor; }; /* Structure representing a single tool (finger or stylus) pressed @@ -176,6 +179,7 @@ struct android_output /* Various colors. */ unsigned long cursor_pixel; + unsigned long mouse_pixel; unsigned long cursor_foreground_pixel; /* Foreground color for scroll bars. A value of -1 means use the @@ -187,7 +191,7 @@ struct android_output bars). */ unsigned long scroll_bar_background_pixel; - /* Unused stuff (cursors). */ + /* Cursors associated with this frame. */ Emacs_Cursor text_cursor; Emacs_Cursor nontext_cursor; Emacs_Cursor modeline_cursor; -- cgit v1.3 From 1eb546309b24f41b124a0f94aee4009c6dbd8580 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Mar 2023 19:13:22 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Windowing): Document how to pass multimedia keys to the system. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function. * java/org/gnu/emacs/EmacsView.java (onKeyDown, onKeyMultiple) (onKeyUp): Check that function. * java/org/gnu/emacs/EmacsWindow.java (defineCursor): Handle cases where cursor is NULL. * src/android.c (NATIVE_NAME): New function. * src/androidfns.c (syms_of_androidfns): New variable. * src/keyboard.c (lispy_function_keys): Add volume keys. --- doc/emacs/android.texi | 10 ++++++++++ java/org/gnu/emacs/EmacsNative.java | 4 ++++ java/org/gnu/emacs/EmacsView.java | 18 ++++++++++++++++++ java/org/gnu/emacs/EmacsWindow.java | 5 ++++- src/android.c | 9 +++++++++ src/androidfns.c | 11 +++++++++++ src/keyboard.c | 5 +++++ 7 files changed, 61 insertions(+), 1 deletion(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 8e98b92314a..d50acda7710 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -498,6 +498,16 @@ selection, Emacs provides an emulation instead. This means there is no way to transfer the contents of the primary selection to another application via cut-and-paste. +@vindex android-pass-multimedia-buttons-to-system +@cindex volume/multimedia buttons, Android + The volume keys are normally reserved by Emacs and used to provide +the ability to quit Emacs without a physical keyboard +(@pxref{On-Screen-Keyboards}.) However, if you want them to adjust +the volume instead, you can set the variable +@code{android-pass-multimedia-buttons-to-system} to a non-@code{nil} +value; note that you will no longer be able to quit Emacs using the +volume buttons in that case. + @node Android Fonts @section Font backends and selection under Android @cindex fonts, android diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index d96c93a83a1..7d13ff99abb 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -174,6 +174,10 @@ public final class EmacsNative main thread's looper to respond. */ public static native void endSynchronous (); + /* Return whether or not KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP and + KEYCODE_VOLUME_MUTE should be forwarded to Emacs. */ + public static native boolean shouldForwardMultimediaButtons (); + /* Input connection functions. These mostly correspond to their diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 6ace609f386..878ef2f3fbf 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -361,6 +361,12 @@ public final class EmacsView extends ViewGroup public boolean onKeyDown (int keyCode, KeyEvent event) { + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP + || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) + && !EmacsNative.shouldForwardMultimediaButtons ()) + return false; + window.onKeyDown (keyCode, event); return true; } @@ -369,6 +375,12 @@ public final class EmacsView extends ViewGroup public boolean onKeyMultiple (int keyCode, int repeatCount, KeyEvent event) { + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP + || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) + && !EmacsNative.shouldForwardMultimediaButtons ()) + return false; + window.onKeyDown (keyCode, event); return true; } @@ -377,6 +389,12 @@ public final class EmacsView extends ViewGroup public boolean onKeyUp (int keyCode, KeyEvent event) { + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP + || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) + && !EmacsNative.shouldForwardMultimediaButtons ()) + return false; + window.onKeyUp (keyCode, event); return true; } diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 6be609edcfe..d786c104153 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -1234,7 +1234,10 @@ public final class EmacsWindow extends EmacsHandleObject public void run () { - view.setPointerIcon (cursor.icon); + if (cursor != null) + view.setPointerIcon (cursor.icon); + else + view.setPointerIcon (null); } }); } diff --git a/src/android.c b/src/android.c index e39c34136de..79598ab86df 100644 --- a/src/android.c +++ b/src/android.c @@ -2825,6 +2825,15 @@ NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object, return event_serial; } +JNIEXPORT jboolean JNICALL +NATIVE_NAME (shouldForwardMultimediaButtons) (JNIEnv *env, + jobject object) +{ + /* Yes, android_pass_multimedia_buttons_to_system is being + read from the UI thread. */ + return !android_pass_multimedia_buttons_to_system; +} + /* Forward declarations of deadlock prevention functions. */ static void android_begin_query (void); diff --git a/src/androidfns.c b/src/androidfns.c index 589ae4331cb..e1d423ab3eb 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -3075,6 +3075,17 @@ syms_of_androidfns (void) doc: /* SKIP: real doc in xfns.c. */); Vx_max_tooltip_size = Qnil; + DEFVAR_BOOL ("android-pass-multimedia-buttons-to-system", + android_pass_multimedia_buttons_to_system, + doc: /* Whether or not to pass volume control buttons to the system. +Generally, the `volume-up', `volume-down' and `volume-mute' keys are +processed by Emacs, but setting this to non-nil they are passed to the +operating system instead of being intercepted by Emacs. + +Note that if you set this, you will no longer be able to quit Emacs +using the volume down button. */); + android_pass_multimedia_buttons_to_system = false; + /* Functions defined. */ defsubr (&Sx_create_frame); defsubr (&Sxw_color_defined_p); diff --git a/src/keyboard.c b/src/keyboard.c index a38c7394543..0a74b435f0e 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4972,6 +4972,7 @@ const char *const lispy_function_keys[] = [111] = "escape", [112] = "delete", [121] = "break", + [120] = "sysrq", [122] = "home", [123] = "end", [124] = "insert", @@ -4988,6 +4989,7 @@ const char *const lispy_function_keys[] = [141] = "f11", [142] = "f12", [160] = "kp-ret", + [164] = "volume-mute", [19] = "up", [20] = "down", [213] = "muhenkan", @@ -4996,6 +4998,8 @@ const char *const lispy_function_keys[] = [218] = "kana", [21] = "left", [22] = "right", + [24] = "volume-up", + [25] = "volume-down", [259] = "help", [268] = "kp-up-left", [269] = "kp-down-left", @@ -5010,6 +5014,7 @@ const char *const lispy_function_keys[] = [66] = "return", [67] = "backspace", [82] = "menu", + [84] = "find", [92] = "prior", [93] = "next", }; -- cgit v1.3 From d6bddca26c7cf827e098ae783e865fcbcdd48799 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 14 Mar 2023 13:19:01 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsWindow.java (figureChange): Detect mice on up events as well. (onSomeKindOfMotionEvent): Work past framework bug. * src/androidterm.c (android_perform_conversion_query): * src/textconv.c (textconv_query): * src/textconv.h (TEXTCONV_SKIP_ACTIVE_REGION): Remove unused code. --- java/org/gnu/emacs/EmacsWindow.java | 33 ++++++++++++++++++++++++++++++++- src/androidterm.c | 4 +--- src/textconv.c | 34 ---------------------------------- src/textconv.h | 1 - 4 files changed, 33 insertions(+), 39 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index d786c104153..a8d1beedef7 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -755,6 +755,14 @@ public final class EmacsWindow extends EmacsHandleObject break; case MotionEvent.ACTION_UP: + + /* Detect mice. If this is a mouse event, give it to + onSomeKindOfMotionEvent. */ + if ((Build.VERSION.SDK_INT + >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + && event.getToolType (0) == MotionEvent.TOOL_TYPE_MOUSE) + return -2; + /* Primary pointer released with index 0. */ pointerID = event.getPointerId (0); pointerMap.remove (pointerID); @@ -916,6 +924,7 @@ public final class EmacsWindow extends EmacsHandleObject EmacsNative.sendLeaveNotify (this.handle, (int) event.getX (), (int) event.getY (), event.getEventTime ()); + return true; case MotionEvent.ACTION_BUTTON_PRESS: @@ -949,13 +958,35 @@ public final class EmacsWindow extends EmacsHandleObject return true; case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_UP: /* Emacs must return true even though touch events are not handled here, because the value of this function is used by the system to decide whether or not Emacs gets ACTION_MOVE events. */ return true; + case MotionEvent.ACTION_UP: + /* However, if ACTION_UP reports a different button state from + the last known state, look up which button was released and + send a ButtonRelease event; this is to work around a bug in + the framework where real ACTION_BUTTON_RELEASE events are + not delivered. */ + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + return true; + + if (event.getButtonState () == 0 && lastButtonState != 0) + { + EmacsNative.sendButtonRelease (this.handle, (int) event.getX (), + (int) event.getY (), + event.getEventTime (), + lastModifiers, + whatButtonWasIt (event, false)); + lastButtonState = event.getButtonState (); + } + + return true; + case MotionEvent.ACTION_SCROLL: /* Send a scroll event with the specified deltas. */ EmacsNative.sendWheel (this.handle, (int) event.getX (), diff --git a/src/androidterm.c b/src/androidterm.c index 72d81744128..0110c4b6dd8 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -4685,8 +4685,6 @@ struct android_conversion_query_context /* Obtain the text from the frame whose window is that specified in DATA using the text conversion query specified there. - Adjust the query position to skip over any active composing region. - Set ((struct android_conversion_query_context *) DATA)->success on success. */ @@ -4704,7 +4702,7 @@ android_perform_conversion_query (void *data) if (!f) return; - textconv_query (f, &context->query, TEXTCONV_SKIP_ACTIVE_REGION); + textconv_query (f, &context->query, 0); /* context->query.text will have been set even if textconv_query returns 1. */ diff --git a/src/textconv.c b/src/textconv.c index 900277016f2..91f6e861c60 100644 --- a/src/textconv.c +++ b/src/textconv.c @@ -147,9 +147,6 @@ select_window (Lisp_Object window, Lisp_Object norecord) If FLAGS & TEXTCONV_SKIP_CONVERSION_REGION, then first move PT past the conversion region in the specified direction if it is inside. - If FLAGS & TEXTCONV_SKIP_ACTIVE_REGION, then also move PT past the - region if the mark is active. - Value is 0 if QUERY->operation was not TEXTCONV_SUBSTITUTION or if deleting the text was successful, and 1 otherwise. */ @@ -234,37 +231,6 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query, } } - /* Likewise for the region if the mark is active. */ - - if (flags & TEXTCONV_SKIP_ACTIVE_REGION) - { - temp = mark; - - if (temp == -1) - goto escape; - - start = min (temp, PT); - end = max (temp, PT); - - if (pos >= start && pos < end) - { - switch (query->direction) - { - case TEXTCONV_FORWARD_CHAR: - case TEXTCONV_FORWARD_WORD: - case TEXTCONV_CARET_DOWN: - case TEXTCONV_NEXT_LINE: - case TEXTCONV_LINE_START: - pos = end; - break; - - default: - pos = max (BEGV, start); - break; - } - } - } - escape: /* If pos is outside the accessible part of the buffer or if it diff --git a/src/textconv.h b/src/textconv.h index 9bc9e7d9bd1..6abca97bc52 100644 --- a/src/textconv.h +++ b/src/textconv.h @@ -119,7 +119,6 @@ struct textconv_callback_struct }; #define TEXTCONV_SKIP_CONVERSION_REGION (1 << 0) -#define TEXTCONV_SKIP_ACTIVE_REGION (1 << 1) extern int textconv_query (struct frame *, struct textconv_callback_struct *, int); -- cgit v1.3 From 634e3fcc20ea9fa5b1af59286f4b846a351f52c8 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 18 Mar 2023 10:54:26 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsView.java (EmacsView) (prepareForLayout): New function. Call this prior to mapping the view. (onGlobalLayout): New function. Register as global layout listener. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow) (notifyContentRectPosition): New function. Use specified xPosition and yPosition when reporting the offsets of children of the root window. * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (registerWindow): Specify activity launch bounds if necessary. * src/androidterm.c (handle_one_android_event): Send MOVE_FRAME_EVENT where necessary. --- java/org/gnu/emacs/EmacsView.java | 30 ++++- java/org/gnu/emacs/EmacsWindow.java | 145 ++++++++++++--------- .../gnu/emacs/EmacsWindowAttachmentManager.java | 16 ++- src/androidterm.c | 32 +++++ 4 files changed, 160 insertions(+), 63 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 681da98fa16..88d17a255a2 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -28,6 +28,7 @@ import android.view.View; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -51,6 +52,7 @@ import android.util.Log; It is also a ViewGroup, as it also lays out children. */ public final class EmacsView extends ViewGroup + implements ViewTreeObserver.OnGlobalLayoutListener { public static final String TAG = "EmacsView"; @@ -136,6 +138,9 @@ public final class EmacsView extends ViewGroup context = getContext (); tem = context.getSystemService (Context.INPUT_METHOD_SERVICE); imManager = (InputMethodManager) tem; + + /* Add this view as its own global layout listener. */ + getViewTreeObserver ().addOnGlobalLayoutListener (this); } private void @@ -238,6 +243,13 @@ public final class EmacsView extends ViewGroup return canvas; } + public void + prepareForLayout (int wantedWidth, int wantedHeight) + { + measuredWidth = wantedWidth; + measuredHeight = wantedWidth; + } + @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) @@ -562,8 +574,7 @@ public final class EmacsView extends ViewGroup bitmapDirty = true; /* Now expose the view contents again. */ - EmacsNative.sendExpose (this.window.handle, 0, 0, - measuredWidth, measuredHeight); + EmacsNative.sendExpose (this.window.handle, 0, 0, 0, 0); super.onAttachedToWindow (); } @@ -678,4 +689,19 @@ public final class EmacsView extends ViewGroup { return icMode; } + + @Override + public void + onGlobalLayout () + { + int[] locations; + + /* Get the absolute offset of this view and specify its left and + top position in subsequent ConfigureNotify events. */ + + locations = new int[2]; + getLocationInWindow (locations); + window.notifyContentRectPosition (locations[0], + locations[1]); + } }; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index a8d1beedef7..c14bf16b96e 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -133,6 +133,9 @@ public final class EmacsWindow extends EmacsHandleObject creating new bitmaps. */ public volatile int background; + /* The position of this window relative to the root window. */ + public int xPosition, yPosition; + public EmacsWindow (short handle, final EmacsWindow parent, int x, int y, int width, int height, boolean overrideRedirect) @@ -192,18 +195,14 @@ public final class EmacsWindow extends EmacsHandleObject background = pixel; } - public Rect + public synchronized Rect getGeometry () { - synchronized (this) - { - /* Huh, this is it. */ - return rect; - } + return new Rect (rect); } @Override - public void + public synchronized void destroyHandle () throws IllegalStateException { if (parent != null) @@ -258,22 +257,28 @@ public final class EmacsWindow extends EmacsHandleObject return attached; } - public long + public synchronized long viewLayout (int left, int top, int right, int bottom) { int rectWidth, rectHeight; - synchronized (this) - { - rect.left = left; - rect.top = top; - rect.right = right; - rect.bottom = bottom; - } + rect.left = left; + rect.top = top; + rect.right = right; + rect.bottom = bottom; rectWidth = right - left; rectHeight = bottom - top; + /* If parent is null, use xPosition and yPosition instead of the + geometry rectangle positions. */ + + if (parent == null) + { + left = xPosition; + top = yPosition; + } + return EmacsNative.sendConfigureNotify (this.handle, System.currentTimeMillis (), left, top, rectWidth, @@ -283,8 +288,6 @@ public final class EmacsWindow extends EmacsHandleObject public void requestViewLayout () { - /* This is necessary because otherwise subsequent drawing on the - Emacs thread may be lost. */ view.explicitlyDirtyBitmap (); EmacsService.SERVICE.runOnUiThread (new Runnable () { @@ -302,35 +305,29 @@ public final class EmacsWindow extends EmacsHandleObject }); } - public void + public synchronized void resizeWindow (int width, int height) { - synchronized (this) - { - rect.right = rect.left + width; - rect.bottom = rect.top + height; + rect.right = rect.left + width; + rect.bottom = rect.top + height; - requestViewLayout (); - } + requestViewLayout (); } - public void + public synchronized void moveWindow (int x, int y) { int width, height; - synchronized (this) - { - width = rect.width (); - height = rect.height (); + width = rect.width (); + height = rect.height (); - rect.left = x; - rect.top = y; - rect.right = x + width; - rect.bottom = y + height; + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; - requestViewLayout (); - } + requestViewLayout (); } private WindowManager.LayoutParams @@ -366,13 +363,17 @@ public final class EmacsWindow extends EmacsHandleObject return EmacsService.SERVICE; } - public void + public synchronized void mapWindow () { + final int width, height; + if (isMapped) return; isMapped = true; + width = rect.width (); + height = rect.height (); if (parent == null) { @@ -424,6 +425,7 @@ public final class EmacsWindow extends EmacsHandleObject /* Attach the view. */ try { + view.prepareForLayout (width, height); windowManager.addView (view, params); /* Record the window manager being used in the @@ -448,10 +450,14 @@ public final class EmacsWindow extends EmacsHandleObject public void run () { + /* Prior to mapping the view, set its measuredWidth and + measuredHeight to some reasonable value, in order to + avoid excessive bitmap dirtying. */ + + view.prepareForLayout (width, height); view.setVisibility (View.VISIBLE); if (!getDontFocusOnMap ()) - /* Eventually this should check no-focus-on-map. */ view.requestFocus (); } }); @@ -512,12 +518,9 @@ public final class EmacsWindow extends EmacsHandleObject public void clearWindow () { - synchronized (this) - { - EmacsService.SERVICE.fillRectangle (this, scratchGC, - 0, 0, rect.width (), - rect.height ()); - } + EmacsService.SERVICE.fillRectangle (this, scratchGC, + 0, 0, rect.width (), + rect.height ()); } public void @@ -1001,7 +1004,7 @@ public final class EmacsWindow extends EmacsHandleObject return false; } - public void + public synchronized void reparentTo (final EmacsWindow otherWindow, int x, int y) { int width, height; @@ -1017,15 +1020,12 @@ public final class EmacsWindow extends EmacsHandleObject parent = otherWindow; /* Move this window to the new location. */ - synchronized (this) - { - width = rect.width (); - height = rect.height (); - rect.left = x; - rect.top = y; - rect.right = x + width; - rect.bottom = y + height; - } + width = rect.width (); + height = rect.height (); + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; /* Now do the work necessary on the UI thread to reparent the window. */ @@ -1081,7 +1081,7 @@ public final class EmacsWindow extends EmacsHandleObject }); } - public void + public synchronized void raise () { /* This does nothing here. */ @@ -1103,7 +1103,7 @@ public final class EmacsWindow extends EmacsHandleObject }); } - public void + public synchronized void lower () { /* This does nothing here. */ @@ -1125,17 +1125,15 @@ public final class EmacsWindow extends EmacsHandleObject }); } - public int[] + public synchronized int[] getWindowGeometry () { int[] array; - Rect rect; array = new int[4]; - rect = getGeometry (); - array[0] = rect.left; - array[1] = rect.top; + array[0] = parent != null ? rect.left : xPosition; + array[1] = parent != null ? rect.top : yPosition; array[2] = rect.width (); array[3] = rect.height (); @@ -1272,4 +1270,31 @@ public final class EmacsWindow extends EmacsHandleObject } }); } + + public synchronized void + notifyContentRectPosition (int xPosition, int yPosition) + { + Rect geometry; + + /* Ignore these notifications if not a child of the root + window. */ + if (parent != null) + return; + + /* xPosition and yPosition are the position of this window + relative to the screen. Set them and request a ConfigureNotify + event. */ + + if (this.xPosition != xPosition + || this.yPosition != yPosition) + { + this.xPosition = xPosition; + this.yPosition = yPosition; + + EmacsNative.sendConfigureNotify (this.handle, + System.currentTimeMillis (), + xPosition, yPosition, + rect.width (), rect.height ()); + } + } }; diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index c0197ab802c..4fda48616f0 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -22,7 +22,9 @@ package org.gnu.emacs; import java.util.ArrayList; import java.util.List; +import android.app.ActivityOptions; import android.content.Intent; +import android.os.Build; import android.util.Log; /* Code to paper over the differences in lifecycles between @@ -102,6 +104,7 @@ public final class EmacsWindowAttachmentManager registerWindow (EmacsWindow window) { Intent intent; + ActivityOptions options; Log.d (TAG, "registerWindow (maybe): " + window); @@ -128,7 +131,18 @@ public final class EmacsWindowAttachmentManager intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - EmacsService.SERVICE.startActivity (intent); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) + EmacsService.SERVICE.startActivity (intent); + else + { + /* Specify the desired window size. */ + options = ActivityOptions.makeBasic (); + options.setLaunchBounds (window.getGeometry ()); + EmacsService.SERVICE.startActivity (intent, + options.toBundle ()); + } + Log.d (TAG, "registerWindow: startActivity"); } diff --git a/src/androidterm.c b/src/androidterm.c index 1cf9bc4afde..825d6da60fa 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -787,6 +787,33 @@ handle_one_android_event (struct android_display_info *dpyinfo, cancel_mouse_face (f); } + /* Now change the left and top position of this window. */ + + { + int old_left = f->left_pos; + int old_top = f->top_pos; + Lisp_Object frame; + + XSETFRAME (frame, f); + + { + android_window root; + unsigned int dummy_uint; + + android_get_geometry (FRAME_ANDROID_WINDOW (f), + &root, &f->left_pos, &f->top_pos, + &dummy_uint, &dummy_uint, + &dummy_uint); + } + + if (!FRAME_TOOLTIP_P (f) + && (old_left != f->left_pos || old_top != f->top_pos)) + { + inev.ie.kind = MOVE_FRAME_EVENT; + XSETFRAME (inev.ie.frame_or_window, f); + } + } + goto OTHER; case ANDROID_KEY_PRESS: @@ -1100,6 +1127,11 @@ handle_one_android_event (struct android_display_info *dpyinfo, if (!FRAME_GARBAGED_P (f)) { + __android_log_print (ANDROID_LOG_VERBOSE, __func__, + "expose: %d %d %d %d\n", + event->xexpose.x, event->xexpose.y, + event->xexpose.width, + event->xexpose.height); expose_frame (f, event->xexpose.x, event->xexpose.y, event->xexpose.width, event->xexpose.height); show_back_buffer (f); -- cgit v1.3 From 5abc977bbb2d38f3c607f1575e02aa7a6c483db0 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 11 Jun 2023 08:52:10 +0800 Subject: Avoid extraneous calls to the UI thread * java/org/gnu/emacs/EmacsView.java (EmacsView) (showOnScreenKeyboard, hideOnScreenKeyboard) (onCheckIsTextEditor): Make synchronized. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow) (toggleOnScreenKeyboard): Don't post to the main thread. --- java/org/gnu/emacs/EmacsView.java | 6 +++--- java/org/gnu/emacs/EmacsWindow.java | 21 +++++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index d432162132d..7fd672233f2 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -590,7 +590,7 @@ public final class EmacsView extends ViewGroup super.onAttachedToWindow (); } - public void + public synchronized void showOnScreenKeyboard () { /* Specifying no flags at all tells the system the user asked for @@ -599,7 +599,7 @@ public final class EmacsView extends ViewGroup isCurrentlyTextEditor = true; } - public void + public synchronized void hideOnScreenKeyboard () { imManager.hideSoftInputFromWindow (this.getWindowToken (), @@ -686,7 +686,7 @@ public final class EmacsView extends ViewGroup } @Override - public boolean + public synchronized boolean onCheckIsTextEditor () { /* If value is true, then the system will display the on screen diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index c14bf16b96e..f5e40e9a2d9 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -1201,19 +1201,16 @@ public final class EmacsWindow extends EmacsHandleObject } public void - toggleOnScreenKeyboard (final boolean on) + toggleOnScreenKeyboard (boolean on) { - EmacsService.SERVICE.runOnUiThread (new Runnable () { - @Override - public void - run () - { - if (on) - view.showOnScreenKeyboard (); - else - view.hideOnScreenKeyboard (); - } - }); + /* InputMethodManager functions are thread safe. Call + `showOnScreenKeyboard' etc from the Emacs thread in order to + keep the calls in sync with updates to the input context. */ + + if (on) + view.showOnScreenKeyboard (); + else + view.hideOnScreenKeyboard (); } public String -- cgit v1.3 From 3b08bb1318cd0bf6bc1811b520f9c6934b1aa3bd Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 12 Jun 2023 14:19:01 +0800 Subject: Fix deadlocks * java/org/gnu/emacs/EmacsView.java (EmacsView) (showOnScreenKeyboard, hideOnScreenKeyboard): Don't synchronize. * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow) (toggleOnScreenKeyboard): Revert to calling IMM functions from the main thread. * src/android.c (struct android_event_container) (android_pselect_nfds, android_pselect_readfds) (android_pselect_writefds, android_pselect_exceptfds) (android_pselect_timeout): Don't make volatile. (android_wait_event): Run queries if necessary. --- java/org/gnu/emacs/EmacsView.java | 5 +++-- java/org/gnu/emacs/EmacsWindow.java | 25 +++++++++++++++++-------- src/android.c | 30 +++++++++++++++++++----------- 3 files changed, 39 insertions(+), 21 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 7fd672233f2..278c6025902 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -590,16 +590,17 @@ public final class EmacsView extends ViewGroup super.onAttachedToWindow (); } - public synchronized void + public void showOnScreenKeyboard () { /* Specifying no flags at all tells the system the user asked for the input method to be displayed. */ + imManager.showSoftInput (this, 0); isCurrentlyTextEditor = true; } - public synchronized void + public void hideOnScreenKeyboard () { imManager.hideSoftInputFromWindow (this.getWindowToken (), diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index f5e40e9a2d9..68a18ec2aa7 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -1201,16 +1201,25 @@ public final class EmacsWindow extends EmacsHandleObject } public void - toggleOnScreenKeyboard (boolean on) + toggleOnScreenKeyboard (final boolean on) { - /* InputMethodManager functions are thread safe. Call - `showOnScreenKeyboard' etc from the Emacs thread in order to - keep the calls in sync with updates to the input context. */ + /* Even though InputMethodManager functions are thread safe, + `showOnScreenKeyboard' etc must be called from the UI thread in + order to avoid deadlocks if the calls happen in tandem with a + call to a synchronizing function within + `onCreateInputConnection'. */ - if (on) - view.showOnScreenKeyboard (); - else - view.hideOnScreenKeyboard (); + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + if (on) + view.showOnScreenKeyboard (); + else + view.hideOnScreenKeyboard (); + } + }); } public String diff --git a/src/android.c b/src/android.c index 4414d465107..873d821361c 100644 --- a/src/android.c +++ b/src/android.c @@ -272,7 +272,7 @@ void *unused_pointer; struct android_event_container { /* The next and last events in this queue. */ - struct android_event_container *volatile next, *last; + struct android_event_container *next, *last; /* The event itself. */ union android_event event; @@ -301,11 +301,11 @@ struct android_event_queue }; /* Arguments to pselect used by the select thread. */ -static volatile int android_pselect_nfds; -static fd_set *volatile android_pselect_readfds; -static fd_set *volatile android_pselect_writefds; -static fd_set *volatile android_pselect_exceptfds; -static struct timespec *volatile android_pselect_timeout; +static int android_pselect_nfds; +static fd_set *android_pselect_readfds; +static fd_set *android_pselect_writefds; +static fd_set *android_pselect_exceptfds; +static struct timespec *android_pselect_timeout; /* Value of pselect. */ static int android_pselect_rc; @@ -569,12 +569,20 @@ android_pending (void) return i; } +/* Forward declaration. */ + +static void android_check_query (void); + /* Wait for events to become available synchronously. Return once an - event arrives. */ + event arrives. Also, reply to the UI thread whenever it requires a + response. */ void android_wait_event (void) { + /* Run queries from the UI thread to the Emacs thread. */ + android_check_query (); + pthread_mutex_lock (&event_queue.mutex); /* Wait for events to appear if there are none available to @@ -584,6 +592,10 @@ android_wait_event (void) &event_queue.mutex); pthread_mutex_unlock (&event_queue.mutex); + + /* Check for queries again. If a query is sent after the call to + `android_check_query' above, `read_var' will be signaled. */ + android_check_query (); } void @@ -701,10 +713,6 @@ android_write_event (union android_event *event) should answer the query ASAP. */ static bool android_urgent_query; -/* Forward declaration. */ - -static void android_check_query (void); - int android_select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timespec *timeout) -- cgit v1.3 From 377a3ebbb55a9b944551394e00d24c445e3ff4a1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 16 Jun 2023 12:59:44 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): * java/org/gnu/emacs/EmacsApplication.java (findDumpFile): * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu) (addSubmenu, display): * java/org/gnu/emacs/EmacsDocumentsProvider.java (getNotificationUri, queryChildDocuments, deleteDocument): * java/org/gnu/emacs/EmacsDrawRectangle.java (perform): * java/org/gnu/emacs/EmacsFillRectangle.java (perform): * java/org/gnu/emacs/EmacsOpenActivity.java (readEmacsClientLog) (checkReadableOrCopy): * java/org/gnu/emacs/EmacsSdk7FontDriver.java (EmacsSdk7FontDriver): * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView): * java/org/gnu/emacs/EmacsView.java (EmacsView): * java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, onKeyUp): * java/org/gnu/emacs/EmacsWindowAttachmentManager.java (EmacsWindowAttachmentManager): Remove various unused arguments and variables, dead stores, and make minor cleanups and performance improvements. * src/androidmenu.c (FIND_METHOD_STATIC, android_menu_show): Adjust accordingly. --- java/org/gnu/emacs/EmacsActivity.java | 2 +- java/org/gnu/emacs/EmacsApplication.java | 3 ++ java/org/gnu/emacs/EmacsContextMenu.java | 9 +++-- java/org/gnu/emacs/EmacsDocumentsProvider.java | 17 ++++++---- java/org/gnu/emacs/EmacsDrawRectangle.java | 2 -- java/org/gnu/emacs/EmacsFillRectangle.java | 2 +- java/org/gnu/emacs/EmacsOpenActivity.java | 38 +++++++++++++++++----- java/org/gnu/emacs/EmacsSdk7FontDriver.java | 4 +++ java/org/gnu/emacs/EmacsSurfaceView.java | 5 --- java/org/gnu/emacs/EmacsView.java | 4 --- java/org/gnu/emacs/EmacsWindow.java | 24 ++++++-------- .../gnu/emacs/EmacsWindowAttachmentManager.java | 7 +++- src/androidmenu.c | 16 +++------ 13 files changed, 73 insertions(+), 60 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 7ba268ba42d..fa9bff39bb0 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -55,7 +55,7 @@ public class EmacsActivity extends Activity private FrameLayout layout; /* List of activities with focus. */ - public static List focusedActivities; + public static final List focusedActivities; /* The last activity to have been focused. */ public static EmacsActivity lastFocusedActivity; diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java index 10099721744..d8b77acdf9e 100644 --- a/java/org/gnu/emacs/EmacsApplication.java +++ b/java/org/gnu/emacs/EmacsApplication.java @@ -60,6 +60,9 @@ public final class EmacsApplication extends Application } }); + if (allFiles == null) + return; + /* Now try to find the right dump file. */ for (i = 0; i < allFiles.length; ++i) { diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index d69d0263b93..eb83016c849 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -131,20 +131,18 @@ public final class EmacsContextMenu }; public List menuItems; - public String title; private EmacsContextMenu parent; /* Create a context menu with no items inside and the title TITLE, which may be NULL. */ public static EmacsContextMenu - createContextMenu (String title) + createContextMenu () { EmacsContextMenu menu; menu = new EmacsContextMenu (); menu.menuItems = new ArrayList (); - menu.title = title; return menu; } @@ -197,7 +195,7 @@ public final class EmacsContextMenu item name. */ public EmacsContextMenu - addSubmenu (String itemName, String title, String tooltip) + addSubmenu (String itemName, String tooltip) { EmacsContextMenu submenu; Item item; @@ -206,7 +204,7 @@ public final class EmacsContextMenu item.itemID = 0; item.itemName = itemName; item.tooltip = tooltip; - item.subMenu = createContextMenu (title); + item.subMenu = createContextMenu (); item.subMenu.parent = this; menuItems.add (item); @@ -334,6 +332,7 @@ public final class EmacsContextMenu final EmacsHolder rc; rc = new EmacsHolder (); + rc.thing = false; runnable = new Runnable () { @Override diff --git a/java/org/gnu/emacs/EmacsDocumentsProvider.java b/java/org/gnu/emacs/EmacsDocumentsProvider.java index b4ac4624829..96dc2bc6e14 100644 --- a/java/org/gnu/emacs/EmacsDocumentsProvider.java +++ b/java/org/gnu/emacs/EmacsDocumentsProvider.java @@ -131,9 +131,7 @@ public final class EmacsDocumentsProvider extends DocumentsProvider getNotificationUri (File file) { Uri updatedUri; - Context context; - context = getContext (); updatedUri = buildChildDocumentsUri ("org.gnu.emacs", file.getAbsolutePath ()); @@ -294,6 +292,7 @@ public final class EmacsDocumentsProvider extends DocumentsProvider { MatrixCursor result; File directory; + File[] files; Context context; if (projection == null) @@ -305,9 +304,15 @@ public final class EmacsDocumentsProvider extends DocumentsProvider requested. */ directory = new File (parentDocumentId); - /* Now add each child. */ - for (File child : directory.listFiles ()) - queryDocument1 (result, child); + /* Look up each child. */ + files = directory.listFiles (); + + if (files != null) + { + /* Now add each child. */ + for (File child : files) + queryDocument1 (result, child); + } context = getContext (); @@ -406,12 +411,10 @@ public final class EmacsDocumentsProvider extends DocumentsProvider { File file, parent; File[] children; - Context context; /* Java makes recursively deleting a file hard. File name encoding issues also prevent easily calling into C... */ - context = getContext (); file = new File (documentId); parent = file.getParentFile (); diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index 3bd5779c54e..b54b031b56b 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -36,7 +36,6 @@ public final class EmacsDrawRectangle Paint maskPaint, paint; Canvas maskCanvas; Bitmap maskBitmap; - Rect rect; Rect maskRect, dstRect; Canvas canvas; Bitmap clipBitmap; @@ -52,7 +51,6 @@ public final class EmacsDrawRectangle paint = gc.gcPaint; paint.setStyle (Paint.Style.STROKE); - rect = new Rect (x, y, x + width, y + height); if (gc.clip_mask == null) /* Use canvas.drawRect with a RectF. That seems to reliably diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index 4a0478b446f..461fd3c639c 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -61,6 +61,7 @@ public final class EmacsFillRectangle /* 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 = gc.clip_mask.bitmap; dstRect = new Rect (x, y, x + width, y + height); maskRect = new Rect (gc.clip_x_origin, @@ -69,7 +70,6 @@ public final class EmacsFillRectangle + clipBitmap.getWidth ()), (gc.clip_y_origin + clipBitmap.getHeight ())); - clipBitmap = gc.clip_mask.bitmap; if (!maskRect.setIntersect (dstRect, maskRect)) /* There is no intersection between the clip mask and the diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java index 6af2b2d2e94..9411f85d434 100644 --- a/java/org/gnu/emacs/EmacsOpenActivity.java +++ b/java/org/gnu/emacs/EmacsOpenActivity.java @@ -146,7 +146,7 @@ public final class EmacsOpenActivity extends Activity FileReader reader; char[] buffer; int rc; - String what; + StringBuilder builder; /* Because the ProcessBuilder functions necessary to redirect process output are not implemented on Android 7 and earlier, @@ -160,7 +160,8 @@ public final class EmacsOpenActivity extends Activity cache = getCacheDir (); file = new File (cache, "emacsclient.log"); - what = ""; + builder = new StringBuilder (); + reader = null; try { @@ -168,13 +169,25 @@ public final class EmacsOpenActivity extends Activity buffer = new char[2048]; while ((rc = reader.read (buffer, 0, 2048)) != -1) - what += String.valueOf (buffer, 0, 2048); + builder.append (buffer, 0, rc); reader.close (); - return what; + return builder.toString (); } catch (IOException exception) { + /* Close the reader if it's already been opened. */ + + try + { + if (reader != null) + reader.close (); + } + catch (IOException e) + { + /* Not sure what to do here. */ + } + return ("Couldn't read emacsclient.log: " + exception.toString ()); } @@ -248,11 +261,16 @@ public final class EmacsOpenActivity extends Activity /* inFile is now the file being written to. */ inFile = new File (getCacheDir (), inFile.getName ()); buffer = new byte[4098]; - outStream = new FileOutputStream (inFile); - stream = new FileInputStream (fd.getFileDescriptor ()); + + /* Initialize both streams to NULL. */ + outStream = null; + stream = null; try { + outStream = new FileOutputStream (inFile); + stream = new FileInputStream (fd.getFileDescriptor ()); + while ((read = stream.read (buffer)) >= 0) outStream.write (buffer, 0, read); } @@ -263,8 +281,12 @@ public final class EmacsOpenActivity extends Activity Keep in mind that execution is transferred to ``finally'' even if an exception happens inside the while loop above. */ - stream.close (); - outStream.close (); + + if (stream != null) + stream.close (); + + if (outStream != null) + outStream.close (); } return inFile.getCanonicalPath (); diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java index 6df102f18a2..9122b46458a 100644 --- a/java/org/gnu/emacs/EmacsSdk7FontDriver.java +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -252,6 +252,10 @@ public class EmacsSdk7FontDriver extends EmacsFontDriver systemFontsDirectory = new File ("/system/fonts"); fontFamilyList = systemFontsDirectory.list (); + + /* If that returned null, replace it with an empty array. */ + fontFamilyList = new String[0]; + typefaceList = new Sdk7Typeface[fontFamilyList.length + 3]; /* It would be nice to avoid opening each and every font upon diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 738b1a99eef..5b3e05eb9f4 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -39,10 +39,6 @@ public final class EmacsSurfaceView extends View { private static final String TAG = "EmacsSurfaceView"; - /* The EmacsView representing the window that this surface is - displaying. */ - private EmacsView view; - /* The complete buffer contents at the time of the last draw. */ private Bitmap frontBuffer; @@ -71,7 +67,6 @@ public final class EmacsSurfaceView extends View { super (view.getContext ()); - this.view = view; this.bitmap = new WeakReference (null); } diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index aba1184b0c2..0cabefdf385 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -68,9 +68,6 @@ public final class EmacsView extends ViewGroup /* The damage region. */ public Region damageRegion; - /* The paint. */ - public Paint paint; - /* The associated surface view. */ private EmacsSurfaceView surfaceView; @@ -128,7 +125,6 @@ public final class EmacsView extends ViewGroup this.window = window; this.damageRegion = new Region (); - this.paint = new Paint (); setFocusable (true); setFocusableInTouchMode (true); diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 68a18ec2aa7..739a1f43b7d 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -103,11 +103,10 @@ public final class EmacsWindow extends EmacsHandleObject public int lastButtonState, lastModifiers; /* Whether or not the window is mapped. */ - private boolean isMapped; + private volatile boolean isMapped; - /* Whether or not to ask for focus upon being mapped, and whether or - not the window should be focusable. */ - private boolean dontFocusOnMap, dontAcceptFocus; + /* Whether or not to ask for focus upon being mapped. */ + private boolean dontFocusOnMap; /* Whether or not the window is override-redirect. An override-redirect window always has its own system window. */ @@ -464,7 +463,7 @@ public final class EmacsWindow extends EmacsHandleObject } } - public void + public synchronized void unmapWindow () { if (!isMapped) @@ -618,7 +617,7 @@ public final class EmacsWindow extends EmacsHandleObject onKeyUp (int keyCode, KeyEvent event) { int state, state_1; - long time, serial; + long time; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -645,12 +644,11 @@ public final class EmacsWindow extends EmacsHandleObject state_1 = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); - serial - = EmacsNative.sendKeyRelease (this.handle, - event.getEventTime (), - state, keyCode, - getEventUnicodeChar (event, - state_1)); + EmacsNative.sendKeyRelease (this.handle, + event.getEventTime (), + state, keyCode, + getEventUnicodeChar (event, + state_1)); lastModifiers = state; if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) @@ -1155,8 +1153,6 @@ public final class EmacsWindow extends EmacsHandleObject public synchronized void setDontAcceptFocus (final boolean dontAcceptFocus) { - this.dontAcceptFocus = dontAcceptFocus; - /* Update the view's focus state. */ EmacsService.SERVICE.runOnUiThread (new Runnable () { @Override diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 4fda48616f0..bc96de7fe1a 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -53,9 +53,11 @@ import android.util.Log; public final class EmacsWindowAttachmentManager { - public static EmacsWindowAttachmentManager MANAGER; private final static String TAG = "EmacsWindowAttachmentManager"; + /* The single window attachment manager ``object''. */ + public static final EmacsWindowAttachmentManager MANAGER; + static { MANAGER = new EmacsWindowAttachmentManager (); @@ -69,7 +71,10 @@ public final class EmacsWindowAttachmentManager public void destroy (); }; + /* List of currently attached window consumers. */ public List consumers; + + /* List of currently attached windows. */ public List windows; public diff --git a/src/androidmenu.c b/src/androidmenu.c index f74e7ca6d99..75710486c75 100644 --- a/src/androidmenu.c +++ b/src/androidmenu.c @@ -101,13 +101,12 @@ android_init_emacs_context_menu (void) eassert (menu_class.c_name); FIND_METHOD_STATIC (create_context_menu, "createContextMenu", - "(Ljava/lang/String;)" - "Lorg/gnu/emacs/EmacsContextMenu;"); + "()Lorg/gnu/emacs/EmacsContextMenu;"); FIND_METHOD (add_item, "addItem", "(ILjava/lang/String;ZZZ" "Ljava/lang/String;Z)V"); FIND_METHOD (add_submenu, "addSubmenu", "(Ljava/lang/String;" - "Ljava/lang/String;Ljava/lang/String;)" + "Ljava/lang/String;)" "Lorg/gnu/emacs/EmacsContextMenu;"); FIND_METHOD (add_pane, "addPane", "(Ljava/lang/String;)V"); FIND_METHOD (parent, "parent", "()Lorg/gnu/emacs/EmacsContextMenu;"); @@ -271,18 +270,11 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, android_push_local_frame (); /* Push the first local frame for the context menu. */ - title_string = (!NILP (title) - ? (jobject) android_build_string (title) - : NULL); method = menu_class.create_context_menu; current_context_menu = context_menu = (*android_java_env)->CallStaticObjectMethod (android_java_env, menu_class.class, - method, - title_string); - - if (title_string) - ANDROID_DELETE_LOCAL_REF (title_string); + method); /* Push the second local frame for temporaries. */ count1 = SPECPDL_INDEX (); @@ -391,7 +383,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, = (*android_java_env)->CallObjectMethod (android_java_env, current_context_menu, menu_class.add_submenu, - title_string, NULL, + title_string, help_string); android_exception_check (); -- cgit v1.3 From 97f926b82d5674f90a16327b0ad4d6b226fcacb4 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 9 Jul 2023 12:50:15 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsService.java (onStartCommand): Fix typo in notification message. * java/org/gnu/emacs/EmacsWindow.java (onFocusChanged): Reset the recorded modifier state upon a change to the window focus. --- java/org/gnu/emacs/EmacsService.java | 2 +- java/org/gnu/emacs/EmacsWindow.java | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index a7e83e276f2..0543c3a1bdd 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -147,7 +147,7 @@ public final class EmacsService extends Service manager = (NotificationManager) tem; infoBlurb = ("This notification is displayed to keep Emacs" + " running while it is in the background. You" - + " may disable if you want;" + + " may disable it if you want;" + " see (emacs)Android Environment."); channel = new NotificationChannel ("emacs", "Emacs persistent notification", diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 739a1f43b7d..2d8a8627468 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -99,7 +99,8 @@ public final class EmacsWindow extends EmacsHandleObject private EmacsGC scratchGC; /* The button state and keyboard modifier mask at the time of the - last button press or release event. */ + last button press or release event. The modifier mask is reset + upon each window focus change. */ public int lastButtonState, lastModifiers; /* Whether or not the window is mapped. */ @@ -670,6 +671,12 @@ public final class EmacsWindow extends EmacsHandleObject onFocusChanged (boolean gainFocus) { EmacsActivity.invalidateFocus (); + + /* If focus has been lost, reset the keyboard modifier state, as + subsequent changes will not be recorded. */ + + if (!gainFocus) + lastModifiers = 0; } /* Notice that the activity has been detached or destroyed. -- cgit v1.3 From ed5ade097e5b675167f9c7e4916a4beec62d118a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 9 Jul 2023 13:13:53 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsWindow.java (eventModifiers) (motionEventModifiers): New functions. (onKeyDown, onKeyUp, onFocusChanged, onSomeKindOfMotionEvent): Don't record the previous modifier mask; instead, always use the modifier state specified in the event. * src/androidterm.c (handle_one_android_event): Don't dispatch button release events when a popup is active. --- java/org/gnu/emacs/EmacsWindow.java | 99 +++++++++++++++++++++---------------- src/androidterm.c | 11 +++-- 2 files changed, 63 insertions(+), 47 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 2d8a8627468..15d5fe8a175 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -99,9 +99,8 @@ public final class EmacsWindow extends EmacsHandleObject private EmacsGC scratchGC; /* The button state and keyboard modifier mask at the time of the - last button press or release event. The modifier mask is reset - upon each window focus change. */ - public int lastButtonState, lastModifiers; + last button press or release event. */ + public int lastButtonState; /* Whether or not the window is mapped. */ private volatile boolean isMapped; @@ -562,15 +561,16 @@ public final class EmacsWindow extends EmacsHandleObject eventStrings.put (serial, string); } - /* event.getCharacters is used because older input methods still - require it. */ - @SuppressWarnings ("deprecation") - public void - onKeyDown (int keyCode, KeyEvent event) + + + /* Return the modifier mask associated with the specified keyboard + input EVENT. Replace bits corresponding to Left or Right keys + with their corresponding general modifier bits. */ + + private int + eventModifiers (KeyEvent event) { - int state, state_1; - long serial; - String characters; + int state; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) state = event.getModifiers (); @@ -592,6 +592,46 @@ public final class EmacsWindow extends EmacsHandleObject state |= KeyEvent.META_CTRL_MASK; } + return state; + } + + /* Return the modifier mask associated with the specified motion + EVENT. Replace bits corresponding to Left or Right keys with + their corresponding general modifier bits. */ + + private int + motionEventModifiers (MotionEvent event) + { + int state; + + state = event.getMetaState (); + + /* Normalize the state by setting the generic modifier bit if + either a left or right modifier is pressed. */ + + if ((state & KeyEvent.META_ALT_LEFT_ON) != 0 + || (state & KeyEvent.META_ALT_RIGHT_ON) != 0) + state |= KeyEvent.META_ALT_MASK; + + if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0 + || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0) + state |= KeyEvent.META_CTRL_MASK; + + return state; + } + + /* event.getCharacters is used because older input methods still + require it. */ + @SuppressWarnings ("deprecation") + public void + onKeyDown (int keyCode, KeyEvent event) + { + int state, state_1; + long serial; + String characters; + + state = eventModifiers (event); + /* Ignore meta-state understood by Emacs for now, or Ctrl+C will not be recognized as an ASCII key press event. */ state_1 @@ -605,7 +645,6 @@ public final class EmacsWindow extends EmacsHandleObject state, keyCode, getEventUnicodeChar (event, state_1)); - lastModifiers = state; characters = event.getCharacters (); @@ -620,25 +659,8 @@ public final class EmacsWindow extends EmacsHandleObject int state, state_1; long time; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) - state = event.getModifiers (); - else - { - /* Replace this with getMetaState and manual - normalization. */ - state = event.getMetaState (); - - /* Normalize the state by setting the generic modifier bit if - either a left or right modifier is pressed. */ - - if ((state & KeyEvent.META_ALT_LEFT_ON) != 0 - || (state & KeyEvent.META_ALT_RIGHT_ON) != 0) - state |= KeyEvent.META_ALT_MASK; - - if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0 - || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0) - state |= KeyEvent.META_CTRL_MASK; - } + /* Compute the event's modifier mask. */ + state = eventModifiers (event); /* Ignore meta-state understood by Emacs for now, or Ctrl+C will not be recognized as an ASCII key press event. */ @@ -650,7 +672,6 @@ public final class EmacsWindow extends EmacsHandleObject state, keyCode, getEventUnicodeChar (event, state_1)); - lastModifiers = state; if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { @@ -671,12 +692,6 @@ public final class EmacsWindow extends EmacsHandleObject onFocusChanged (boolean gainFocus) { EmacsActivity.invalidateFocus (); - - /* If focus has been lost, reset the keyboard modifier state, as - subsequent changes will not be recorded. */ - - if (!gainFocus) - lastModifiers = 0; } /* Notice that the activity has been detached or destroyed. @@ -940,7 +955,7 @@ public final class EmacsWindow extends EmacsHandleObject EmacsNative.sendButtonPress (this.handle, (int) event.getX (), (int) event.getY (), event.getEventTime (), - lastModifiers, + motionEventModifiers (event), whatButtonWasIt (event, true)); if (Build.VERSION.SDK_INT @@ -955,7 +970,7 @@ public final class EmacsWindow extends EmacsHandleObject EmacsNative.sendButtonRelease (this.handle, (int) event.getX (), (int) event.getY (), event.getEventTime (), - lastModifiers, + motionEventModifiers (event), whatButtonWasIt (event, false)); if (Build.VERSION.SDK_INT @@ -988,7 +1003,7 @@ public final class EmacsWindow extends EmacsHandleObject EmacsNative.sendButtonRelease (this.handle, (int) event.getX (), (int) event.getY (), event.getEventTime (), - lastModifiers, + motionEventModifiers (event), whatButtonWasIt (event, false)); lastButtonState = event.getButtonState (); } @@ -1000,7 +1015,7 @@ public final class EmacsWindow extends EmacsHandleObject EmacsNative.sendWheel (this.handle, (int) event.getX (), (int) event.getY (), event.getEventTime (), - lastModifiers, + motionEventModifiers (event), event.getAxisValue (MotionEvent.AXIS_HSCROLL), event.getAxisValue (MotionEvent.AXIS_VSCROLL)); return true; diff --git a/src/androidterm.c b/src/androidterm.c index 466a99a1e28..135eda45245 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -1315,12 +1315,13 @@ handle_one_android_event (struct android_display_info *dpyinfo, } if (!(tab_bar_p && NILP (tab_bar_arg)) && !tool_bar_p) - { - android_construct_mouse_click (&inev.ie, &event->xbutton, f); + if (! popup_activated ()) + { + android_construct_mouse_click (&inev.ie, &event->xbutton, f); - if (!NILP (tab_bar_arg)) - inev.ie.arg = tab_bar_arg; - } + if (!NILP (tab_bar_arg)) + inev.ie.arg = tab_bar_arg; + } } if (event->type == ANDROID_BUTTON_PRESS) -- cgit v1.3 From 24ae08c11342d93ac68b284302fcf5929c19ffb0 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 11 Jul 2023 10:22:03 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsView.java (onGenericMotionEvent): Call onGenericMotionEvent. * java/org/gnu/emacs/EmacsWindow.java (Coordinate): New fields `button' and `id'. (): Add new arguments to the construtor. (whatButtonWasIt): Return 0 if the button state has not changed. (buttonForEvent): New function. (figureChange): Return the Coordinate object associated to EVENT. Determine whether or not EVENT was accompanied by a change to the button state, and ascertain which button that was. (motionEvent): New function. (onGenericMotionEvent, onTouchEvent): Factor out touch and mouse event delivery to motionEvent. --- java/org/gnu/emacs/EmacsView.java | 2 +- java/org/gnu/emacs/EmacsWindow.java | 435 +++++++++++++++++++++--------------- 2 files changed, 258 insertions(+), 179 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index bb4dace655a..12d8ff4da56 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -475,7 +475,7 @@ public final class EmacsView extends ViewGroup public boolean onGenericMotionEvent (MotionEvent motion) { - return window.onSomeKindOfMotionEvent (motion); + return window.onGenericMotionEvent (motion); } @Override diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 15d5fe8a175..6816f3e8e71 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -66,11 +66,20 @@ public final class EmacsWindow extends EmacsHandleObject /* Integral coordinate. */ int x, y; + /* Button associated with the coordinate, or 0 if it is a touch + event. */ + int button; + + /* Pointer ID associated with the coordinate. */ + int id; + public - Coordinate (int x, int y) + Coordinate (int x, int y, int button, int id) { this.x = x; this.y = y; + this.button = button; + this.id = id; } }; @@ -595,31 +604,6 @@ public final class EmacsWindow extends EmacsHandleObject return state; } - /* Return the modifier mask associated with the specified motion - EVENT. Replace bits corresponding to Left or Right keys with - their corresponding general modifier bits. */ - - private int - motionEventModifiers (MotionEvent event) - { - int state; - - state = event.getMetaState (); - - /* Normalize the state by setting the generic modifier bit if - either a left or right modifier is pressed. */ - - if ((state & KeyEvent.META_ALT_LEFT_ON) != 0 - || (state & KeyEvent.META_ALT_RIGHT_ON) != 0) - state |= KeyEvent.META_ALT_MASK; - - if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0 - || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0) - state |= KeyEvent.META_CTRL_MASK; - - return state; - } - /* event.getCharacters is used because older input methods still require it. */ @SuppressWarnings ("deprecation") @@ -710,6 +694,69 @@ public final class EmacsWindow extends EmacsHandleObject EmacsNative.sendWindowAction (this.handle, 0); } + + + /* Mouse and touch event handling. + + Android does not conceptually distinguish between mouse events + (those coming from a device whose movement affects the on-screen + pointer image) and touch screen events. When a touch, click, or + pointer motion takes place, several kinds of event can be sent: + + ACTION_DOWN or ACTION_POINTER_DOWN is sent with a new coordinate + and an associated ``pointer ID'' identifying the event when a + click or touch takes place. Emacs is responsible for recording + both the position of this click for the purpose of determining + future changes to the position of that touch. + + ACTION_UP or ACTION_POINTER_UP is sent with a pointer ID when the + click associated with a previous ACTION_DOWN event is released. + + ACTION_CANCEL (or ACTION_POINTER_UP with FLAG_CANCELED) is sent + if a similar situation transpires: the window system has chosen + to grab of the click, and future movement will no longer be + reported to Emacs. + + ACTION_MOVE is sent if a coordinate tied to a click that has not + been released changes. Emacs processes this event by comparing + each of the coordinates within the event with its recollection of + those contained within prior ACTION_DOWN and ACTION_MOVE events; + the pointer ID of the difference is then reported within a touch + or pointer motion event along with its new position. + + The events described above are all sent for both touch and mouse + click events. Determining whether an ACTION_DOWN event is + associated with a button event is performed by inspecting the + mouse button state associated with that event. If it contains + any mouse buttons that were not contained in the button state at + the time of the last ACTION_DOWN or ACTION_UP event, the + coordinate contained within is assumed to be a mouse click, + leading to it and associated motion or ACTION_UP events being + reported as mouse button or motion events. Otherwise, those + events are reported as touch screen events, with the touch ID set + to the pointer ID. + + In addition to the events illustrated above, Android also sends + several other types of event upon select types of activity from a + mouse device: + + ACTION_HOVER_MOVE is sent with the coordinate of the mouse + pointer if it moves above a frame prior to any click taking + place. Emacs sends a mouse motion event containing the + coordinate. + + ACTION_HOVER_ENTER and ACTION_HOVER_LEAVE are respectively sent + when the mouse pointer enters and leaves a frame. + + On Android 6.0 and later, ACTION_BUTTON_PRESS is sent with the + coordinate of the mouse pointer if a mouse click occurs, + alongside a ACTION_DOWN event. ACTION_BUTTON_RELEASE is sent + with the same information upon a mouse click being released, also + accompanying an ACTION_UP event. + + However, both types of button events are implemented in a buggy + fashion and cannot be used to report button events. */ + /* Look through the button state to determine what button EVENT was generated from. DOWN is true if EVENT is a button press event, false otherwise. Value is the X number of the button. */ @@ -719,16 +766,20 @@ public final class EmacsWindow extends EmacsHandleObject { int eventState, notIn; - if (Build.VERSION.SDK_INT - < Build.VERSION_CODES.ICE_CREAM_SANDWICH) - /* Earlier versions of Android only support one mouse - button. */ - return 1; - + /* Obtain the new button state. */ eventState = event.getButtonState (); + + /* Compute which button is now set or no longer set. */ + notIn = (down ? eventState & ~lastButtonState : lastButtonState & ~eventState); + if ((notIn & (MotionEvent.BUTTON_PRIMARY + | MotionEvent.BUTTON_SECONDARY + | MotionEvent.BUTTON_TERTIARY)) == 0) + /* No buttons have been pressed, so this is a touch event. */ + return 0; + if ((notIn & MotionEvent.BUTTON_PRIMARY) != 0) return 1; @@ -742,53 +793,55 @@ public final class EmacsWindow extends EmacsHandleObject return 4; } - /* Return the ID of the pointer which changed in EVENT. Value is -1 - if it could not be determined, else the pointer that changed, or - -2 if -1 would have been returned, but there is also a pointer - that is a mouse. */ + /* Return the mouse button associated with the specified ACTION_DOWN + or ACTION_POINTER_DOWN EVENT. + + Value is 0 if no mouse button was pressed, or the X number of + that mouse button. */ private int + buttonForEvent (MotionEvent event) + { + /* ICS and earlier don't support true mouse button events, so + treat all down events as touch screen events. */ + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + return 0; + + return whatButtonWasIt (event, true); + } + + /* Return the coordinate object associated with the specified + EVENT, or null if it is not known. */ + + private Coordinate figureChange (MotionEvent event) { - int pointerID, i, truncatedX, truncatedY, pointerIndex; + int i, truncatedX, truncatedY, pointerIndex, pointerID, count; Coordinate coordinate; - boolean mouseFlag; - /* pointerID is always initialized but the Java compiler is too - dumb to know that. */ - pointerID = -1; - mouseFlag = false; + /* Initialize this variable now. */ + coordinate = null; switch (event.getActionMasked ()) { case MotionEvent.ACTION_DOWN: /* Primary pointer pressed with index 0. */ - /* Detect mice. If this is a mouse event, give it to - onSomeKindOfMotionEvent. */ - if ((Build.VERSION.SDK_INT - >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - && event.getToolType (0) == MotionEvent.TOOL_TYPE_MOUSE) - return -2; - pointerID = event.getPointerId (0); - pointerMap.put (pointerID, - new Coordinate ((int) event.getX (0), - (int) event.getY (0))); + coordinate = new Coordinate ((int) event.getX (0), + (int) event.getY (0), + buttonForEvent (event), + pointerID); + pointerMap.put (pointerID, coordinate); break; case MotionEvent.ACTION_UP: - - /* Detect mice. If this is a mouse event, give it to - onSomeKindOfMotionEvent. */ - if ((Build.VERSION.SDK_INT - >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - && event.getToolType (0) == MotionEvent.TOOL_TYPE_MOUSE) - return -2; - + case MotionEvent.ACTION_CANCEL: /* Primary pointer released with index 0. */ pointerID = event.getPointerId (0); - pointerMap.remove (pointerID); + coordinate = pointerMap.remove (pointerID); break; case MotionEvent.ACTION_POINTER_DOWN: @@ -796,22 +849,26 @@ public final class EmacsWindow extends EmacsHandleObject it in the map. */ pointerIndex = event.getActionIndex (); pointerID = event.getPointerId (pointerIndex); - pointerMap.put (pointerID, - new Coordinate ((int) event.getX (pointerIndex), - (int) event.getY (pointerIndex))); + coordinate = new Coordinate ((int) event.getX (0), + (int) event.getY (0), + buttonForEvent (event), + pointerID); + pointerMap.put (pointerID, coordinate); break; case MotionEvent.ACTION_POINTER_UP: /* Pointer removed. Remove it from the map. */ pointerIndex = event.getActionIndex (); pointerID = event.getPointerId (pointerIndex); - pointerMap.remove (pointerID); + coordinate = pointerMap.remove (pointerID); break; default: /* Loop through each pointer in the event. */ - for (i = 0; i < event.getPointerCount (); ++i) + + count = event.getPointerCount (); + for (i = 0; i < count; ++i) { pointerID = event.getPointerId (i); @@ -835,73 +892,152 @@ public final class EmacsWindow extends EmacsHandleObject break; } } - - /* See if this is a mouse. If so, set the mouseFlag. */ - if ((Build.VERSION.SDK_INT - >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - && event.getToolType (i) == MotionEvent.TOOL_TYPE_MOUSE) - mouseFlag = true; } - /* Set the pointer ID to -1 if the loop failed to find any - changed pointer. If a mouse pointer was found, set it to - -2. */ - if (i == event.getPointerCount ()) - pointerID = (mouseFlag ? -2 : -1); + /* Set coordinate to NULL if the loop failed to find any + matching pointer. */ + + if (i == count) + coordinate = null; } /* Return the pointer ID. */ - return pointerID; + return coordinate; } - public boolean - onTouchEvent (MotionEvent event) + /* Return the modifier mask associated with the specified motion + EVENT. Replace bits corresponding to Left or Right keys with + their corresponding general modifier bits. */ + + private int + motionEventModifiers (MotionEvent event) + { + int state; + + state = event.getMetaState (); + + /* Normalize the state by setting the generic modifier bit if + either a left or right modifier is pressed. */ + + if ((state & KeyEvent.META_ALT_LEFT_ON) != 0 + || (state & KeyEvent.META_ALT_RIGHT_ON) != 0) + state |= KeyEvent.META_ALT_MASK; + + if ((state & KeyEvent.META_CTRL_LEFT_ON) != 0 + || (state & KeyEvent.META_CTRL_RIGHT_ON) != 0) + state |= KeyEvent.META_CTRL_MASK; + + return state; + } + + /* Process a single ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_UP, + ACTION_POINTER_UP, ACTION_CANCEL, or ACTION_MOVE event. + + Ascertain which coordinate changed and send an appropriate mouse + or touch screen event. */ + + private void + motionEvent (MotionEvent event) { - int pointerID, index; + Coordinate coordinate; + int modifiers; + long time; - /* Extract the ``touch ID'' (or in Android, the ``pointer - ID''.) */ - pointerID = figureChange (event); + /* Find data associated with this event's pointer. Namely, its + current location, whether or not a change has taken place, and + whether or not it is a button event. */ - if (pointerID < 0) + coordinate = figureChange (event); + + if (coordinate == null) + return; + + time = event.getEventTime (); + + if (coordinate.button != 0) { - /* If this is a mouse event, give it to - onSomeKindOfMotionEvent. */ - if (pointerID == -2) - return onSomeKindOfMotionEvent (event); + /* This event is tied to a mouse click, so report mouse motion + and button events. */ + + modifiers = motionEventModifiers (event); - return false; + switch (event.getAction ()) + { + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + EmacsNative.sendButtonPress (this.handle, coordinate.x, + coordinate.y, time, modifiers, + coordinate.button); + break; + + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + EmacsNative.sendButtonRelease (this.handle, coordinate.x, + coordinate.y, time, modifiers, + coordinate.button); + break; + + case MotionEvent.ACTION_MOVE: + EmacsNative.sendMotionNotify (this.handle, coordinate.x, + coordinate.y, time); + break; + } } + else + { + /* This event is a touch event, and the touch ID is the + pointer ID. */ - /* Find the pointer index corresponding to the event. */ - index = event.findPointerIndex (pointerID); + switch (event.getActionMasked ()) + { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + /* Touch down event. */ + EmacsNative.sendTouchDown (this.handle, coordinate.x, + coordinate.y, time, + coordinate.id); + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + /* Touch up event. Android documentation says ACTION_CANCEL + should be treated as more or less equivalent to ACTION_UP, + so that is what is done here. */ + EmacsNative.sendTouchUp (this.handle, coordinate.x, + coordinate.y, time, coordinate.id); + break; + + case MotionEvent.ACTION_MOVE: + /* Pointer motion event. */ + EmacsNative.sendTouchMove (this.handle, coordinate.x, + coordinate.y, time, coordinate.id); + break; + } + } + + if (Build.VERSION.SDK_INT + < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + return; + + /* Now update the button state. */ + lastButtonState = event.getButtonState (); + return; + } + public boolean + onTouchEvent (MotionEvent event) + { switch (event.getActionMasked ()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: - /* Touch down event. */ - EmacsNative.sendTouchDown (this.handle, (int) event.getX (index), - (int) event.getY (index), - event.getEventTime (), pointerID); - return true; - case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_CANCEL: - /* Touch up event. Android documentation says ACTION_CANCEL - should be treated as more or less equivalent to ACTION_UP, - so that is what is done here. */ - EmacsNative.sendTouchUp (this.handle, (int) event.getX (index), - (int) event.getY (index), - event.getEventTime (), pointerID); - return true; - case MotionEvent.ACTION_MOVE: - /* Pointer motion event. */ - EmacsNative.sendTouchMove (this.handle, (int) event.getX (index), - (int) event.getY (index), - event.getEventTime (), pointerID); + motionEvent (event); return true; } @@ -909,18 +1045,8 @@ public final class EmacsWindow extends EmacsHandleObject } public boolean - onSomeKindOfMotionEvent (MotionEvent event) + onGenericMotionEvent (MotionEvent event) { - /* isFromSource is not available until API level 18. */ - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) - { - if (!event.isFromSource (InputDevice.SOURCE_CLASS_POINTER)) - return false; - } - else if (event.getSource () != InputDevice.SOURCE_CLASS_POINTER) - return false; - switch (event.getAction ()) { case MotionEvent.ACTION_HOVER_ENTER: @@ -929,7 +1055,6 @@ public final class EmacsWindow extends EmacsHandleObject event.getEventTime ()); return true; - case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_HOVER_MOVE: EmacsNative.sendMotionNotify (this.handle, (int) event.getX (), (int) event.getY (), @@ -950,64 +1075,16 @@ public final class EmacsWindow extends EmacsHandleObject return true; - case MotionEvent.ACTION_BUTTON_PRESS: - /* Find the button which was pressed. */ - EmacsNative.sendButtonPress (this.handle, (int) event.getX (), - (int) event.getY (), - event.getEventTime (), - motionEventModifiers (event), - whatButtonWasIt (event, true)); - - if (Build.VERSION.SDK_INT - < Build.VERSION_CODES.ICE_CREAM_SANDWICH) - return true; - - lastButtonState = event.getButtonState (); - return true; - - case MotionEvent.ACTION_BUTTON_RELEASE: - /* Find the button which was released. */ - EmacsNative.sendButtonRelease (this.handle, (int) event.getX (), - (int) event.getY (), - event.getEventTime (), - motionEventModifiers (event), - whatButtonWasIt (event, false)); - - if (Build.VERSION.SDK_INT - < Build.VERSION_CODES.ICE_CREAM_SANDWICH) - return true; - - lastButtonState = event.getButtonState (); - return true; - case MotionEvent.ACTION_DOWN: - /* Emacs must return true even though touch events are not - handled here, because the value of this function is used by - the system to decide whether or not Emacs gets ACTION_MOVE - events. */ - return true; - + case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_UP: - /* However, if ACTION_UP reports a different button state from - the last known state, look up which button was released and - send a ButtonRelease event; this is to work around a bug in - the framework where real ACTION_BUTTON_RELEASE events are - not delivered. */ - - if (Build.VERSION.SDK_INT - < Build.VERSION_CODES.ICE_CREAM_SANDWICH) - return true; - - if (event.getButtonState () == 0 && lastButtonState != 0) - { - EmacsNative.sendButtonRelease (this.handle, (int) event.getX (), - (int) event.getY (), - event.getEventTime (), - motionEventModifiers (event), - whatButtonWasIt (event, false)); - lastButtonState = event.getButtonState (); - } - + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_MOVE: + /* MotionEvents may either be sent to onGenericMotionEvent or + onTouchEvent depending on if Android thinks it is a mouse + event or not, but we detect them ourselves. */ + motionEvent (event); return true; case MotionEvent.ACTION_SCROLL: @@ -1024,6 +1101,8 @@ public final class EmacsWindow extends EmacsHandleObject return false; } + + public synchronized void reparentTo (final EmacsWindow otherWindow, int x, int y) { -- cgit v1.3 From c8c2bec5f8e4964f23345e1150a7ab85003e688b Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 12 Jul 2023 09:45:58 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsWindow.java (whatButtonWasIt): Handle back and forward buttons along with styluses. * src/doc.c (close_file_unwind_android_fd): New function. (get_doc_string, Fsnarf_documentation): Don't create a temporary fd if it can be avoided. --- java/org/gnu/emacs/EmacsWindow.java | 53 +++++++++++++++++++------ src/doc.c | 79 ++++++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 22 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 6816f3e8e71..5e45275631b 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -700,29 +700,36 @@ public final class EmacsWindow extends EmacsHandleObject Android does not conceptually distinguish between mouse events (those coming from a device whose movement affects the on-screen - pointer image) and touch screen events. When a touch, click, or - pointer motion takes place, several kinds of event can be sent: + pointer image) and touch screen events. Each click or touch + starts a single pointer gesture sequence, and subsequent motion + of the device will result in updates being reported relative to + that sequence until the mouse button or touch is released. + + When a touch, click, or pointer motion takes place, several kinds + of event can be sent: ACTION_DOWN or ACTION_POINTER_DOWN is sent with a new coordinate - and an associated ``pointer ID'' identifying the event when a - click or touch takes place. Emacs is responsible for recording - both the position of this click for the purpose of determining - future changes to the position of that touch. + and an associated ``pointer ID'' identifying the event and its + gesture sequence when a click or touch takes place. Emacs is + responsible for recording both the position and pointer ID of + this click for the purpose of determining future changes to its + position. ACTION_UP or ACTION_POINTER_UP is sent with a pointer ID when the click associated with a previous ACTION_DOWN event is released. ACTION_CANCEL (or ACTION_POINTER_UP with FLAG_CANCELED) is sent if a similar situation transpires: the window system has chosen - to grab of the click, and future movement will no longer be - reported to Emacs. + to grab the click, and future changes to its position will no + longer be reported to Emacs. ACTION_MOVE is sent if a coordinate tied to a click that has not been released changes. Emacs processes this event by comparing each of the coordinates within the event with its recollection of those contained within prior ACTION_DOWN and ACTION_MOVE events; - the pointer ID of the difference is then reported within a touch - or pointer motion event along with its new position. + the pointer ID of the differing coordinate is then reported + within a touch or pointer motion event along with its new + position. The events described above are all sent for both touch and mouse click events. Determining whether an ACTION_DOWN event is @@ -746,7 +753,12 @@ public final class EmacsWindow extends EmacsHandleObject coordinate. ACTION_HOVER_ENTER and ACTION_HOVER_LEAVE are respectively sent - when the mouse pointer enters and leaves a frame. + when the mouse pointer enters and leaves a frame. Moreover, + ACTION_HOVER_LEAVE events are sent immediately before an + ACTION_DOWN event associated with a mouse click. These + extraneous events are distinct in that their button states always + contain an additional button compared to the button state + recorded at the time of the last ACTION_UP event. On Android 6.0 and later, ACTION_BUTTON_PRESS is sent with the coordinate of the mouse pointer if a mouse click occurs, @@ -789,8 +801,25 @@ public final class EmacsWindow extends EmacsHandleObject if ((notIn & MotionEvent.BUTTON_TERTIARY) != 0) return 2; + /* Buttons 4, 5, 6 and 7 are actually scroll wheels under X. + Thus, report additional buttons starting at 8. */ + + if ((notIn & MotionEvent.BUTTON_BACK) != 0) + return 8; + + if ((notIn & MotionEvent.BUTTON_FORWARD) != 0) + return 9; + + /* Report stylus events as touch screen events. */ + + if ((notIn & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0) + return 0; + + if ((notIn & MotionEvent.BUTTON_STYLUS_SECONDARY) != 0) + return 0; + /* Not a real value. */ - return 4; + return 11; } /* Return the mouse button associated with the specified ACTION_DOWN diff --git a/src/doc.c b/src/doc.c index 174341523d7..56991ffcdfe 100644 --- a/src/doc.c +++ b/src/doc.c @@ -37,6 +37,41 @@ along with GNU Emacs. If not, see . */ #include "intervals.h" #include "keymap.h" + + +#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY \ + || (__ANDROID_API__ < 9) +#define doc_fd int +#define doc_fd_p(fd) ((fd) >= 0) +#define doc_open emacs_open +#define doc_close emacs_close +#define doc_read_quit emacs_read_quit +#define doc_lseek lseek +#else /* HAVE_ANDROID && !defined ANDROID_STUBIFY + && __ANDROID_API__ >= 9 */ + +#include "android.h" + +/* Use an Android file descriptor under Android instead, as this + allows loading directly from asset files without loading each asset + into memory and creating a separate file descriptor every time. + + However, lread requires the ability to seek inside asset files, + which is not provided under Android 2.2. So when building for that + particular system, fall back to the usual file descriptor-based + code. */ + +#define doc_fd struct android_fd_or_asset +#define doc_fd_p(fd) ((fd).asset != (void *) -1) +#define doc_open android_open_asset +#define doc_close android_close_asset +#define doc_read_quit android_asset_read_quit +#define doc_lseek android_asset_lseek +#define USE_ANDROID_ASSETS +#endif /* !HAVE_ANDROID || ANDROID_STUBIFY || __ANDROID_API__ < 9 */ + + + /* Buffer used for reading from documentation file. */ static char *get_doc_string_buffer; static ptrdiff_t get_doc_string_buffer_size; @@ -59,6 +94,22 @@ read_bytecode_char (bool unreadflag) return *read_bytecode_pointer++; } +#ifdef USE_ANDROID_ASSETS + +/* Like `close_file_unwind'. However, PTR is a pointer to an Android + file descriptor instead of a system file descriptor. */ + +static void +close_file_unwind_android_fd (void *ptr) +{ + struct android_fd_or_asset *fd; + + fd = ptr; + android_close_asset (*fd); +} + +#endif /* USE_ANDROID_ASSETS */ + /* Extract a doc string from a file. FILEPOS says where to get it. If it is an integer, use that position in the standard DOC file. If it is (FILE . INTEGER), use FILE as the file name @@ -123,8 +174,8 @@ get_doc_string (Lisp_Object filepos, bool unibyte, bool definition) name = SAFE_ALLOCA (docdir_sizemax + SBYTES (file)); lispstpcpy (lispstpcpy (name, docdir), file); - int fd = emacs_open (name, O_RDONLY, 0); - if (fd < 0) + doc_fd fd = doc_open (name, O_RDONLY, 0); + if (!doc_fd_p (fd)) { if (will_dump_p ()) { @@ -132,9 +183,9 @@ get_doc_string (Lisp_Object filepos, bool unibyte, bool definition) So check in ../etc. */ lispstpcpy (stpcpy (name, sibling_etc), file); - fd = emacs_open (name, O_RDONLY, 0); + fd = doc_open (name, O_RDONLY, 0); } - if (fd < 0) + if (!doc_fd_p (fd)) { if (errno != ENOENT && errno != ENOTDIR) report_file_error ("Read error on documentation file", file); @@ -145,14 +196,18 @@ get_doc_string (Lisp_Object filepos, bool unibyte, bool definition) return concat3 (cannot_open, file, quote_nl); } } +#ifndef USE_ANDROID_ASSETS record_unwind_protect_int (close_file_unwind, fd); +#else /* USE_ANDROID_ASSETS */ + record_unwind_protect_ptr (close_file_unwind_android_fd, &fd); +#endif /* !USE_ANDROID_ASSETS */ /* Seek only to beginning of disk block. */ /* Make sure we read at least 1024 bytes before `position' so we can check the leading text for consistency. */ int offset = min (position, max (1024, position % (8 * 1024))); if (TYPE_MAXIMUM (off_t) < position - || lseek (fd, position - offset, 0) < 0) + || doc_lseek (fd, position - offset, 0) < 0) error ("Position %"pI"d out of range in doc string file \"%s\"", position, name); @@ -181,7 +236,7 @@ get_doc_string (Lisp_Object filepos, bool unibyte, bool definition) If we read the same block last time, maybe skip this? */ if (space_left > 1024 * 8) space_left = 1024 * 8; - int nread = emacs_read_quit (fd, p, space_left); + int nread = doc_read_quit (fd, p, space_left); if (nread < 0) report_file_error ("Read error on documentation file", file); p[nread] = 0; @@ -504,7 +559,7 @@ That file is found in `../etc' now; later, when the dumped Emacs is run, the same file name is found in the `doc-directory'. */) (Lisp_Object filename) { - int fd; + doc_fd fd; char buf[1024 + 1]; int filled; EMACS_INT pos; @@ -551,21 +606,25 @@ the same file name is found in the `doc-directory'. */) Vbuild_files = Fpurecopy (Vbuild_files); } - fd = emacs_open (name, O_RDONLY, 0); - if (fd < 0) + fd = doc_open (name, O_RDONLY, 0); + if (!doc_fd_p (fd)) { int open_errno = errno; report_file_errno ("Opening doc string file", build_string (name), open_errno); } +#ifndef USE_ANDROID_ASSETS record_unwind_protect_int (close_file_unwind, fd); +#else /* USE_ANDROID_ASSETS */ + record_unwind_protect_ptr (close_file_unwind_android_fd, &fd); +#endif /* !USE_ANDROID_ASSETS */ Vdoc_file_name = filename; filled = 0; pos = 0; while (true) { if (filled < 512) - filled += emacs_read_quit (fd, &buf[filled], sizeof buf - 1 - filled); + filled += doc_read_quit (fd, &buf[filled], sizeof buf - 1 - filled); if (!filled) break; -- cgit v1.3 From 140755f2cfe6a39f643ab0a9ca2d81b0ed470ae7 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 13 Jul 2023 12:05:50 +0800 Subject: Disable hardware acceleration on Android It serves no purpose and causes tearing. Uploading the bitmap to the GPU takes about as long as it does to incrementally update the surface in software. * java/AndroidManifest.xml.in: Disable hardware acceleration. * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): Make lastClosedMenu static. * java/org/gnu/emacs/EmacsDialog.java (toAlertDialog): Enable hardware acceleration within alert dialogs. * java/org/gnu/emacs/EmacsSurfaceView.java (onDraw): Describe why hardware acceleration is disabled. * java/org/gnu/emacs/EmacsWindow.java (run): Remove redundant call. --- java/AndroidManifest.xml.in | 5 +++++ java/org/gnu/emacs/EmacsActivity.java | 2 +- java/org/gnu/emacs/EmacsDialog.java | 14 +++++++++++++- java/org/gnu/emacs/EmacsSurfaceView.java | 20 +++++++++++++++++++- java/org/gnu/emacs/EmacsWindow.java | 2 +- 5 files changed, 39 insertions(+), 4 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index e79fb4e46e7..f2aede7369c 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -77,10 +77,14 @@ along with GNU Emacs. If not, see . --> @ANDROID_SHARED_USER_ID@ android:extractNativeLibs="true"> + + @@ -173,6 +177,7 @@ along with GNU Emacs. If not, see . --> = Build.VERSION_CODES.HONEYCOMB) + { + flag = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + window = dialog.getWindow (); + window.addFlags (flag); + } + return dialog; } diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 3f62af4ab99..54fe70e1634 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -176,7 +176,25 @@ public final class EmacsSurfaceView extends View onDraw (Canvas canvas) { /* Paint the view's bitmap; the bitmap might be recycled right - now. */ + now. + + Hardware acceleration is disabled in AndroidManifest.xml to + prevent Android from uploading the front buffer to the GPU from + a separate thread. This is important for two reasons: first, + the GPU command queue uses a massive amount of memory (dozens + of MiB) to upload bitmaps to the GPU, regardless of how much of + the bitmap has actually changed. + + Secondly, asynchronous texturization leads to race conditions + when a buffer swap occurs before the front buffer is fully + uploaded to the GPU. Normally, only slight and tolerable + tearing should result from this behavior, but Android does not + properly interlock the ``generation ID'' used to avoid + texturizing unchanged bitmaps with the bitmap contents, + consequentially leading to textures in an incomplete state + remaining in use to the GPU if a buffer swap happens between + the image data being uploaded and the ``generation ID'' being + read. */ if (frontBuffer != null) canvas.drawBitmap (frontBuffer, 0f, 0f, uiThreadPaint); diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 5e45275631b..0e96a8382d0 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -1377,7 +1377,7 @@ public final class EmacsWindow extends EmacsHandleObject if (tem != null) { - activity = (EmacsActivity) getAttachedConsumer (); + activity = (EmacsActivity) tem; activity.syncFullscreenWith (EmacsWindow.this); } } -- cgit v1.3 From 5ff31bf36cf00f9d5a378ce139fd5c2ae8d3f25e Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 18 Jul 2023 10:12:40 +0800 Subject: Update Android port * doc/lispref/commands.texi (Touchscreen Events): Describe treatment of canceled touch sequences during touch event translation. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Update JNI prototypes. * java/org/gnu/emacs/EmacsWindow.java (motionEvent): Set cancelation flag in events sent where appropriate. * lisp/touch-screen.el (touch-screen-handle-point-update): Improve treatment of horizontal scrolling near window edges. (touch-screen-handle-touch): Don't handle point up if the touch sequence has been canceled. * src/android.c (sendTouchDown, sendTouchUp, sendTouchMove): New argument `flags'. * src/androidgui.h (enum android_touch_event_flags): New enum. (struct android_touch_event): New field `flags'. * src/androidterm.c (handle_one_android_event): Report cancelation in TOUCHSCREEN_END_EVENTs. * src/keyboard.c (make_lispy_event): Fix botched merge. --- doc/lispref/commands.texi | 23 ++++---- java/org/gnu/emacs/EmacsNative.java | 9 ++-- java/org/gnu/emacs/EmacsWindow.java | 19 ++++--- lisp/touch-screen.el | 27 ++++++++-- src/android.c | 12 +++-- src/androidgui.h | 10 ++++ src/androidterm.c | 5 ++ src/keyboard.c | 103 +++++++++++++++++++++++++----------- 8 files changed, 152 insertions(+), 56 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index ffb01254fc9..55fecdce2d7 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2061,9 +2061,11 @@ keymaps that are active at the location of the of a single @code{down-mouse-1} event, with subsequent @code{touchscreen-update} events translated to mouse motion events (@pxref{Motion Events}), and a final @code{touchscreen-end} event -translated to a @code{mouse-1} or @code{drag-mouse-1} event. This is -referred to ``simple translation'', and produces a simple -correspondence between touchpoint motion and mouse motion. +translated to a @code{mouse-1} or @code{drag-mouse-1} event (unless +the @code{touchscreen-end} event indicates that the touch sequence has +been intercepted by another program.) This is referred to ``simple +translation'', and produces a simple correspondence between touchpoint +motion and mouse motion. @cindex @code{ignored-mouse-command}, a symbol property However, some commands bound to @@ -2078,13 +2080,14 @@ takes place: here, Emacs processes touch screen gestures (@pxref{Touchscreens,,, emacs, The GNU Emacs Manual}) first, and finally attempts to translate touch screen events into mouse events if no gesture was detected prior to a closing @code{touchscreen-end} -event and a command is bound to @code{mouse-1} at the location of that -event. Before generating the @code{mouse-1} event, point is also set -to the location of the @code{touchscreen-end} event, and the window -containing the position of that event is selected, as a compromise for -packages which assume @code{mouse-drag-region} has already set point -to the location of any mouse click and selected the window where it -took place. +event (with its @var{canceled} parameter @code{nil}, as with simple +translation) and a command is bound to @code{mouse-1} at the location +of that event. Before generating the @code{mouse-1} event, point is +also set to the location of the @code{touchscreen-end} event, and the +window containing the position of that event is selected, as a +compromise for packages which assume @code{mouse-drag-region} has +already set point to the location of any mouse click and selected the +window where it took place. To prevent unwanted @code{mouse-1} events arriving after a mouse menu is dismissed (@pxref{Mouse Menus}), Emacs also avoids simple diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 1331539879a..d4d502ede5a 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -142,15 +142,18 @@ public final class EmacsNative /* Send an ANDROID_TOUCH_DOWN event. */ public static native long sendTouchDown (short window, int x, int y, - long time, int pointerID); + long time, int pointerID, + int flags); /* Send an ANDROID_TOUCH_UP event. */ public static native long sendTouchUp (short window, int x, int y, - long time, int pointerID); + long time, int pointerID, + int flags); /* Send an ANDROID_TOUCH_MOVE event. */ public static native long sendTouchMove (short window, int x, int y, - long time, int pointerID); + long time, int pointerID, + int flags); /* Send an ANDROID_WHEEL event. */ public static native long sendWheel (short window, int x, int y, diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 0e96a8382d0..8118479319e 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -1025,23 +1025,30 @@ public final class EmacsWindow extends EmacsHandleObject /* Touch down event. */ EmacsNative.sendTouchDown (this.handle, coordinate.x, coordinate.y, time, - coordinate.id); + coordinate.id, 0); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: + /* Touch up event. */ + EmacsNative.sendTouchUp (this.handle, coordinate.x, + coordinate.y, time, + coordinate.id, 0); + break; + case MotionEvent.ACTION_CANCEL: - /* Touch up event. Android documentation says ACTION_CANCEL - should be treated as more or less equivalent to ACTION_UP, - so that is what is done here. */ + /* Touch sequence cancellation event. */ EmacsNative.sendTouchUp (this.handle, coordinate.x, - coordinate.y, time, coordinate.id); + coordinate.y, time, + coordinate.id, + 1 /* ANDROID_TOUCH_SEQUENCE_CANCELED */); break; case MotionEvent.ACTION_MOVE: /* Pointer motion event. */ EmacsNative.sendTouchMove (this.handle, coordinate.x, - coordinate.y, time, coordinate.id); + coordinate.y, time, + coordinate.id, 0); break; } } diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el index 1ef66d0043f..8f10bc3e794 100644 --- a/lisp/touch-screen.el +++ b/lisp/touch-screen.el @@ -548,11 +548,26 @@ then move point to the position of POINT." ;; WINDOW. (relative-xy (touch-screen-relative-xy posn window)) + (col (and (eq (posn-area posn) 'text-area) + (car (posn-col-row posn + (posn-window posn))))) + ;; Don't start horizontal scrolling if the touch + ;; point originated within two columns of the window + ;; edges, as systems like Android use those two + ;; columns to implement gesture navigation. + (diff-x-eligible + (and col (> (car col) 2) + (< (car col) (- (window-width window) 2)))) (diff-x (- (car last-posn) (car relative-xy))) (diff-y (- (cdr last-posn) (cdr relative-xy)))) - ;; Decide whether or not to start scrolling. - (when (or (> diff-y 10) (> diff-x 10) - (< diff-y -10) (< diff-x -10)) + ;; Decide whether or not to start scrolling. Make the + ;; hscrolling threshold slightly larger than the vertical + ;; scrolling threshold, to compensate better for + ;; Android-style gesture navigation. + (when (or (> diff-y 10) (and diff-x-eligible + (> diff-x 20)) + (< diff-y -10) (and diff-x-eligible + (< diff-x -20))) (setcar (nthcdr 3 touch-screen-current-tool) 'scroll) (setcar (nthcdr 2 touch-screen-current-tool) @@ -852,7 +867,11 @@ the place of EVENT within the key sequence being translated, or (cancel-timer touch-screen-current-timer) (setq touch-screen-current-timer nil)) (unwind-protect - (touch-screen-handle-point-up (cadr event) prefix) + ;; Don't perform any actions associated with releasing the + ;; tool if the touch sequence was intercepted by another + ;; program. + (unless (caddr event) + (touch-screen-handle-point-up (cadr event) prefix)) ;; Make sure the tool list is cleared even if ;; `touch-screen-handle-point-up' throws. (setq touch-screen-current-tool nil))) diff --git a/src/android.c b/src/android.c index 90288737c77..f2e5e75d35e 100644 --- a/src/android.c +++ b/src/android.c @@ -2870,7 +2870,8 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, jshort window, jint x, jint y, - jlong time, jint pointer_id) + jlong time, jint pointer_id, + jint flags) { JNI_STACK_ALIGNMENT_PROLOGUE; @@ -2883,6 +2884,7 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, event.touch.y = y; event.touch.time = time; event.touch.pointer_id = pointer_id; + event.touch.flags = flags; android_write_event (&event); return event_serial; @@ -2891,7 +2893,8 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, jshort window, jint x, jint y, - jlong time, jint pointer_id) + jlong time, jint pointer_id, + jint flags) { JNI_STACK_ALIGNMENT_PROLOGUE; @@ -2904,6 +2907,7 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, event.touch.y = y; event.touch.time = time; event.touch.pointer_id = pointer_id; + event.touch.flags = flags; android_write_event (&event); return event_serial; @@ -2912,7 +2916,8 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, jshort window, jint x, jint y, - jlong time, jint pointer_id) + jlong time, jint pointer_id, + jint flags) { JNI_STACK_ALIGNMENT_PROLOGUE; @@ -2925,6 +2930,7 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, event.touch.y = y; event.touch.time = time; event.touch.pointer_id = pointer_id; + event.touch.flags = flags; android_write_event (&event); return event_serial; diff --git a/src/androidgui.h b/src/androidgui.h index 9e604cdcb8c..265ec29b678 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -365,6 +365,13 @@ struct android_expose_event int width, height; }; +enum android_touch_event_flags + { + /* This touch sequence has been intercepted by the WM (probably + for back gesture navigation or some such.) */ + ANDROID_TOUCH_SEQUENCE_CANCELED = 1, + }; + struct android_touch_event { /* Type of the event. */ @@ -384,6 +391,9 @@ struct android_touch_event /* Index of the pointer being tracked. */ unsigned int pointer_id; + + /* Flags associated with this event. */ + int flags; }; struct android_wheel_event diff --git a/src/androidterm.c b/src/androidterm.c index 27800a61864..bcb6cd6db45 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -1511,6 +1511,11 @@ handle_one_android_event (struct android_display_info *dpyinfo, inev.ie.kind = TOUCHSCREEN_END_EVENT; inev.ie.timestamp = event->touch.time; + /* Report whether the sequence has been canceled. */ + + if (event->touch.flags & ANDROID_TOUCH_SEQUENCE_CANCELED) + inev.ie.modifiers = 1; + XSETFRAME (inev.ie.frame_or_window, any); XSETINT (inev.ie.x, event->touch.x); XSETINT (inev.ie.y, event->touch.y); diff --git a/src/keyboard.c b/src/keyboard.c index bd7433e584a..73c4e3f2593 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -6560,11 +6560,10 @@ make_lispy_event (struct input_event *event) { Lisp_Object x, y, id, position; struct frame *f; +#ifdef HAVE_WINDOW_SYSTEM int tab_bar_item; bool close; -#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR - int column, row, dummy; -#endif /* defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR */ +#endif /* HAVE_WINDOW_SYSTEM */ f = XFRAME (event->frame_or_window); id = event->arg; @@ -6572,16 +6571,82 @@ make_lispy_event (struct input_event *event) y = event->y; #if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR - if (event->kind == TOUCHSCREEN_BEGIN_EVENT - && coords_in_menu_bar_window (f, XFIXNUM (x), XFIXNUM (y))) + if (coords_in_menu_bar_window (f, XFIXNUM (x), XFIXNUM (y))) { /* If the tap began in the menu bar window, then save the id. */ menu_bar_touch_id = id; return Qnil; } - else if (event->kind == TOUCHSCREEN_END_EVENT - && EQ (menu_bar_touch_id, id)) +#endif /* defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR */ + + position = make_lispy_position (f, x, y, event->timestamp); + +#ifdef HAVE_WINDOW_SYSTEM + + /* Now check if POSITION lies on the tab bar. If so, look up + the corresponding tab bar item's propertized string as the + OBJECT. */ + + if (coords_in_tab_bar_window (f, XFIXNUM (event->x), + XFIXNUM (event->y)) + /* `get_tab_bar_item_kbd' returns 0 if the item was + previously highlighted, 1 otherwise, and -1 if there is + no tab bar item. */ + && get_tab_bar_item_kbd (f, XFIXNUM (event->x), + XFIXNUM (event->y), &tab_bar_item, + &close) >= 0) + { + /* First, obtain the propertized string. */ + x = Fcopy_sequence (AREF (f->tab_bar_items, + (tab_bar_item + + TAB_BAR_ITEM_CAPTION))); + + /* Next, add the key binding. */ + AUTO_LIST2 (y, Qmenu_item, list3 (AREF (f->tab_bar_items, + (tab_bar_item + + TAB_BAR_ITEM_KEY)), + AREF (f->tab_bar_items, + (tab_bar_item + + TAB_BAR_ITEM_BINDING)), + close ? Qt : Qnil)); + + /* And add the new properties to the propertized string. */ + Fadd_text_properties (make_fixnum (0), + make_fixnum (SCHARS (x)), + y, x); + + /* Set the position to 0. */ + x = Fcons (x, make_fixnum (0)); + + /* Finally, add the OBJECT. */ + position = nconc2 (position, Fcons (x, Qnil)); + } + +#endif /* HAVE_WINDOW_SYSTEM */ + + return list2 (Qtouchscreen_begin, + Fcons (id, position)); + } + + case TOUCHSCREEN_END_EVENT: + { + Lisp_Object x, y, id, position; + struct frame *f = XFRAME (event->frame_or_window); +#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR + int column, row, dummy; +#endif /* defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR */ +#ifdef HAVE_WINDOW_SYSTEM + int tab_bar_item; + bool close; +#endif /* HAVE_WINDOW_SYSTEM */ + + id = event->arg; + x = event->x; + y = event->y; + +#if defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR + if (EQ (menu_bar_touch_id, id)) { /* This touch should activate the menu bar. Generate the menu bar event. */ @@ -6631,8 +6696,6 @@ make_lispy_event (struct input_event *event) } #endif /* defined HAVE_WINDOW_SYSTEM && !defined HAVE_EXT_MENU_BAR */ - position = make_lispy_position (f, x, y, event->timestamp); - #ifdef HAVE_WINDOW_SYSTEM /* Now check if POSITION lies on the tab bar. If so, look up @@ -6676,29 +6739,9 @@ make_lispy_event (struct input_event *event) #endif /* HAVE_WINDOW_SYSTEM */ - return list2 (((event->kind - == TOUCHSCREEN_BEGIN_EVENT) - ? Qtouchscreen_begin - : Qtouchscreen_end), - Fcons (id, position)); - } - - case TOUCHSCREEN_END_EVENT: - { - Lisp_Object x, y, id, position; - struct frame *f = XFRAME (event->frame_or_window); - - id = event->arg; - x = event->x; - y = event->y; - position = make_lispy_position (f, x, y, event->timestamp); - return list3 (((event->kind - == TOUCHSCREEN_BEGIN_EVENT) - ? Qtouchscreen_begin - : Qtouchscreen_end), - Fcons (id, position), + return list3 (Qtouchscreen_end, Fcons (id, position), event->modifiers ? Qt : Qnil); } -- cgit v1.3 From 8ff8a7fd5c50aaa7721a562a11836b4ec733ba5e Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 2 Aug 2023 09:09:53 +0800 Subject: Fix reporting of key events containing SYM and META * doc/emacs/android.texi (Android)::(What is Android?): (Android Startup, Android File System, Android Environment) (Android Windowing, Android Fonts, Android Troubleshooting): Improve section titles. (Android Windowing): Describe the relation between keyboard modifiers reported by Android and those in key events. * java/org/gnu/emacs/EmacsWindow.java (onKeyDown, onKeyUp): Clear META_SYM_ON and META_META_MASK when retrieving ASCII characters. * src/androidgui.h: Add ANDROID_META_MASK. * src/androidterm.c (android_android_to_emacs_modifiers) (android_emacs_to_android_modifiers): Transform META to Alt, and vice versa. --- doc/emacs/android.texi | 33 +++++++++++++++++++++++---------- java/org/gnu/emacs/EmacsWindow.java | 18 ++++++++++++------ src/androidgui.h | 1 + src/androidterm.c | 6 ++++-- 4 files changed, 40 insertions(+), 18 deletions(-) (limited to 'java/org/gnu/emacs/EmacsWindow.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 4b8f36a65eb..9d352f3a484 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -26,7 +26,7 @@ about using such devices with Emacs, @pxref{Other Input Devices}. @end menu @node What is Android? -@section Android history +@section Android History Android is an operating system for mobile devices developed by the Open Handset Alliance, a group of companies interested in developing @@ -59,7 +59,7 @@ your freedom's sake. hope this taste of freedom will inspire users to escape from them. @node Android Startup -@section Starting up Emacs on Android +@section Starting Emacs on Android Emacs is not installed on Android devices from source code or through a package manager. Instead, Emacs is compiled for Android on @@ -155,7 +155,7 @@ case such files are copied to a temporary directory before being opened. @node Android File System -@section What files Emacs can access under Android +@section What Files Emacs Can Access on Android @cindex /assets directory, android Emacs exposes a special directory on Android systems: the name of @@ -281,7 +281,7 @@ files under @file{/sdcard} as usual. These settings are not present on some proprietary versions of Android. @node Android Document Providers -@section Accessing files from other programs under Android +@section Accessing Files from Other Programs on Android @cindex document providers, Android @cindex /content/storage directory, Android @@ -399,7 +399,7 @@ Startup}) connect the Android system to another computer, and run: $ adb shell "settings put global settings_enable_monitor_phantom_procs false" @end example -@section Running Emacs in the background +@section Running Emacs in the Background @cindex emacs killed, android @cindex emacs in the background, android @@ -429,7 +429,7 @@ the background in their proprietary versions of Android. There is a list of such troublesome manufacturers and sometimes workarounds at @url{https://dontkillmyapp.com/}. -@section Android permissions +@section Android Permissions @cindex external storage, android Android also defines a permissions system that determines what @@ -526,7 +526,7 @@ more details, as how to do this varies by device. @end itemize @node Android Windowing -@section The Android window system +@section The Android Window System Android has an unusual window system; there, all windows are maximized or full-screen, and only one window can be displayed at a @@ -650,8 +650,21 @@ System -> Apps -> Emacs -> More -> Display over other apps menu in the system settings, but this procedure may vary by device. +@cindex keyboard modifiers, android + There is a direct relation between physical modifier keys and Emacs +modifiers (@pxref{Modifier Keys}) reported within key events, subject +to a single exception: if @key{Alt} on your keyboard is depressed, +then the @key{Meta} modifier will be reported by Emacs in its place, +and vice versa. This irregularity is since most keyboards posses no +special @key{Meta} key, and the @key{Alt} modifier is seldom employed +in Emacs. + + Bear in mind that Android uses a different name for the @key{Super} +modifier: it is referred to as @key{SYM} on Android keyboards and +within the Settings keymap menu. + @node Android Fonts -@section Font backends and selection under Android +@section Font Backends and Selection under Android @cindex fonts, android Emacs supports two font backends under Android: they are respectively @@ -693,7 +706,7 @@ removed; distortable fonts with the same family will no longer be used to provide that style. @node Android Troubleshooting -@section What to do when something goes wrong on Android +@section Troubleshooting Startup Problems on Android @cindex troubleshooting, android @cindex emacs -Q, android @@ -741,7 +754,7 @@ manager that comes with your device, you can rename, delete, or edit your initialization or dump files from there instead. @node Android Software -@section Installing extra software on Android +@section Installing Extra Software on Android @cindex installing extra software on Android @cindex installing Unix software on Android diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index 8118479319e..a1f70644e16 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -616,10 +616,13 @@ public final class EmacsWindow extends EmacsHandleObject state = eventModifiers (event); - /* Ignore meta-state understood by Emacs for now, or Ctrl+C will - not be recognized as an ASCII key press event. */ + /* Ignore meta-state understood by Emacs for now, or key presses + such as Ctrl+C and Meta+C will not be recognized as an ASCII + key press event. */ + state_1 - = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); + = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK + | KeyEvent.META_SYM_ON | KeyEvent.META_META_MASK); synchronized (eventStrings) { @@ -646,10 +649,13 @@ public final class EmacsWindow extends EmacsHandleObject /* Compute the event's modifier mask. */ state = eventModifiers (event); - /* Ignore meta-state understood by Emacs for now, or Ctrl+C will - not be recognized as an ASCII key press event. */ + /* Ignore meta-state understood by Emacs for now, or key presses + such as Ctrl+C and Meta+C will not be recognized as an ASCII + key press event. */ + state_1 - = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK); + = state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK + | KeyEvent.META_SYM_ON | KeyEvent.META_META_MASK); EmacsNative.sendKeyRelease (this.handle, event.getEventTime (), diff --git a/src/androidgui.h b/src/androidgui.h index 265ec29b678..14225f7bf80 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -263,6 +263,7 @@ enum android_modifier_mask ANDROID_CONTROL_MASK = 4096, ANDROID_ALT_MASK = 2, ANDROID_SUPER_MASK = 4, + ANDROID_META_MASK = 65536, }; struct android_key_event diff --git a/src/androidterm.c b/src/androidterm.c index bcb6cd6db45..f74463f88cd 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -365,7 +365,8 @@ android_android_to_emacs_modifiers (struct android_display_info *dpyinfo, return (((state & ANDROID_CONTROL_MASK) ? ctrl_modifier : 0) | ((state & ANDROID_SHIFT_MASK) ? shift_modifier : 0) | ((state & ANDROID_ALT_MASK) ? meta_modifier : 0) - | ((state & ANDROID_SUPER_MASK) ? super_modifier : 0)); + | ((state & ANDROID_SUPER_MASK) ? super_modifier : 0) + | ((state & ANDROID_META_MASK) ? alt_modifier : 0)); } static int @@ -375,7 +376,8 @@ android_emacs_to_android_modifiers (struct android_display_info *dpyinfo, return (((state & ctrl_modifier) ? ANDROID_CONTROL_MASK : 0) | ((state & shift_modifier) ? ANDROID_SHIFT_MASK : 0) | ((state & meta_modifier) ? ANDROID_ALT_MASK : 0) - | ((state & super_modifier) ? ANDROID_SUPER_MASK : 0)); + | ((state & super_modifier) ? ANDROID_SUPER_MASK : 0) + | ((state & alt_modifier) ? ANDROID_META_MASK : 0)); } static void android_frame_rehighlight (struct android_display_info *); -- cgit v1.3