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/EmacsView.java | 211 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 java/org/gnu/emacs/EmacsView.java (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java new file mode 100644 index 00000000000..237946d6366 --- /dev/null +++ b/java/org/gnu/emacs/EmacsView.java @@ -0,0 +1,211 @@ +/* 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.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; + +/* This is an Android view which has a back and front buffer. When + swapBuffers is called, the back buffer is swapped to the front + buffer, and any damage is invalidated. frontBitmap and backBitmap + are modified and used both from the UI and the Emacs thread. As a + result, there is a lock held during all drawing operations. + + It is also a ViewGroup, as it also lays out children. */ + +public class EmacsView extends ViewGroup +{ + public static final String TAG = "EmacsView"; + + /* The associated EmacsWindow. */ + public EmacsWindow window; + + /* The buffer bitmap. */ + public Bitmap bitmap; + + /* The associated canvases. */ + public Canvas canvas; + + /* The damage region. */ + public Region damageRegion; + + /* The paint. */ + public Paint paint; + + /* The associated surface view. */ + private EmacsSurfaceView surfaceView; + + public + EmacsView (EmacsWindow window) + { + super (EmacsService.SERVICE); + + this.window = window; + this.damageRegion = new Region (); + this.paint = new Paint (); + + /* Create the surface view. */ + this.surfaceView = new EmacsSurfaceView (this); + + setFocusable (FOCUSABLE); + addView (this.surfaceView); + } + + @Override + protected void + onMeasure (int widthMeasureSpec, int heightMeasureSpec) + { + Rect measurements; + int width, height; + + /* Return the width and height of the window regardless of what + the parent says. */ + measurements = window.getGeometry (); + + width = measurements.width (); + height = measurements.height (); + + /* Now apply any extra requirements in widthMeasureSpec and + heightMeasureSpec. */ + + if (MeasureSpec.getMode (widthMeasureSpec) == MeasureSpec.EXACTLY) + width = MeasureSpec.getSize (widthMeasureSpec); + else if (MeasureSpec.getMode (widthMeasureSpec) == MeasureSpec.AT_MOST + && width > MeasureSpec.getSize (widthMeasureSpec)) + width = MeasureSpec.getSize (widthMeasureSpec); + + if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.EXACTLY) + height = MeasureSpec.getSize (heightMeasureSpec); + else if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.AT_MOST + && height > MeasureSpec.getSize (heightMeasureSpec)) + height = MeasureSpec.getSize (heightMeasureSpec); + + super.setMeasuredDimension (width, height); + } + + @Override + protected void + onLayout (boolean changed, int left, int top, int right, + int bottom) + { + int count, i; + View child; + Rect windowRect; + + if (changed) + { + 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); + } + + count = getChildCount (); + + for (i = 0; i < count; ++i) + { + child = getChildAt (i); + + if (child == surfaceView) + /* The child is the surface view, so give it the entire + view. */ + child.layout (left, top, right, bottom); + else if (child.getVisibility () != GONE) + { + if (!(child instanceof EmacsView)) + continue; + + /* What to do: lay out the view precisely according to its + window rect. */ + windowRect = ((EmacsView) child).window.getGeometry (); + child.layout (windowRect.left, windowRect.top, + windowRect.right, windowRect.bottom); + } + } + } + + public void + damageRect (Rect damageRect) + { + damageRegion.union (damageRect); + } + + public void + swapBuffers () + { + Bitmap back; + Canvas canvas; + Rect damageRect; + + if (damageRegion.isEmpty ()) + return; + + if (!surfaceView.isCreated ()) + return; + + if (bitmap == null) + return; + + /* Lock the canvas with the specified damage. */ + damageRect = damageRegion.getBounds (); + canvas = surfaceView.lockCanvas (damageRect); + + /* Return if locking the canvas failed. */ + if (canvas == null) + return; + + /* Copy from the back buffer to the canvas. */ + canvas.drawBitmap (bitmap, damageRect, damageRect, paint); + + /* Unlock the canvas and clear the damage. */ + surfaceView.unlockCanvasAndPost (canvas); + damageRegion.setEmpty (); + } + + @Override + public boolean + onKeyDown (int keyCode, KeyEvent event) + { + window.onKeyDown (keyCode, event); + return true; + } + + @Override + public boolean + onKeyUp (int keyCode, KeyEvent event) + { + window.onKeyUp (keyCode, event); + return true; + } +}; -- 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/EmacsView.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/EmacsView.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/EmacsView.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/EmacsView.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 6e2bc91d924fbeb0ad5728e0424eabc905c0d366 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 15 Jan 2023 11:57:10 +0800 Subject: Implement toolkit menus on Android * java/org/gnu/emacs/EmacsActivity.java (onContextMenuClosed): New function. * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): New field `itemAlreadySelected'. (onMenuItemClick): New function. (inflateMenuItems): Attach onClickListener as appropriate. (display1): Clear itemAlreadySelected. (display): Fix runnable synchronization. * java/org/gnu/emacs/EmacsNative.java (sendContextMenu): New function. * java/org/gnu/emacs/EmacsView.java (popupMenu): (cancelPopupMenu): Set popupactive correctly. * src/android.c (android_run_select_thread): Fix android_select again. (android_wait_event): New function. * src/android.h: Update prototypes. * src/androidgui.h (enum android_event_type): New `ANDROID_CONTEXT_MENU' event. (struct android_menu_event, union android_event): Add new event. * src/androidmenu.c (struct android_emacs_context_menu): New structure. (android_init_emacs_context_menu): Add `dismiss' method. (struct android_dismiss_menu_data): New structure. (android_dismiss_menu, android_process_events_for_menu): New functions. (android_menu_show): Set an actual item ID. (popup_activated): Define when stubify as well. (Fmenu_or_popup_active_p): New function. (syms_of_androidmenu): New function. * src/androidterm.c (handle_one_android_event): Handle context menu events. * src/androidterm.h (struct android_display_info): New field for menu item ID. * src/emacs.c (android_emacs_init): Call syms_of_androidmenu. * src/xdisp.c (note_mouse_highlight): Return if popup_activated on Android as well. --- java/org/gnu/emacs/EmacsActivity.java | 15 ++++ java/org/gnu/emacs/EmacsContextMenu.java | 64 +++++++++++++++-- java/org/gnu/emacs/EmacsNative.java | 3 + java/org/gnu/emacs/EmacsView.java | 2 + src/android.c | 42 +++++++++++- src/android.h | 1 + src/androidgui.h | 16 +++++ src/androidmenu.c | 114 +++++++++++++++++++++++++++++-- src/androidterm.c | 8 +++ src/androidterm.h | 5 ++ src/emacs.c | 1 + src/xdisp.c | 3 +- 12 files changed, 258 insertions(+), 16 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 4cd286d1e89..4b96a376987 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.util.Log; import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; +import android.view.Menu; public class EmacsActivity extends Activity implements EmacsWindowAttachmentManager.WindowConsumer @@ -227,4 +228,18 @@ public class EmacsActivity extends Activity EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); super.onResume (); } + + @Override + public void + onContextMenuClosed (Menu menu) + { + Log.d (TAG, "onContextMenuClosed: " + menu); + + /* Send a context menu event given that no menu item has already + been selected. */ + if (!EmacsContextMenu.itemAlreadySelected) + EmacsNative.sendContextMenu ((short) 0, 0); + + super.onContextMenuClosed (menu); + } }; diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 8d7ae08b257..02dd1c7efa9 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -31,6 +31,8 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.util.Log; + import android.widget.PopupMenu; /* Context menu implementation. This object is built from JNI and @@ -40,12 +42,31 @@ import android.widget.PopupMenu; public class EmacsContextMenu { - private class Item + private static final String TAG = "EmacsContextMenu"; + + /* Whether or not an item was selected. */ + public static boolean itemAlreadySelected; + + private class Item implements MenuItem.OnMenuItemClickListener { public int itemID; public String itemName; public EmacsContextMenu subMenu; public boolean isEnabled; + + @Override + public boolean + onMenuItemClick (MenuItem item) + { + Log.d (TAG, "onMenuItemClick: " + itemName + " (" + itemID + ")"); + + /* Send a context menu event. */ + EmacsNative.sendContextMenu ((short) 0, itemID); + + /* Say that an item has already been selected. */ + itemAlreadySelected = true; + return true; + } }; public List menuItems; @@ -137,6 +158,7 @@ public class EmacsContextMenu else { menuItem = menu.add (item.itemName); + menuItem.setOnMenuItemClickListener (item); /* If the item ID is zero, then disable the item. */ if (item.itemID == 0 || !item.isEnabled) @@ -171,6 +193,10 @@ public class EmacsContextMenu private boolean display1 (EmacsWindow window, int xPosition, int yPosition) { + /* Set this flag to false. It is used to decide whether or not to + send 0 in response to the context menu being closed. */ + itemAlreadySelected = false; + return window.view.popupMenu (this, xPosition, yPosition); } @@ -199,15 +225,39 @@ public class EmacsContextMenu } }; - try + synchronized (runnable) { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); + EmacsService.SERVICE.runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } } return rc.thing; } + + /* Dismiss this context menu. WINDOW is the window where the + context menu is being displayed. */ + + public void + dismiss (final EmacsWindow window) + { + Runnable runnable; + + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + window.view.cancelPopupMenu (); + itemAlreadySelected = false; + } + }); + } }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index a11e509cd7f..4a80f88edcf 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -124,6 +124,9 @@ public class EmacsNative /* Send an ANDROID_DEICONIFIED event. */ public static native void sendDeiconified (short window); + /* Send an ANDROID_CONTEXT_MENU event. */ + public static native void sendContextMenu (short window, int menuEventID); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 1391f630be0..445d8ffa023 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -453,6 +453,7 @@ public class EmacsView extends ViewGroup return false; contextMenu = menu; + popupActive = true; /* On API 21 or later, use showContextMenu (float, float). */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) @@ -469,5 +470,6 @@ public class EmacsView extends ViewGroup + " popupActive set"); contextMenu = null; + popupActive = false; } }; diff --git a/src/android.c b/src/android.c index 5e5e28c60ca..ed162a903ba 100644 --- a/src/android.c +++ b/src/android.c @@ -236,7 +236,7 @@ static sem_t android_pselect_sem, android_pselect_start_sem; static void * android_run_select_thread (void *data) { - sigset_t signals; + sigset_t signals, sigset; int rc; sigfillset (&signals); @@ -259,7 +259,11 @@ android_run_select_thread (void *data) /* Make sure SIGUSR1 can always wake pselect up. */ if (android_pselect_sigset) - sigdelset (android_pselect_sigset, SIGUSR1); + { + sigset = *android_pselect_sigset; + sigdelset (&sigset, SIGUSR1); + android_pselect_sigset = &sigset; + } else android_pselect_sigset = &signals; @@ -356,6 +360,23 @@ android_pending (void) return i; } +/* Wait for events to become available synchronously. Return once an + event arrives. */ + +void +android_wait_event (void) +{ + pthread_mutex_lock (&event_queue.mutex); + + /* Wait for events to appear if there are none available to + read. */ + if (!event_queue.num_events) + pthread_cond_wait (&event_queue.read_var, + &event_queue.mutex); + + pthread_mutex_unlock (&event_queue.mutex); +} + void android_next_event (union android_event *event_return) { @@ -1472,6 +1493,8 @@ NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object, event.iconified.type = ANDROID_ICONIFIED; event.iconified.window = window; + + android_write_event (&event); } extern JNIEXPORT void JNICALL @@ -1482,6 +1505,21 @@ NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object, event.iconified.type = ANDROID_DEICONIFIED; event.iconified.window = window; + + android_write_event (&event); +} + +extern JNIEXPORT void 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.window = window; + event.menu.menu_event_id = menu_event_id; + + android_write_event (&event); } #pragma clang diagnostic pop diff --git a/src/android.h b/src/android.h index 98f2494e9a3..e68e0a51fbf 100644 --- a/src/android.h +++ b/src/android.h @@ -89,6 +89,7 @@ extern jstring android_build_string (Lisp_Object); extern void android_exception_check (void); extern void android_get_keysym_name (int, char *, size_t); +extern void android_wait_event (void); diff --git a/src/androidgui.h b/src/androidgui.h index 8450a1f637b..0e075fda95e 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -233,6 +233,7 @@ enum android_event_type ANDROID_WHEEL, ANDROID_ICONIFIED, ANDROID_DEICONIFIED, + ANDROID_CONTEXT_MENU, }; struct android_any_event @@ -371,6 +372,18 @@ struct android_iconify_event android_window window; }; +struct android_menu_event +{ + /* Type of the event. */ + enum android_event_type type; + + /* Window associated with the event. Always None. */ + android_window window; + + /* Menu event ID. */ + int menu_event_id; +}; + union android_event { enum android_event_type type; @@ -395,6 +408,9 @@ union android_event /* This has no parallel in X because Android doesn't have window properties. */ struct android_iconify_event iconified; + + /* This is only used to transmit selected menu items. */ + struct android_menu_event menu; }; enum diff --git a/src/androidmenu.c b/src/androidmenu.c index 0f0c6f4ef1f..7522f9c5a52 100644 --- a/src/androidmenu.c +++ b/src/androidmenu.c @@ -54,6 +54,7 @@ struct android_emacs_context_menu jmethodID add_pane; jmethodID parent; jmethodID display; + jmethodID dismiss; }; /* Identifiers associated with the EmacsContextMenu class. */ @@ -101,6 +102,7 @@ android_init_emacs_context_menu (void) 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"); + FIND_METHOD (dismiss, "dismiss", "(Lorg/gnu/emacs/EmacsWindow;)V"); #undef FIND_METHOD #undef FIND_METHOD_STATIC @@ -130,6 +132,62 @@ android_push_local_frame (void) record_unwind_protect_void (android_unwind_local_frame); } +/* Data for android_dismiss_menu. */ + +struct android_dismiss_menu_data +{ + /* The menu object. */ + jobject menu; + + /* The window object. */ + jobject window; +}; + +/* Cancel the context menu passed in POINTER. Also, clear + popup_activated_flag. */ + +static void +android_dismiss_menu (void *pointer) +{ + struct android_dismiss_menu_data *data; + + data = pointer; + (*android_java_env)->CallVoidMethod (android_java_env, + data->menu, + menu_class.dismiss, + data->window); + popup_activated_flag = 0; +} + +/* Recursively process events until a ANDROID_CONTEXT_MENU event + arrives. Then, return the item ID specified in the event in + *ID. */ + +static void +android_process_events_for_menu (int *id) +{ + /* 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; + + /* Now wait for the menu event ID to change. */ + while (x_display_list->menu_event_id == -1) + { + /* Wait for events to become available. */ + android_wait_event (); + + /* Process pending signals. */ + process_pending_signals (); + + /* Maybe quit. */ + maybe_quit (); + } + + /* Return the ID. */ + *id = x_display_list->menu_event_id; +} + Lisp_Object android_menu_show (struct frame *f, int x, int y, int menuflags, Lisp_Object title, const char **error_name) @@ -140,11 +198,13 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, Lisp_Object pane_name, prefix; const char *pane_string; specpdl_ref count, count1; - Lisp_Object item_name, enable, def; + Lisp_Object item_name, enable, def, tem; jmethodID method; jobject store; bool rc; jobject window; + int id, item_id; + struct android_dismiss_menu_data data; count = SPECPDL_INDEX (); @@ -266,6 +326,12 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, ; else { + /* Compute the item ID. This is the index of value. + Make sure it doesn't overflow. */ + + if (!INT_ADD_OK (0, i + MENU_ITEMS_ITEM_VALUE, &item_id)) + memory_full (i + MENU_ITEMS_ITEM_VALUE * sizeof (Lisp_Object)); + /* Add this menu item with the appropriate state. */ title_string = (!NILP (item_name) @@ -274,7 +340,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, (*android_java_env)->CallVoidMethod (android_java_env, current_context_menu, menu_class.add_item, - (jint) 1, + (jint) item_id, title_string, (jboolean) !NILP (enable)); android_exception_check (); @@ -295,6 +361,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, ANDROID_HANDLE_WINDOW); rc = (*android_java_env)->CallBooleanMethod (android_java_env, context_menu, + menu_class.display, window, (jint) x, (jint) y); android_exception_check (); @@ -303,25 +370,54 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, /* This means displaying the menu failed. */ goto finish; -#if 0 - record_unwind_protect_ptr (android_dismiss_menu, &context_menu); + /* Make sure the context menu is always dismissed. */ + data.menu = context_menu; + data.window = window; + record_unwind_protect_ptr (android_dismiss_menu, &data); - /* Otherwise, loop waiting for the menu event to arrive. */ + /* 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. */ goto finish; -#endif + /* id is an index into menu_items. Check that it remains + valid. */ + if (id >= ASIZE (menu_items)) + goto finish; + + /* Now return the menu item at that location. */ + tem = AREF (menu_items, id); + unblock_input (); + return unbind_to (count, tem); finish: unblock_input (); return unbind_to (count, Qnil); } +#else + +int +popup_activated (void) +{ + return 0; +} + #endif +DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, + Smenu_or_popup_active_p, 0, 0, 0, + doc: /* SKIP: real doc in xfns.c. */) + (void) +{ + return (popup_activated ()) ? Qt : Qnil; +} + void init_androidmenu (void) { @@ -329,3 +425,9 @@ init_androidmenu (void) android_init_emacs_context_menu (); #endif } + +void +syms_of_androidmenu (void) +{ + defsubr (&Smenu_or_popup_active_p); +} diff --git a/src/androidterm.c b/src/androidterm.c index 002d39af707..4017fec60a5 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -1125,6 +1125,14 @@ handle_one_android_event (struct android_display_info *dpyinfo, XSETFRAME (inev.ie.frame_or_window, any); goto OTHER; + /* Context menu handling. */ + case ANDROID_CONTEXT_MENU: + + if (dpyinfo->menu_event_id == -1) + dpyinfo->menu_event_id = event->menu.menu_event_id; + + goto OTHER; + default: goto OTHER; } diff --git a/src/androidterm.h b/src/androidterm.h index e83e32a5854..9aa09877196 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -132,6 +132,10 @@ struct android_display_info /* The time of the last mouse movement. */ Time last_mouse_movement_time; + + /* ID of the last menu event received. -1 means Emacs is waiting + for a context menu event. */ + int menu_event_id; }; /* Structure representing a single tool (finger or stylus) pressed @@ -407,6 +411,7 @@ 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 void init_androidmenu (void); +extern void syms_of_androidmenu (void); /* Defined in sfntfont-android.c. */ diff --git a/src/emacs.c b/src/emacs.c index 8f5be53aad9..f4973c70610 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -2397,6 +2397,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem #ifdef HAVE_ANDROID syms_of_androidterm (); syms_of_androidfns (); + syms_of_androidmenu (); syms_of_fontset (); #if !defined ANDROID_STUBIFY syms_of_androidfont (); diff --git a/src/xdisp.c b/src/xdisp.c index b9ee102af26..262a823f899 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -34959,7 +34959,8 @@ note_mouse_highlight (struct frame *f, int x, int y) struct buffer *b; /* When a menu is active, don't highlight because this looks odd. */ -#if defined (HAVE_X_WINDOWS) || defined (HAVE_NS) || defined (MSDOS) +#if defined (HAVE_X_WINDOWS) || defined (HAVE_NS) || defined (MSDOS) \ + || defined (HAVE_ANDROID) if (popup_activated ()) return; #endif -- 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/EmacsView.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/EmacsView.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/EmacsView.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 4de6b187933479ce93b6079f42a485e5868f01a5 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 24 Jan 2023 10:34:40 +0800 Subject: Update Android port * .gitignore: Update with new files. Do not ignore std*.in.h. * INSTALL.android: Explain how to build Emacs with external dependencies. * Makefile.in (xcompile, cross): Rename to `cross'. (clean_dirs): Clean cross, not xcompile. * README: Document new directories. * build-aux/ndk-build-helper-1.mk (build_kind, NDK_SO_NAMES): * build-aux/ndk-build-helper-2.mk (build_kind, NDK_SO_NAMES): * build-aux/ndk-build-helper-3.mk (build_kind): * build-aux/ndk-build-helper-4.mk: * build-aux/ndk-build-helper.mk (NDK_BUILD_DIR, my-dir): * build-aux/ndk-module-extract.awk: New files. * configure.ac: Set up libgif, libwebp, and libpng for ndk-build. * cross/ndk-build/Makefile.in (srcdir, NDK_BUILD_ANDROID_MK): * cross/ndk-build/ndk-build-executable.mk: * cross/ndk-build/ndk-build-shared-library.mk (eq, objname): * cross/ndk-build/ndk-build-static-library.mk (eq, objname): * cross/ndk-build/ndk-build.in (NDK_BUILD_MODULES): * cross/ndk-build/ndk-build.mk.in (NDK_BUILD_MODULES) (NDK_BUILD_SHARED): * cross/ndk-build/ndk-clear-vars.mk: * cross/ndk-build/ndk-prebuilt-shared-library.mk: * cross/ndk-build/ndk-prebuilt-static-library.mk: New files. * doc/emacs/android.texi (Android, Android Environment): Document clipboard support on Android. * doc/emacs/emacs.texi (Top): Update menus. * etc/MACHINES: Document Android. * java/AndroidManifest.xml.in: Respect new `--with-android-debug' option. * java/Makefile.in (CROSS_BINS, CROSS_LIBS): Adjust for rename. Include ndk-build.mk.:(emacs.apk-in): Depend on shared libraries. Then, package shared libraries. * java/org/gnu/emacs/EmacsClipboard.java (EmacsClipboard): New class. * java/org/gnu/emacs/EmacsFontDriver.java: Update comment to say this is unused. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function `sendExpose'. * java/org/gnu/emacs/EmacsSdk11Clipboard.java (EmacsSdk11Clipboard): * java/org/gnu/emacs/EmacsSdk8Clipboard.java (EmacsSdk8Clipboard): New classes. * java/org/gnu/emacs/EmacsView.java (EmacsView, handleDirtyBitmap) (onDetachedFromWindow): When window is reattached, expose the frame. * lib/Makefile.in (VPATH): (ALL_CFLAGS): Adjust for rename. * lisp/term/android-win.el (android-clipboard-exists-p) (android-get-clipboard, android-set-clipboard) (android-clipboard-owner-p, android-primary-selection) (android-get-clipboard-1, android-get-primary) (android-selection-bounds, android-encode-select-string) (gui-backend-get-selection, gui-backend-selection-exists-p) (gui-backend-selection-owner-p, gui-backend-set-selection): New functions. * m4/ndk-build.m4: New file. * src/Makefile.in (GIF_CFLAGS, ANDROID_LDFLAGS): New variables. (EMACS_CFLAGS): Add GIF_CFLAGS. Include ndk-build.mk. (libemacs.so): Depend on and link with required libraries. * src/android.c (android_check_compressed_file): New function. (android_open): Work around Android platform bug. (sendExpose): New function. (android_readdir): Set d_type if this is a directory. * src/androidgui.h (enum android_event_type) (struct android_expose_event, union android_event): Add expose events. * src/androidselect.c (struct android_emacs_clipboard) (android_init_emacs_clipboard, Fandroid_clipboard_owner_p) (Fandroid_set_clipboard, Fandroid_get_clipboard) (Fandroid_clipboard_exists_p, init_androidselect) (syms_of_androidselect): New file. * src/androidterm.c (handle_one_android_event): Handle exposures. * src/androidterm.h: Update prototypes. * src/emacs.c (android_emacs_init): Initialize androidselect. --- .gitignore | 64 ++--- INSTALL.android | 334 ++++++++++++++++++++++++- Makefile.in | 4 +- README | 3 + build-aux/ndk-build-helper-1.mk | 92 +++++++ build-aux/ndk-build-helper-2.mk | 87 +++++++ build-aux/ndk-build-helper-3.mk | 28 +++ build-aux/ndk-build-helper-4.mk | 38 +++ build-aux/ndk-build-helper.mk | 52 ++++ build-aux/ndk-module-extract.awk | 64 +++++ configure.ac | 90 +++++-- cross/ndk-build/Makefile.in | 115 +++++++++ cross/ndk-build/ndk-build-executable.mk | 22 ++ cross/ndk-build/ndk-build-shared-library.mk | 83 ++++++ cross/ndk-build/ndk-build-static-library.mk | 83 ++++++ cross/ndk-build/ndk-build.in | 44 ++++ cross/ndk-build/ndk-build.mk.in | 43 ++++ cross/ndk-build/ndk-clear-vars.mk | 47 ++++ cross/ndk-build/ndk-prebuilt-shared-library.mk | 24 ++ cross/ndk-build/ndk-prebuilt-static-library.mk | 24 ++ doc/emacs/android.texi | 29 ++- doc/emacs/emacs.texi | 1 + etc/MACHINES | 8 + java/AndroidManifest.xml.in | 5 +- java/Makefile.in | 30 ++- java/org/gnu/emacs/EmacsClipboard.java | 44 ++++ java/org/gnu/emacs/EmacsFontDriver.java | 3 + java/org/gnu/emacs/EmacsNative.java | 4 + java/org/gnu/emacs/EmacsSdk11Clipboard.java | 156 ++++++++++++ java/org/gnu/emacs/EmacsSdk8Clipboard.java | 116 +++++++++ java/org/gnu/emacs/EmacsView.java | 23 ++ lib/Makefile.in | 4 +- lisp/term/android-win.el | 106 ++++++++ m4/ndk-build.m4 | 213 ++++++++++++++++ src/Makefile.in | 19 +- src/android.c | 82 +++++- src/androidgui.h | 11 + src/androidselect.c | 249 ++++++++++++++++++ src/androidterm.c | 22 ++ src/androidterm.h | 9 + src/emacs.c | 2 + 41 files changed, 2406 insertions(+), 71 deletions(-) create mode 100644 build-aux/ndk-build-helper-1.mk create mode 100644 build-aux/ndk-build-helper-2.mk create mode 100644 build-aux/ndk-build-helper-3.mk create mode 100644 build-aux/ndk-build-helper-4.mk create mode 100644 build-aux/ndk-build-helper.mk create mode 100644 build-aux/ndk-module-extract.awk create mode 100644 cross/ndk-build/Makefile.in create mode 100644 cross/ndk-build/ndk-build-executable.mk create mode 100644 cross/ndk-build/ndk-build-shared-library.mk create mode 100644 cross/ndk-build/ndk-build-static-library.mk create mode 100644 cross/ndk-build/ndk-build.in create mode 100644 cross/ndk-build/ndk-build.mk.in create mode 100644 cross/ndk-build/ndk-clear-vars.mk create mode 100644 cross/ndk-build/ndk-prebuilt-shared-library.mk create mode 100644 cross/ndk-build/ndk-prebuilt-static-library.mk create mode 100644 java/org/gnu/emacs/EmacsClipboard.java create mode 100644 java/org/gnu/emacs/EmacsSdk11Clipboard.java create mode 100644 java/org/gnu/emacs/EmacsSdk8Clipboard.java create mode 100644 m4/ndk-build.m4 create mode 100644 src/androidselect.c (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/.gitignore b/.gitignore index 6494e4e8f39..6b624767c66 100644 --- a/.gitignore +++ b/.gitignore @@ -96,36 +96,40 @@ src/lisp.mk src/verbose.mk # Stuff built during cross compilation -xcompile/lib/alloca.h -xcompile/lib/assert.h -xcompile/lib/byteswap.h -xcompile/lib/dirent.h -xcompile/lib/errno.h -xcompile/lib/execinfo.h -xcompile/lib/fcntl.h -xcompile/lib/getopt.h -xcompile/lib/getopt-cdefs.h -xcompile/lib/gmp.h -xcompile/lib/ieee754.h -xcompile/lib/inttypes.h -xcompile/lib/libgnu.a -xcompile/lib/limits.h -xcompile/lib/malloc/*.gl.h -xcompile/lib/signal.h -xcompile/lib/std*.h -xcompile/!lib/std*.in.h -xcompile/!lib/stdio-impl.h -xcompile/lib/string.h -xcompile/lib/sys/ -xcompile/lib/time.h -xcompile/lib/unistd.h -xcompile/lib/config.h -xcompile/lib/gnulib.mk -xcompile/src/* -xcompile/lib-src/* -xcompile/sys/* -xcompile/config.status -xcompile/*.bak +cross/lib/alloca.h +cross/lib/assert.h +cross/lib/byteswap.h +cross/lib/dirent.h +cross/lib/errno.h +cross/lib/execinfo.h +cross/lib/fcntl.h +cross/lib/getopt.h +cross/lib/getopt-cdefs.h +cross/lib/gmp.h +cross/lib/ieee754.h +cross/lib/inttypes.h +cross/lib/libgnu.a +cross/lib/limits.h +cross/lib/malloc/*.gl.h +cross/lib/signal.h +cross/lib/std*.h +!cross/lib/std*.in.h +!cross/lib/stdio-impl.h +cross/lib/string.h +cross/lib/sys/ +cross/lib/time.h +cross/lib/unistd.h +cross/lib/config.h +cross/lib/gnulib.mk +cross/src/* +cross/lib-src/* +cross/sys/* +cross/config.status +cross/*.bak + +cross/ndk-build/Makefile +cross/ndk-build/ndk-build.mk +cross/ndk-build/*.o # Lisp-level sources built by 'make'. *cus-load.el diff --git a/INSTALL.android b/INSTALL.android index 63c856fe56c..9abea74a55d 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -62,7 +62,7 @@ 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 +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: @@ -94,7 +94,339 @@ 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. +DEBUG AND RELEASE BUILDS + +Android makes a distinction between ``debug'' and ``release'' builds +of applications. With ``release'' builds, the system will apply +stronger optimizations to the application at the cost of being unable +to debug them with the steps in etc/DEBUG. + +Emacs is built as a debuggable package by default, but: + + ./configure --without-android-debug + +will create a release build of Emacs instead. This may be useful when +running Emacs on resource constrained machines. + +If you are building an Emacs package for redistribution, we urge you +to provide both debug and release versions. + +BUILDING WITH THIRD PARTY LIBRARIES + +The Android NDK does not support the usual ways of locating third +party libraries, especially not via `pkg-config'. Instead, it uses +its own system called `ndk-build'. The one exception to this rule is +zlib, which is considered a part of the Android OS itself and is +available on all devices running Android. + +Android also requires that each application include its own +dependencies, as the system makes no guarantee about the existence of +any particular library. + +Emacs is not built with the `ndk-build' system. Instead, it is built +with Autoconf and Make. + +However, it supports building and including dependencies which use the +similarly Make-based `ndk-build' system. + +To use dependencies built through `ndk-build', you must specify a list +of directories within which Emacs will search for ``Android.mk'' +files, like so: + + ./configure "--with-ndk-path=directory1 directory2" + +Emacs will then read the ``Android.mk'' file in each directory, and +automatically build and use those modules. + +Google, Inc. has adapted many common Emacs dependencies to use the +`ndk-build' system. Here is a non-exhaustive list of what is known to +work: + + libpng - https://android.googlesource.com/platform/external/libpng + libwebp - https://android.googlesource.com/platform/external/webp + giflib - https://android.googlesource.com/platform/external/giflib + (You must add LOCAL_EXPORT_CFLAGS := -I$(LOCAL_PATH) before + its Android.mk includes $(BUILD_STATIC_LIBRARY)) + +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. + +NDK BUILD SYSTEM IMPLEMENTATION + +Emacs implements ndk-build itself, because the version that comes with +the Android NDK is not easy to use from another Makefile, and keeps +accumulating incompatible changes. + +The Emacs implementation of ndk-build consists of one m4 file: + + m4/ndk-build.m4 + +four Makefiles in build-aux, run during configure: + + build-aux/ndk-build-helper-1.mk + build-aux/ndk-build-helper-2.mk + build-aux/ndk-build-helper-3.mk + build-aux/ndk-build-helper.mk + +one awk script in build-awx, run during configure: + + build-aux/ndk-module-extract.awk + +six Makefiles in cross/ndk-build, + + cross/ndk-build/ndk-build-shared-library.mk + cross/ndk-build/ndk-build-static-library.mk + cross/ndk-build/ndk-build-executable.mk + cross/ndk-build/ndk-clear-vars.mk + cross/ndk-build/ndk-prebuilt-shared-library.mk + cross/ndk-build/ndk-prebuilt-static-library.mk + +and finally, two more Makefiles in cross/ndk-build, generated by +configure: + + cross/ndk-build/Makefile (generated from cross/ndk-build/Makefile.in) + cross/ndk-build/ndk-build.mk (generated from cross/ndk-build/ndk-build.mk.in) + +m4/ndk-build.m4 is a collection of macros which are used by the +configure script to set up the ndk-build system, look for modules, add +the appropriate options to LIBS and CFLAGS, and generate the Makefiles +necessary to build the rest of Emacs. + +Immediately after determining the list of directories in which to look +for ``Android.mk'' files, the version and type of Android system being +built for, configure calls: + + ndk_INIT([$android_abi], [$ANDROID_SDK], [cross/ndk-build]) + +This expands to a sequence of shell script that enumerates all of the +Android.mk files specified in "$with_ndk_path", sets up some shell +functions used by the rest of the ndk-build code run by the configure +script, and teaches the ndk-build system that the Makefiles to be +generated are found in the directory "cross/ndk-build/Makefile". + +When configure is cross-compiling for Android, the macro +EMACS_CHECK_MODULES will expand to the macro ndk_CHECK_MODULES, +instead of pkg-config.m4's PKG_CHECK_MODULES. Thus, the following +code: + + EMACS_CHECK_MODULES([PNG], [libpng >= 1.0.0]) + +will actually expand to: + + ndk_CHECK_MODULES([PNG], [libpng >= 1.0.0], [HAVE_PNG=yes], + [HAVE_PNG=no]) + +which in turn expands to a sequence shell script that first invokes: + + make -f build-aux/ndk-build-helper.mk + +for each ``Android.mk'' file found by ndk_INIT, with the following +variables given to Make: + + EMACS_SRCDIR=. # the source directory (in which configure is running) + EMACS_ABI=$ndk_ABI # this is the $android_abi given to ndk_INIT + ANDROID_MAKEFILE="/opt/android/libpng/Android.mk" + ANDROID_MODULE_DIRECTORY="/opt/android/libpng" + NDK_BUILD_DIR="$ndk_DIR" # this is the directory given as to ndk_INIT + +build-aux/ndk-build-helper.mk will then evaluate the contents +$(ANDROID_MAKEFILE), the ``Android.mk'' file, for the first time. The +purpose of this evaluation is to establish a list of packages (or +modules) provided by the ``Android.mk'' file, and the corresponding +Makefile targets and compiler and linker flags required to build and +link to those tagets. + +Before doing so, build-aux/ndk-build-helper.mk will define several +variables and functions required by all ``Android.mk'' files. The +most important of these are: + + my-dir # the directory containing the Android.mk file. + BUILD_SHARED_LIBRARY # build-aux/ndk-build-helper-1.mk + BUILD_STATIC_LIBRARY # build-aux/ndk-build-helper-2.mk + BUILD_EXECUTABLE # build-aux/ndk-build-helper-3.mk + CLEAR_VARS # build-aux/ndk-build-helper-4.mk + +Then, ``Android.mk'' will include $(CLEAN_VARS) (to clear variables +previously set), set several variables describing each module to the +ndk-build system, and include one of $(BUILD_SHARED_LIBRARY), +$(BUILD_STATIC_LIBRARY) and $(BUILD_EXECUTABLE). + +Each one of those three scripts will then read from the variables set +by ``Android.mk'', resolve dependencies, and print out some text +describing the module to Emacs. For example, the shared library +module "libpng" results in the following text being printed: + +Building shared +libpng +/opt/android/libpng/png.c /opt/android/libpng/pngerror.c /opt/android/libpng/pngget.c /opt/android/libpng/pngmem.c /opt/android/libpng/pngpread.c /opt/android/libpng/pngread.c /opt/android/libpng/pngrio.c /opt/android/libpng/pngrtran.c /opt/android/libpng/pngrutil.c /opt/android/libpng/pngset.c /opt/android/libpng/pngtrans.c /opt/android/libpng/pngwio.c /opt/android/libpng/pngwrite.c /opt/android/libpng/pngwtran.c /opt/android/libpng/pngwutil.c +-I/opt/android/libpng + + -L/opt/emacs/cross/ndk-build -l:libpng_emacs.so +libpng_emacs.so +End + +The output is arranged as follows: + + - The first line consists of the word ``Building'', followed by + either ``shared'', ``static'', or ``executable'', depending on + what type of module being built. + + - The second line consists of the name of the module currently being + built. + + - The third line consists of all of the source code files comprising + the module. + + - The fourth line consists of the text that has to be added to + CFLAGS in order to find the includes associated with the module. + + - The fifth line consists of the text that has to be added to LIBS + in order to link with this module and all of its dependencies. + + - The sixth line consists of the Make targets (more on this later) + that will build the final shared object or library archive of this + module, along with all of its dependencies. + +The output from Make is given to an awk script, +build-aux/ndk-module-extract.awk. This is responsible for parsing the +that output and filtering out modules other than what is being built: + + awk -f build-aux/ndk-module-extract.awk MODULE=libpng + +eventually generating this section of shell script: + +module_name=libpng +module_kind=shared +module_src="/opt/android/libpng/png.c /opt/android/libpng/pngerror.c /opt/android/libpng/pngget.c /opt/android/libpng/pngmem.c /opt/android/libpng/pngpread.c /opt/android/libpng/pngread.c /opt/android/libpng/pngrio.c /opt/android/libpng/pngrtran.c /opt/android/libpng/pngrutil.c /opt/android/libpng/pngset.c /opt/android/libpng/pngtrans.c /opt/android/libpng/pngwio.c /opt/android/libpng/pngwrite.c /opt/android/libpng/pngwtran.c /opt/android/libpng/pngwutil.c" +module_includes="-I/opt/android/libpng" +module_cflags="" +module_ldflags=" -L/opt/emacs/cross/ndk-build -l:libpng_emacs.so" +module_target="libpng_emacs.so" + +which is then evaluated by `configure'. Once the variable +`module_name' is set, configure apends the remaining +$(module_includes), $(module_cflags) and $(module_ldflags) to the +module's CFLAGS and LIBS variables, and appends the list of Makefile +targets specified to the variable NDK_BUILD_MODULES. + +Finally, immediately before generating src/Makefile.android, configure +expands: + + ndk_CONFIG_FILES + +to generate $ndk_DIR/Makefile and $ndk_DIR/ndk-build.mk. + +Now, the $ndk_DIR directory is set up to build all modules upon which +depends, and $ndk_DIR/ndk-build.mk includes a list of files required +to link Emacs, along with the rules to chdir into $ndk_DIR in order to +build them. + +$ndk_DIR/ndk-build.mk is included by cross/src/Makefile +(Makefile.android) and java/Makefile. It defines three different +variables: + + NDK_BUILD_MODULES the file names of all modules to be built. + NDK_BUILD_STATIC absolute names of all library archives + to be built. + NDK_BUILD_SHARED absolute names of all shared libraries to + be built. + +and then proceeds to define rules to build each of the modules in +$(NDK_BUILD_MODULES). + +cross/src/Makefile arranges to have all dependencies of Emacs not +already built built before linking ``libemacs.so'' with them. + +java/Makefile additionally arranges to have all shared object +dependencies built before the application package is built, which is +normally redundant because they should have already been built before +linking ``libemacs.so''. + +Building the modules is performed through $ndk_DIR/Makefile, which +contains the actual implementation of the ``ndk-build'' build system. +First, it defines certain variables constant within the ``ndk-build'' +build system, such as the files included by ``Android.mk'' to build +shared or static libraries, and CLEAR_VARS. The most important of +these are: + + CLEAR_VARS cross/ndk-build/ndk-clear-vars.mk + BUILD_EXECUTABLE cross/ndk-build/ndk-build-executable.mk + BUILD_SHARED_LIBRARY cross/ndk-build/ndk-build-shared-library.mk + BUILD_STATIC_LIBRARY cross/ndk-build/ndk-build-static-library.mk + PREBUILT_SHARED_LIBRARY cross/ndk-build/ndk-prebuilt-shared-library.mk + PREBUILT_STATIC_LIBRARY cross/ndk-build/ndk-prebuilt-static-library.mk + +Then, it loads each Emacs dependency's ``Android.mk'' file. For each +module defined there, ``Android.mk'' includes $(CLEAR_VARS) to unset +all variables specific to each module, and then includes +$(BUILD_SHARED_LIBRARY) or $(BUILD_STATIC_LIBRARY) for each shared or +static library module. + +This results in cross/ndk-build/ndk-build-shared-library.mk or +cross/ndk-build/ndk-build-static-library being included, just like the +Makefiles in build-aux were inside the configure script. + +Each one of those two scripts then defines rules to build all of the +object files associated with the module, and then link or archive +them. The name under which the module is linked is the same as the +Make target found on the sixth line of output from +build-aux/ndk-build-helper.mk. + +However, none of the Makefiles in +cross/ndk-build/ndk-build-shared-library.mk perform any kind of +dependency resolution! Instead, they only define rules to build +individual modules, leaving dependency resolution up to the Makefiles +in the `build-aux' directory. + +libpng is a very simple module, providing only a single shared object +module. This module is named libpng_emacs.so and is eventually built +and packaged into the library directory of the Emacs application +package. Now, let us look at a more complex module, libwebp: + + +When built with libwebp, Emacs depends on a single library, +libwebpdemux. This library is named ``libwebpdemux'' on Unix systems, +and that is the name by which it is found with pkg-config. + +However, the library's module is only named ``webpdemux'' on Android. +When ndk_CHECK_MODULES begins to look for a module, it first tries to +see if its name is found in the variable `ndk_package_map', which was +set inside ndk_INIT. In this case, it finds the following word: + + libwebpdemux:webpdemux + +and immediately replaces ``libwebpdemux'' with ``webpdemux''. + +Then, it locates the ``Android.mk'' file containing a static library +module named webpdemux and gives the output from +build-aux/ndk-build-helper.mk to the awk script, resulting in: + +module_name=webpdemux +module_kind=static +module_src="/opt/android/webp/src/demux/anim_decode.c /opt/android/webp/src/demux/demux.c" +module_includes="-I/opt/android/webp/src" +module_cflags="" +module_ldflags=" cross/ndk-build/libwebpdemux.a cross/ndk-build/libwebp.a cross/ndk-build/libwebpdecoder_static.a " +module_target="libwebpdemux.a libwebp.a libwebpdecoder_static.a" + +The attentive reader will notice that in addition to the +``libwebpdemux.a'' archive associated with the ``webpdemux'' library, +Emacs has been made to link with two additional libraries. This is +because the ``webpdemux'' module specifies a dependency on the +``webp'' module (defined in the same Android.mk). +build-aux/ndk-build-helper.mk resolved that dependency, noticing that +it in turn specified another dependency on ``webpdecoder_static'', +which in turn was added to the linker command line and list of targets +to build. + +As a result, all three dependencies will be built and linked to Emacs, +instead of just the single ``webpdemux'' dependency that was +specified. + + + This file is part of GNU Emacs. GNU Emacs is free software: you can redistribute it and/or modify diff --git a/Makefile.in b/Makefile.in index 0090b858228..c4b346128dc 100644 --- a/Makefile.in +++ b/Makefile.in @@ -539,7 +539,7 @@ lib lib-src lisp nt: Makefile java: lisp info $(MAKE) -C $@ all -xcompile: src +cross: src $(MAKE) -C $@ all trampolines: src lisp @@ -1012,7 +1012,7 @@ mostlyclean: $(mostlyclean_dirs:=_mostlyclean) ### with them. ### ### Delete '.dvi' files here if they are not part of the distribution. -clean_dirs = $(mostlyclean_dirs) java xcompile nextstep admin/charsets \ +clean_dirs = $(mostlyclean_dirs) java cross nextstep admin/charsets \ admin/unidata $(foreach dir,$(clean_dirs),$(eval $(call submake_template,$(dir),clean))) diff --git a/README b/README index 19d5c96e348..c8a3d485c28 100644 --- a/README +++ b/README @@ -95,6 +95,9 @@ There are several subdirectories: 'admin' holds files used by Emacs developers, and Unicode data files. 'build-aux' holds auxiliary files used during the build. 'm4' holds Autoconf macros used for generating the configure script. +'java' holds the Java code for the Emacs port to Android. +'cross' holds Makefiles and an additional copy of gnulib used to build + Emacs for Android devices. Building Emacs on non-Posix platforms requires tools that aren't part of the standard distribution of the OS. The platform-specific README diff --git a/build-aux/ndk-build-helper-1.mk b/build-aux/ndk-build-helper-1.mk new file mode 100644 index 00000000000..fb8e4da2f0a --- /dev/null +++ b/build-aux/ndk-build-helper-1.mk @@ -0,0 +1,92 @@ +# ndk-build-helper-1.mk -- Helper for ndk-build.m4. +# 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 . + +# Print out information now defined. Important details include: +# - list of source files to compile. +# - module export include directories. +# - module export CFLAGS. +# - module export LDFLAGS. +# - module name. + +build_kind = shared +NDK_SO_NAMES = +NDK_A_NAMES = + +# Record this module's dependencies. This information is used later +# on to recurse over libraries. +NDK_$(LOCAL_MODULE)_STATIC_LIBRARIES := $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES) +NDK_$(LOCAL_MODULE)_SHARED_LIBRARIES := $(LOCAL_SHARED_LIBRARIES) + +$(info Building $(build_kind)) +$(info $(LOCAL_MODULE)) +$(info $(addprefix $(ANDROID_MODULE_DIRECTORY)?,$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) + +$(info $(foreach dir,$(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES),-I$(dir))) +$(info $(LOCAL_EXPORT_CFLAGS)) +ifeq ($(LOCAL_MODULE_FILENAME),) +ifeq ($(findstring lib,$(LOCAL_MODULE)),lib) +NDK_SO_NAMES = $(LOCAL_MODULE)_emacs.so +else +NDK_SO_NAMES = lib$(LOCAL_MODULE)_emacs.so +endif +else +NDK_SO_NAMES = $(LOCAL_MODULE_FILENAME).so +endif + +define add-a-name +ifeq ($(findstring lib,$(1)),lib) +NDK_A_NAME = $(1).a +else +NDK_A_NAME = lib$(1).a +endif + +ifeq ($$(NDK_A_NAMES:$$(NDK_A_NAME)=),$$(NDK_A_NAMES)) +NDK_A_NAMES := $$(NDK_A_NAMES) $$(NDK_A_NAME) + +# Now recurse over this module's dependencies. +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-a-name,$$(module)))) +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module)))) +endif +endef + +define add-so-name +ifeq ($(findstring lib,$(1)),lib) +NDK_SO_NAME = $(1)_emacs.so +else +NDK_SO_NAME = lib$(1)_emacs.so +endif + +ifeq ($$(NDK_SO_NAMES:$$(NDK_SO_NAME)=),$$(NDK_SO_NAMES)) +NDK_SO_NAMES := $$(NDK_SO_NAMES) $$(NDK_SO_NAME) + +# Now recurse over this module's dependencies. +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-a-name,$$(module)))) +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module)))) +endif +endef + +# Resolve additional dependencies based on LOCAL_STATIC_LIBRARIES and +# LOCAL_SHARED_LIBRARIES. + +SYSTEM_LIBRARIES = z libz libc c + +$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_STATIC_LIBRARIES)),$(eval $(call add-a-name,$(module)))) +$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES)),$(eval $(call add-so-name,$(module)))) + +$(info $(LOCAL_EXPORT_LDFLAGS) $(abspath $(addprefix $(NDK_BUILD_DIR)/,$(NDK_A_NAMES))) -L$(abspath $(NDK_BUILD_DIR)) $(foreach soname,$(NDK_SO_NAMES),-l:$(soname))) +$(info $(NDK_SO_NAMES)) +$(info End) diff --git a/build-aux/ndk-build-helper-2.mk b/build-aux/ndk-build-helper-2.mk new file mode 100644 index 00000000000..82c1d53c7d0 --- /dev/null +++ b/build-aux/ndk-build-helper-2.mk @@ -0,0 +1,87 @@ +# ndk-build-helper-2.mk -- Helper for ndk-build.m4. +# 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 . + +# Say a static library is being built +build_kind = static +NDK_SO_NAMES = +NDK_A_NAMES = + +# Record this module's dependencies. This information is used later +# on to recurse over libraries. +NDK_$(LOCAL_MODULE)_STATIC_LIBRARIES := $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES) +NDK_$(LOCAL_MODULE)_SHARED_LIBRARIES := $(LOCAL_SHARED_LIBRARIES) + +$(info Building $(build_kind)) +$(info $(LOCAL_MODULE)) +$(info $(addprefix $(ANDROID_MODULE_DIRECTORY)/,$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) + +$(info $(foreach dir,$(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES),-I$(dir))) +$(info $(LOCAL_EXPORT_CFLAGS)) +ifeq ($(LOCAL_MODULE_FILENAME),) + +ifeq ($(findstring lib,$(LOCAL_MODULE)),lib) +NDK_A_NAMES = $(LOCAL_MODULE).a +else +NDK_A_NAMES = lib$(LOCAL_MODULE).a +endif +else +NDK_A_NAMES = $(LOCAL_MODULE_FILENAME).a +endif + +define add-a-name +ifeq ($(findstring lib,$(1)),lib) +NDK_A_NAME = $(1).a +else +NDK_A_NAME = lib$(1).a +endif + +ifeq ($$(NDK_A_NAMES:$$(NDK_A_NAME)=),$$(NDK_A_NAMES)) +NDK_A_NAMES := $$(NDK_A_NAMES) $$(NDK_A_NAME) + +# Now recurse over this module's dependencies. +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-a-name,$$(module)))) +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module)))) +endif +endef + +define add-so-name +ifeq ($(findstring lib,$(1)),lib) +NDK_SO_NAME = $(1)_emacs.so +else +NDK_SO_NAME = lib$(1)_emacs.so +endif + +ifeq ($$(NDK_SO_NAMES:$$(NDK_SO_NAME)=),$$(NDK_SO_NAMES)) +NDK_SO_NAMES := $$(NDK_SO_NAMES) $$(NDK_SO_NAME) + +# Now recurse over this module's dependencies. +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_STATIC_LIBRARIES)),$$(eval $$(call add-a-name,$$(module)))) +$$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARIES)),$$(eval $$(call add-so-name,$$(module)))) +endif +endef + +# Resolve additional dependencies based on LOCAL_STATIC_LIBRARIES and +# LOCAL_SHARED_LIBRARIES. + +SYSTEM_LIBRARIES = z libz + +$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_STATIC_LIBRARIES)),$(eval $(call add-a-name,$(module)))) +$(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_SHARED_LIBRARIES)),$(eval $(call add-so-name,$(module)))) + +$(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 End) diff --git a/build-aux/ndk-build-helper-3.mk b/build-aux/ndk-build-helper-3.mk new file mode 100644 index 00000000000..4d0358d4f77 --- /dev/null +++ b/build-aux/ndk-build-helper-3.mk @@ -0,0 +1,28 @@ +# ndk-build-helper-3.mk -- Helper for ndk-build.m4. +# 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 . + +# Say a static library is being built +build_kind = executable + +$(info Building $(build_kind)) +$(info $(LOCAL_MODULE)) +$(info $(addprefix $(ANDROID_MODULE_DIRECTORY),$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) + +$(info $(foreach dir,$(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES),-I$(dir))) +$(info $(LOCAL_EXPORT_CFLAGS)) +$(info $(LOCAL_EXPORT_LDFLAGS)) +$(info End) diff --git a/build-aux/ndk-build-helper-4.mk b/build-aux/ndk-build-helper-4.mk new file mode 100644 index 00000000000..e1024f0a243 --- /dev/null +++ b/build-aux/ndk-build-helper-4.mk @@ -0,0 +1,38 @@ +# 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 . + +undefine LOCAL_MODULE +undefine LOCAL_MODULE_FILENAME +undefine LOCAL_SRC_FILES +undefine LOCAL_CPP_EXTENSION +undefine LOCAL_CPP_FEATURES +undefine LOCAL_C_INCLUDES +undefine LOCAL_CFLAGS +undefine LOCAL_CPPFLAGS +undefine LOCAL_STATIC_LIBRARIES +undefine LOCAL_SHARED_LIBRARIES +undefine LOCAL_WHOLE_STATIC_LIBRARIES +undefine LOCAL_LDLIBS +undefine LOCAL_LDFLAGS +undefine LOCAL_ALLOW_UNDEFINED_SYMBOLS +undefine LOCAL_ARM_MODE +undefine LOCAL_ARM_NEON +undefine LOCAL_DISABLE_FORMAT_STRING_CHECKS +undefine LOCAL_EXPORT_CFLAGS +undefine LOCAL_EXPORT_CPPFLAGS +undefine LOCAL_EXPORT_C_INCLUDES +undefine LOCAL_EXPORT_LDFLAGS +undefine LOCAL_EXPORT_LDLIBS diff --git a/build-aux/ndk-build-helper.mk b/build-aux/ndk-build-helper.mk new file mode 100644 index 00000000000..beb32cae3f1 --- /dev/null +++ b/build-aux/ndk-build-helper.mk @@ -0,0 +1,52 @@ +# ndk-build-helper.mk -- Helper for ndk-build.m4. +# 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 . + +# This Makefile sets up enough to parse an Android-style Android.mk +# file and return useful information about its contents. + +# See the text under ``NDK BUILD SYSTEM IMPLEMENTATION'' in +# INSTALL.android for more details. + +# Make NDK_BUILD_DIR absolute. +NDK_BUILD_DIR := $(absname $(NDK_BUILD_DIR)) + +# my-dir is a function that returns the Android module directory. +my-dir = $(ANDROID_MODULE_DIRECTORY) + +# all-subdir-makefiles is a function which returns all Android.mk +# files within this directory. +all-subdir-makefiles = $(shell find . -name "Android.mk") + +# These functions are not implemented. +parent-makefile = +grand-parent-makefile = +import-module = + +# Print out module information every time BUILD_SHARED_LIBRARY is +# called. + +BUILD_SHARED_LIBRARY=$(EMACS_SRCDIR)/build-aux/ndk-build-helper-1.mk +BUILD_STATIC_LIBRARY=$(EMACS_SRCDIR)/build-aux/ndk-build-helper-2.mk +BUILD_EXECUTABLE=$(EMACS_SRCDIR)/build-aux/ndk-build-helper-3.mk +CLEAR_VARS=$(EMACS_SRCDIR)/build-aux/ndk-build-helper-4.mk + +# Now include Android.mk. + +include $(ANDROID_MAKEFILE) + +# Dummy target. +all: diff --git a/build-aux/ndk-module-extract.awk b/build-aux/ndk-module-extract.awk new file mode 100644 index 00000000000..eaea3e96dd8 --- /dev/null +++ b/build-aux/ndk-module-extract.awk @@ -0,0 +1,64 @@ +/^Building.+$/ { + kind = $2 +} + +// { + if (!match ($0, /^End$/) && !match ($0, /^Building.+$/)) + { + if (kind) + { + if (ldflags_found) + target = $0 + else if (cflags_found) + { + ldflags = $0 + ldflags_found = 1 + } + else if (includes_found) + { + cflags = $0 + cflags_found = 1 + } + else if (src_found) + { + includes = $0 + includes_found = 1 + } + else if (name_found) + { + src = $0 + src_found = 1; + } + else + { + name = $0 + name_found = 1 + } + } + } +} + +/^End$/ { + if (name == MODULE && (kind == "shared" || kind == "static")) + { + printf "module_name=%s\n", name + printf "module_kind=%s\n", kind + printf "module_src=\"%s\"\n", src + printf "module_includes=\"%s\"\n", includes + printf "module_cflags=\"%s\"\n", cflags + printf "module_ldflags=\"%s\"\n", ldflags + printf "module_target=\"%s\"\n", target + } + + src = "" + name = "" + kind = "" + includes = "" + cflags = "" + ldflags = "" + name_found = "" + src_found = "" + includes_found = "" + cflags_found = "" + ldflags_found = "" +} diff --git a/configure.ac b/configure.ac index bdcce8d8694..9df4a6a073f 100644 --- a/configure.ac +++ b/configure.ac @@ -166,6 +166,13 @@ fi AC_CANONICAL_HOST AC_CANONICAL_BUILD +if test "$XCONFIGURE" = "android"; then + # Initialize the Android NDK build system. Make sure to use the + # passed through NDK path. + with_ndk_path="$android_ndk_path" + ndk_INIT([$android_abi], [$ANDROID_SDK], [cross/ndk-build]) +fi + case $host in *-mingw*) @@ -544,6 +551,7 @@ OPTION_DEFAULT_OFF([cygwin32-native-compilation],[use native compilation on 32-b OPTION_DEFAULT_ON([xinput2],[don't use version 2 of the X Input Extension for input]) OPTION_DEFAULT_OFF([small-ja-dic],[generate a smaller-size Japanese dictionary]) OPTION_DEFAULT_OFF([android],[cross-compile Android application package]) +OPTION_DEFAULT_ON([android-debug],[don't build Emacs as a debug package on Android]) AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB], [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, w32, no)])], @@ -1033,8 +1041,14 @@ package will likely install on older systems but crash on startup.]) mv -f confdefs.h _confdefs.h mv -f config.log _config.log + # Figure out what --with-FOO options to pass through. + passthrough="$passthrough --with-png=$with_png" + passthrough="$passthrough --with-webp=$with_webp" + passthrough="$passthrough --with-gif=$with_gif" + AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" \ - ANDROID_SDK="$android_sdk" $0], [], + ANDROID_SDK="$android_sdk" android_abi=$android_abi \ + android_ndk_path="$with_ndk_path" $0 $passthrough], [], [AC_MSG_ERROR([Failed to cross-configure Emacs for android.])]) # Now set ANDROID to yes. @@ -1048,6 +1062,14 @@ package will likely install on older systems but crash on startup.]) AC_MSG_NOTICE([Generating src/config.h.android]) mv -f src/config.h src/config.h.android + # Tell AndroidManifest.xml whether or not Emacs should be built + # debug. + ANDROID_DEBUGGABLE=false + if test "$with_android_debug" = "yes"; then + ANDROID_DEBUGGABLE=true + fi + AC_SUBST([ANDROID_DEBUGGABLE]) + # Move confdefs.h back now that the recursive call to configure is # complete. mv -f _confdefs.h confdefs.h @@ -1083,15 +1105,21 @@ if test "$ANDROID" = "yes"; then with_xpm=no with_jpeg=no with_tiff=no - with_gif=no - with_png=no + + # Some of these dependencies are now supported within Android, so + # they can be enabled. + if test "$XCONFIGURE" != "android"; then + with_png=no + with_webp=no + with_gif=no + fi + + with_xml2=no with_rsvg=no - with_webp=no with_sqlite3=no with_lcms2=no with_libsystemd=no with_cairo=no - with_xml2=no with_imagemagick=no with_json=no with_tree_sitter=no @@ -2218,10 +2246,13 @@ dnl EMACS_CHECK_MODULES accepts optional 3rd and 4th arguments that dnl can take the place of the default HAVE_GSTUFF=yes and HAVE_GSTUFF=no dnl actions. AC_DEFUN([EMACS_CHECK_MODULES], - [PKG_CHECK_MODULES([$1], [$2], - [$1_CFLAGS=`AS_ECHO(["$$1_CFLAGS"]) | sed -e "$edit_cflags"` - m4_default([$3], [HAVE_$1=yes])], - [m4_default([$4], [HAVE_$1=no])])]) + [AS_IF([test -n "$ndk_INITIALIZED"], + [ndk_CHECK_MODULES([$1], [$2], m4_default([$3], [HAVE_$1=yes]), + m4_default([$4],[HAVE_$1=no]))], + [PKG_CHECK_MODULES([$1], [$2], + [$1_CFLAGS=`AS_ECHO(["$$1_CFLAGS"]) | sed -e "$edit_cflags"` + m4_default([$3], [HAVE_$1=yes])], + [m4_default([$4], [HAVE_$1=no])])])]) HAVE_SOUND=no if test "${with_sound}" != "no"; then @@ -2394,6 +2425,7 @@ window_system=none ANDROID_OBJ= ANDROID_LIBS= ANDROID_CFLAGS= +REALLY_ANDROID= CM_OBJ="cm.o" if test "${ANDROID}" = "yes"; then @@ -2423,18 +2455,31 @@ for Android, but all API calls need to be stubbed out]) # Link with libraries required for Android support. ANDROID_LIBS="-landroid -llog -ljnigraphics" + # This is required to make the system load emacs.apk's libpng + # (among others) instead of the system's own. But it doesn't work + # on all Android versions yet, so for now just suffix shared + # libraries with _emacs. + # ANDROID_LDFLAGS="-Wl,-rpath,'\$\$ORIGIN'" + # 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" + # Build androidselect.o. + ANDROID_OBJ="$ANDROID_OBJ androidselect.o" + # Check for some functions not always present in the NDK. AC_CHECK_DECLS([android_get_device_api_level]) + + # Say this build is really for Android. + REALLY_ANDROID=yes fi fi AC_SUBST(ANDROID) AC_SUBST(ANDROID_OBJ) AC_SUBST(ANDROID_LIBS) +AC_SUBST(ANDROID_LDFLAGS) AC_SUBST(ANDROID_CFLAGS) if test "${with_pgtk}" = "yes"; then @@ -3265,7 +3310,8 @@ HAVE_WEBP=no if test "${with_webp}" != "no"; then if test "${HAVE_X11}" = "yes" || test "${opsys}" = "mingw32" \ || test "${HAVE_W32}" = "yes" || test "${HAVE_NS}" = "yes" \ - || test "${HAVE_BE_APP}" = "yes" || test "${HAVE_PGTK}" = "yes"; then + || test "${HAVE_BE_APP}" = "yes" || test "${HAVE_PGTK}" = "yes" \ + || test "${REALLY_ANDROID}" = "yes"; then WEBP_REQUIRED=0.6.0 WEBP_MODULE="libwebpdemux >= $WEBP_REQUIRED" @@ -4506,7 +4552,8 @@ HAVE_JPEG=no LIBJPEG= if test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \ || test "${HAVE_NS}" = "yes" || test "${HAVE_BE_APP}" = "yes" \ - || test "$window_system" = "pgtk"; then + || test "$window_system" = "pgtk" \ + || test "${REALLY_ANDROID}" = "yes"; then if test "${with_jpeg}" != "no"; then AC_CACHE_CHECK([for jpeglib 6b or later], [emacs_cv_jpeglib], @@ -4852,7 +4899,8 @@ if test "${with_png}" != no; then AC_CHECK_HEADER([png.h], [HAVE_PNG=yes]) elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \ || test "${HAVE_NS}" = "yes" || test "${HAVE_BE_APP}" = "yes" \ - || test "$window_system" = "pgtk"; then + || test "$window_system" = "pgtk" \ + || test "${REALLY_ANDROID}" = "yes"; then EMACS_CHECK_MODULES([PNG], [libpng >= 1.0.0]) if test $HAVE_PNG = yes; then LIBPNG=$PNG_LIBS @@ -4951,6 +4999,7 @@ AC_SUBST([LIBTIFF]) ### Use -lgif or -lungif if available, unless '--with-gif=no'. ### mingw32 doesn't use -lgif/-lungif, since it loads the library dynamically. HAVE_GIF=no +GIF_CFLAGS= LIBGIF= if test "${opsys}" = "mingw32"; then if test "${with_gif}" != "no"; then @@ -4963,6 +5012,7 @@ if test "${opsys}" = "mingw32"; then elif test "${HAVE_X11}" = "yes" && test "${with_gif}" != "no" \ || test "${HAVE_W32}" = "yes" || test "${HAVE_NS}" = "yes" \ || test "${HAVE_BE_APP}" = "yes" || test "$window_system" = "pgtk" \ + || test "${REALLY_ANDROID}" = "yes" \ && test "${with_gif}" != "no"; then AC_CHECK_HEADER([gif_lib.h], # EGifPutExtensionLast only exists from version libungif-4.1.0b1. @@ -4982,12 +5032,20 @@ elif test "${HAVE_X11}" = "yes" && test "${with_gif}" != "no" \ test "$HAVE_GIF" = yes && LIBGIF=-lungif fi +# Finally, try ndk-build on Android. + if test "$REALLY_ANDROID" = "yes"; then + ndk_SEARCH_MODULE([libgif], [GIF], [HAVE_GIF=yes], + [HAVE_GIF=no]) + test "$HAVE_GIF" = yes && LIBGIF="$GIF_LIBS" + fi + if test "${HAVE_GIF}" = "yes"; then AC_DEFINE([HAVE_GIF], [1], [Define to 1 if you have a gif (or ungif) library.]) fi fi AC_SUBST([LIBGIF]) +AC_SUBST([GIF_CFLAGS]) dnl Check for required libraries. MISSING= @@ -7291,7 +7349,7 @@ if test "$ANDROID" = "yes"; then fi if test "$XCOMPILE" = "yes"; then - SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES xcompile/Makefile" + SUBDIR_MAKEFILES="$SUBDIR_MAKEFILES cross/Makefile" fi dnl The admin/ directory used to be excluded from tarfiles. @@ -7377,11 +7435,15 @@ fi # Make java/Makefile ARCH_INDEPENDENT_CONFIG_FILES([java/Makefile]) -ARCH_INDEPENDENT_CONFIG_FILES([xcompile/Makefile]) +ARCH_INDEPENDENT_CONFIG_FILES([cross/Makefile]) # Make java/AndroidManifest.xml ARCH_INDEPENDENT_CONFIG_FILES([java/AndroidManifest.xml]) +# Make ndk-build Makefiles. This is only done inside the recursive +# configure. +ndk_CONFIG_FILES + AC_OUTPUT if test ! "$with_mailutils"; then diff --git a/cross/ndk-build/Makefile.in b/cross/ndk-build/Makefile.in new file mode 100644 index 00000000000..ec8e6205680 --- /dev/null +++ b/cross/ndk-build/Makefile.in @@ -0,0 +1,115 @@ +### @configure_input@ + +# Copyright 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 . + +# ndk-build works by including a bunch of Makefiles which set +# variables, and then having those Makefiles include another makefile +# which actually builds targets. + + srcdir = @srcdir@ + +# This is a list of Android.mk files which provide targets. +NDK_BUILD_ANDROID_MK = @NDK_BUILD_ANDROID_MK@ + NDK_BUILD_ARCH = @NDK_BUILD_ARCH@ + NDK_BUILD_ABI = @NDK_BUILD_ABI@ + NDK_BUILD_SDK = @NDK_BUILD_SDK@ + NDK_BUILD_CC = @NDK_BUILD_CC@ + NDK_BUILD_AR = @NDK_BUILD_AR@ + +# This is a list of targets to build. + NDK_BUILD_MODULES = @NDK_BUILD_MODULES@ + +# This is set by the Android in tree build system and is used by some +# libraries to look for the NDK. Its value is unimportant. + NDK_ROOT = /tmp/ + +# Finally, here are rules common to Emacs. +.PHONY: all +all: $(NDK_BUILD_MODULES) + +define uniqify +$(if $1,$(firstword $1) $(call uniqify,$(filter-out $(firstword $1),$1))) +endef + +# Remove duplicate files. +NDK_BUILD_ANDROID_MK := $(call uniqify,$(NDK_BUILD_ANDROID_MK)) + +define subr-1 + +# Define ndk-build functions. + +define my-dir +$(dir $(abspath $(1))) +endef + +# NDK-defined include variables. + +CLEAR_VARS = $(srcdir)/ndk-clear-vars.mk +BUILD_EXECUTABLE = $(srcdir)/ndk-build-executable.mk +BUILD_SHARED_LIBRARY = $(srcdir)/ndk-build-shared-library.mk +BUILD_STATIC_LIBRARY = $(srcdir)/ndk-build-static-library.mk +PREBUILT_SHARED_LIBRARY = $(srcdir)/ndk-prebuilt-shared-library.mk +PREBUILT_STATIC_LIBRARY = $(srcdir)/ndk-prebuilt-static-library.mk + +# Target information variables. + +TARGET_ARCH = $(NDK_BUILD_ARCH) +TARGET_PLATFORM = android-$(NDK_BUILD_SDK) +TARGET_ARCH_ABI = $(NDK_BUILD_ABI) +TARGET_ABI = $(TARGET_PLATFORM)-$(TARGET_ABI) + +# Module description variables. These are defined by Android.mk. +LOCAL_PATH := +LOCAL_MODULE := +LOCAL_MODULE_FILENAME := +LOCAL_SRC_FILES := +LOCAL_CPP_EXTENSION := +LOCAL_CPP_FEATURES := +LOCAL_C_INCLUDES := +LOCAL_CFLAGS := +LOCAL_CPPFLAGS := +LOCAL_STATIC_LIBRARIES := +LOCAL_SHARED_LIBRARIES := +LOCAL_WHOLE_STATIC_LIBRARIES := +LOCAL_LDLIBS := +LOCAL_LDFLAGS := +LOCAL_ALLOW_UNDEFINED_SYMBOLS := +LOCAL_ARM_MODE := +LOCAL_ARM_NEON := +LOCAL_DISABLE_FORMAT_STRING_CHECKS := +LOCAL_EXPORT_CFLAGS := +LOCAL_EXPORT_CPPFLAGS := +LOCAL_EXPORT_C_INCLUDES := +LOCAL_EXPORT_LDFLAGS := +LOCAL_EXPORT_LDLIBS := + +# Now load Android.mk. +include $(1) + +endef + +# Now define rules for each Android.mk file. +$(foreach android_mk,$(NDK_BUILD_ANDROID_MK),$(eval $(call subr-1,$(android_mk)))) + +.PHONY: clean mostlyclean +clean mostlyclean: + rm -rf *.o *.so *.a + +.PHONY: extraclean dist-clean maintainer-clean +extraclean dist-clean maintainer-clean: + rm -rf Makefile diff --git a/cross/ndk-build/ndk-build-executable.mk b/cross/ndk-build/ndk-build-executable.mk new file mode 100644 index 00000000000..9591c862b18 --- /dev/null +++ b/cross/ndk-build/ndk-build-executable.mk @@ -0,0 +1,22 @@ +# Copyright 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 . + +# ndk-build works by including a bunch of Makefiles which set +# variables, and then having those Makefiles include another makefile +# which actually builds targets. + +# Building executables is not supported diff --git a/cross/ndk-build/ndk-build-shared-library.mk b/cross/ndk-build/ndk-build-shared-library.mk new file mode 100644 index 00000000000..0d90d89e55d --- /dev/null +++ b/cross/ndk-build/ndk-build-shared-library.mk @@ -0,0 +1,83 @@ +# Copyright 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 . + +# ndk-build works by including a bunch of Makefiles which set +# variables, and then having those Makefiles include another makefile +# which actually builds targets. + +eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) +objname = $(1)-$(subst /,_,$(2).o) + +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)) + +else +ifneq ($(or $(call eq,x$(suffix $(1)),x.s),$(call eq,x$(suffix $(1)),x.S)),) + +$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) + $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_ASFLAGS_$(LOCAL_MODULE)) + +else +$$(error Unsupported suffix: $(suffix $(1))) +endif +endif + +ALL_OBJECT_FILES$(LOCAL_MODULE) += $(call objname,$(LOCAL_MODULE),$(basename $(1))) + +endef + +NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(addprefix $(LOCAL_PATH),$(LOCAL_C_INCLUDES))) +NDK_CFLAGS_$(LOCAL_MODULE) ::= -fPIC -iquote $(LOCAL_EXPORT_CFLAGS) $(LOCAL_PATH) $(LOCAL_CFLAGS) +NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) +NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDFLAGS) +ALL_OBJECT_FILES_$(LOCAL_MODULE) := + +ifeq ($(NDK_BUILD_ARCH)$(NDK_ARM_MODE),armarm) +NDK_CFLAGS ::= -marm +else +ifeq ($(NDK_BUILD_ARCH),arm) +NDK_CFLAGS ::= -mthumb +endif +endif + +LOCAL_MODULE_FILENAME := $(strip $(LOCAL_MODULE_FILENAME)) + +ifndef LOCAL_MODULE_FILENAME +ifeq ($(findstring lib,$(LOCAL_MODULE)),lib) +LOCAL_MODULE_FILENAME := $(LOCAL_MODULE)_emacs +else +LOCAL_MODULE_FILENAME := lib$(LOCAL_MODULE)_emacs +endif +endif + +# Since a shared library is being built, suffix the library with +# _emacs. Otherwise, libraries already on the system will be found +# first, with potentially nasty consequences. + +LOCAL_MODULE_FILENAME := $(LOCAL_MODULE_FILENAME).so + +# Then define rules to build all objects. +ALL_SOURCE_FILES = $(LOCAL_SRC_FILES) +$(foreach source,$(ALL_SOURCE_FILES),$(eval $(call single-object-target,$(source)))) + +# Now define the rule to build the shared library. +$(LOCAL_MODULE_FILENAME): $(ALL_OBJECT_FILES$(LOCAL_MODULE)) + $(NDK_BUILD_CC) $^ -o $@ -shared $(NDK_LDFLAGS$(LOCAL_MODULE)) diff --git a/cross/ndk-build/ndk-build-static-library.mk b/cross/ndk-build/ndk-build-static-library.mk new file mode 100644 index 00000000000..1423cf243cf --- /dev/null +++ b/cross/ndk-build/ndk-build-static-library.mk @@ -0,0 +1,83 @@ +# Copyright 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 . + +# ndk-build works by including a bunch of Makefiles which set +# variables, and then having those Makefiles include another makefile +# which actually builds targets. + +eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) +objname = $(1)-$(subst /,_,$(2).o) + +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)) + +else +ifneq ($(or $(call eq,x$(suffix $(1)),x.s),$(call eq,x$(suffix $(1)),x.S)),) + +$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) + $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_ASFLAGS_$(LOCAL_MODULE)) + +else +$$(error Unsupported suffix: $(suffix $(1))) +endif +endif + +ALL_OBJECT_FILES$(LOCAL_MODULE) += $(call objname,$(LOCAL_MODULE),$(basename $(1))) +endef + +NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(addprefix $(LOCAL_PATH),$(LOCAL_C_INCLUDES))) +NDK_CFLAGS_$(LOCAL_MODULE) ::= -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH)) +NDK_ASFLAGS_$(LOCAL_MODULE) ::= $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) +NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) +NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDFLAGS) +ALL_OBJECT_FILES$(LOCAL_MODULE) := + +ifeq ($(NDK_BUILD_ARCH)$(NDK_ARM_MODE),armarm) +NDK_CFLAGS ::= -marm +else +ifeq ($(NDK_BUILD_ARCH),arm) +NDK_CFLAGS ::= -mthumb +endif +endif + +LOCAL_MODULE_FILENAME := $(strip $(LOCAL_MODULE_FILENAME)) + +ifndef LOCAL_MODULE_FILENAME +ifeq ($(findstring lib,$(LOCAL_MODULE)),lib) +LOCAL_MODULE_FILENAME := $(LOCAL_MODULE) +else +LOCAL_MODULE_FILENAME := lib$(LOCAL_MODULE) +endif +endif + +LOCAL_MODULE_FILENAME := $(LOCAL_MODULE_FILENAME).a + +# Then define rules to build all objects. +ALL_SOURCE_FILES = $(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES_$(NDK_BUILD_ARCH)) + +# This defines all dependencies. +ALL_OBJECT_FILES$(LOCAL_MODULE) = + +$(foreach source,$(ALL_SOURCE_FILES),$(eval $(call single-object-target,$(source)))) + +# Now define the rule to build the library. +$(LOCAL_MODULE_FILENAME): $(ALL_OBJECT_FILES$(LOCAL_MODULE)) + $(NDK_BUILD_AR) r $@ $^ diff --git a/cross/ndk-build/ndk-build.in b/cross/ndk-build/ndk-build.in new file mode 100644 index 00000000000..8586b90d840 --- /dev/null +++ b/cross/ndk-build/ndk-build.in @@ -0,0 +1,44 @@ +### @configure_input@ + +# 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 . + +# This file is included all over the place to build prerequisites. + +NDK_BUILD_MODULES = @NDK_BUILD_MODULES@ +NDK_BUILD_SHARED = +NDK_BUILD_STATIC = + +define subr-1 + +.PHONY $(top_builddir)/cross/ndk-build/$(0) +$(top_builddir)/cross/ndk-build/$(0): + $(MAKE) -C $(top_builddir)/cross/ndk-build $(0) + +ifeq ($(suffix $(0)),.so) +NDK_BUILD_SHARED += $(top_builddir)/cross/ndk-build/$(0) +else +ifeq ($(suffix $(0)),.a) +NDK_BUILD_STATIC += $(top_builddir)/cross/ndk-build/$(0) +endif +endif + +endef + +# Generate rules for each module. + +$(foreach module,$(NDK_BUILD_MODULES),$(eval $(call subr-1,$(module)))) diff --git a/cross/ndk-build/ndk-build.mk.in b/cross/ndk-build/ndk-build.mk.in new file mode 100644 index 00000000000..6ad577ccc35 --- /dev/null +++ b/cross/ndk-build/ndk-build.mk.in @@ -0,0 +1,43 @@ +### @configure_input@ + +# 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 . + +# This file is included all over the place to build prerequisites. + +NDK_BUILD_MODULES = @NDK_BUILD_MODULES@ +NDK_BUILD_SHARED = +NDK_BUILD_STATIC = + +define subr-1 + +$(top_builddir)/cross/ndk-build/$(1): + $(MAKE) -C $(top_builddir)/cross/ndk-build $(1) + +ifeq ($(suffix $(1)),.so) +NDK_BUILD_SHARED += $(top_builddir)/cross/ndk-build/$(1) +else +ifeq ($(suffix $(1)),.a) +NDK_BUILD_STATIC += $(top_builddir)/cross/ndk-build/$(1) +endif +endif + +endef + +# Generate rules for each module. + +$(foreach module,$(NDK_BUILD_MODULES),$(eval $(call subr-1,$(module)))) diff --git a/cross/ndk-build/ndk-clear-vars.mk b/cross/ndk-build/ndk-clear-vars.mk new file mode 100644 index 00000000000..df2bf95fd5e --- /dev/null +++ b/cross/ndk-build/ndk-clear-vars.mk @@ -0,0 +1,47 @@ +# Copyright 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 . + +# ndk-build works by including a bunch of Makefiles which set +# variables, and then having those Makefiles include another makefile +# which actually builds targets. + +undefine LOCAL_MODULE +undefine LOCAL_MODULE_FILENAME +undefine LOCAL_SRC_FILES +undefine LOCAL_CPP_EXTENSION +undefine LOCAL_CPP_FEATURES +undefine LOCAL_C_INCLUDES +undefine LOCAL_CFLAGS +undefine LOCAL_CPPFLAGS +undefine LOCAL_STATIC_LIBRARIES +undefine LOCAL_SHARED_LIBRARIES +undefine LOCAL_WHOLE_STATIC_LIBRARIES +undefine LOCAL_LDLIBS +undefine LOCAL_LDFLAGS +undefine LOCAL_ALLOW_UNDEFINED_SYMBOLS +undefine LOCAL_ARM_MODE +undefine LOCAL_ARM_NEON +undefine LOCAL_DISABLE_FORMAT_STRING_CHECKS +undefine LOCAL_EXPORT_CFLAGS +undefine LOCAL_EXPORT_CPPFLAGS +undefine LOCAL_EXPORT_C_INCLUDES +undefine LOCAL_EXPORT_LDFLAGS +undefine LOCAL_EXPORT_LDLIBS + +undefine LOCAL_SRC_FILES_$(NDK_BUILD_ARCH) +undefine LOCAL_ASFLAGS_$(NDK_BUILD_ARCH) +undefine LOCAL_CFLAGS_$(NDK_BUILD_ARCH) diff --git a/cross/ndk-build/ndk-prebuilt-shared-library.mk b/cross/ndk-build/ndk-prebuilt-shared-library.mk new file mode 100644 index 00000000000..2a8260f9851 --- /dev/null +++ b/cross/ndk-build/ndk-prebuilt-shared-library.mk @@ -0,0 +1,24 @@ +### @configure_input@ + +# Copyright 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 . + +# ndk-build works by including a bunch of Makefiles which set +# variables, and then having those Makefiles include another makefile +# which actually builds targets. + +$(warn Prebuilt shared libraries are not supported) diff --git a/cross/ndk-build/ndk-prebuilt-static-library.mk b/cross/ndk-build/ndk-prebuilt-static-library.mk new file mode 100644 index 00000000000..9230f690bb1 --- /dev/null +++ b/cross/ndk-build/ndk-prebuilt-static-library.mk @@ -0,0 +1,24 @@ +### @configure_input@ + +# Copyright 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 . + +# ndk-build works by including a bunch of Makefiles which set +# variables, and then having those Makefiles include another makefile +# which actually builds targets. + +$(warn Prebuilt static libraries are not supported) diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 223e9dbf8ef..e4498da401d 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -18,6 +18,7 @@ about using such devices with Emacs, @pxref{Other Input Devices}. * Android Startup:: Starting up Emacs on Android. * Android File System:: The Android file system. * Android Environment:: Running Emacs under Android. +* Android Windowing:: The Android window system. * Android Fonts:: Font selection under Android. @end menu @@ -299,7 +300,8 @@ settings application. Consult the manufacturer of your device for more details, as how to do this varies by device. @end itemize -@section Android windowing +@node Android Windowing +@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 @@ -372,6 +374,31 @@ The @code{fullscreen} frame parameter is always @code{maximized} for top-level frames. @end itemize +@cindex selections, android +@cindex android clipboard + Emacs does not implement all selection related features supported +under the X Window System on Android. For example, only the +@code{CLIPBOARD} and @code{PRIMARY} selections (@pxref{Cut and Paste}) +are supported, and plain text is the only supported data type. + + In addition, the Android system itself places certain restrictions +on what selection data Emacs can access: + +@itemize @bullet +@item +On Android 2.3 and earlier, the function @code{gui-selection-owner-p} +always returns @code{nil} for the clipboard selection. + +@item +On Android 3.0 and later, Emacs can only access clipboard data when +one of its frames has the input focus. +@end itemize + + Since the Android system itself has no concept of a primary +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. + @node Android Fonts @section Font backends and selection under Android @cindex fonts, android diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index 0cb454e5294..30758efb43a 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -1264,6 +1264,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 Windowing:: The Android window system. * Android Fonts:: Font selection under Android. Emacs and unconventional input devices diff --git a/etc/MACHINES b/etc/MACHINES index 8c6f3f48ce7..8ba0b1faa77 100644 --- a/etc/MACHINES +++ b/etc/MACHINES @@ -131,6 +131,14 @@ the list at the end of this file. The earliest release of Haiku that will successfully compile Emacs is R1/Beta2. For windowing support, R1/Beta3 or later is required. +** Android + + Emacs is known to run on all Android versions from 2.3 onwards. + It should work with Android 2.2 as well, but only the build + has been tested, and actually running the built Emacs has not. + + See the file INSTALL.android for detailed installation instructions. + * Obsolete platforms diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in index 74f69d2a8e5..c4a9d1f5177 100644 --- a/java/AndroidManifest.xml.in +++ b/java/AndroidManifest.xml.in @@ -60,12 +60,13 @@ along with GNU Emacs. If not, see . --> android:hardwareAccelerated="true" android:supportsRtl="true" android:theme="@android:style/Theme" - android:debuggable="true" + android:debuggable="@ANDROID_DEBUGGABLE@" android:extractNativeLibs="true"> @@ -76,10 +77,12 @@ along with GNU Emacs. If not, see . --> diff --git a/java/Makefile.in b/java/Makefile.in index 22c912fdce5..d27775ea3db 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -81,21 +81,24 @@ APK_NAME = emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk all: $(APK_NAME) # Binaries to cross-compile. -CROSS_BINS = ../xcompile/src/android-emacs ../xcompile/lib-src/ctags \ - ../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \ - ../xcompile/lib-src/ebrowse ../xcompile/lib-src/emacsclient +CROSS_BINS = ../cross/src/android-emacs ../cross/lib-src/ctags \ + ../cross/lib-src/hexl ../cross/lib-src/movemail \ + ../cross/lib-src/ebrowse ../cross/lib-src/emacsclient # Libraries to cross-compile. -CROSS_LIBS = ../xcompile/src/libemacs.so +CROSS_LIBS = ../cross/src/libemacs.so + +# Third party libraries to compile. +include $(top_builddir)/cross/ndk-build/ndk-build.mk .PHONY: $(CROSS_BINS) $(CROSS_LIBS) -../xcompile/src/android-emacs ../xcompile/src/libemacs.so: - make -C ../xcompile src/$(notdir $@) +../cross/src/android-emacs ../cross/src/libemacs.so: + make -C ../cross src/$(notdir $@) -../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \ -../xcompile/lib-src/ctags ../xcompile/lib-src/ebrowse &: - make -C ../xcompile lib-src/$(notdir $@) +../cross/lib-src/hexl ../cross/lib-src/movemail \ +../cross/lib-src/ctags ../cross/lib-src/ebrowse &: + make -C ../cross lib-src/$(notdir $@) # This is needed to generate the ``.directory-tree'' file used by the # Android emulations of readdir and faccessat. @@ -104,7 +107,7 @@ $(libsrc)/asset-directory-tool: $(MAKE) -C $(libsrc) $(notdir $@) emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ - AndroidManifest.xml + AndroidManifest.xml $(NDK_BUILD_SHARED) # Make the working directory for this stuff rm -rf install_temp mkdir -p install_temp/lib/$(ANDROID_ABI) @@ -145,11 +148,13 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ cp -f $$file install_temp/lib/$(ANDROID_ABI); \ fi \ done + $(foreach module,$(NDK_BUILD_SHARED), \ + cp -f $(module) install_temp/lib/$(ANDROID_ABI)) # 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 + $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f \ + -M AndroidManifest.xml -A install_temp/assets pushd install_temp; $(AAPT) add ../$@ `find lib -type f`; popd rm -rf install_temp @@ -196,3 +201,4 @@ clean: find . -name '*.class' -delete maintainer-clean distclean bootstrap-clean: clean + rm -f Makefile ndk-build.mk diff --git a/java/org/gnu/emacs/EmacsClipboard.java b/java/org/gnu/emacs/EmacsClipboard.java new file mode 100644 index 00000000000..cd6bcebfe0e --- /dev/null +++ b/java/org/gnu/emacs/EmacsClipboard.java @@ -0,0 +1,44 @@ +/* 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.os.Build; + +/* This class provides helper code for accessing the clipboard, + abstracting between the different interfaces on API 8 and 11. */ + +public abstract class EmacsClipboard +{ + public abstract void setClipboard (byte[] bytes); + public abstract int ownsClipboard (); + public abstract boolean clipboardExists (); + public abstract byte[] getClipboard (); + + /* Create the correct kind of clipboard for this system. */ + + public static EmacsClipboard + makeClipboard () + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) + return new EmacsSdk11Clipboard (); + else + return new EmacsSdk8Clipboard (); + } +}; diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java index 1d1e6f7b33f..39bda5a456d 100644 --- a/java/org/gnu/emacs/EmacsFontDriver.java +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -23,6 +23,9 @@ import java.util.List; import android.os.Build; +/* This code is mostly unused. See sfntfont-android.c for the code + that is actually used. */ + public abstract class EmacsFontDriver { /* Font weights. */ diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 3efdc0cff9a..962538bef7b 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -132,6 +132,10 @@ public class EmacsNative /* Send an ANDROID_CONTEXT_MENU event. */ public static native long sendContextMenu (short window, int menuEventID); + /* Send an ANDROID_EXPOSE event. */ + public static native long sendExpose (short window, int x, int y, + int width, int height); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsSdk11Clipboard.java b/java/org/gnu/emacs/EmacsSdk11Clipboard.java new file mode 100644 index 00000000000..0a725200723 --- /dev/null +++ b/java/org/gnu/emacs/EmacsSdk11Clipboard.java @@ -0,0 +1,156 @@ +/* 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.content.ClipboardManager; +import android.content.Context; +import android.content.ClipData; + +import android.util.Log; + +import java.io.UnsupportedEncodingException; + +/* This class implements EmacsClipboard for Android 3.0 and later + systems. */ + +public class EmacsSdk11Clipboard extends EmacsClipboard + implements ClipboardManager.OnPrimaryClipChangedListener +{ + private static final String TAG = "EmacsSdk11Clipboard"; + private ClipboardManager manager; + private boolean ownsClipboard; + private int clipboardChangedCount; + private int monitoredClipboardChangedCount; + + public + EmacsSdk11Clipboard () + { + String what; + Context context; + + what = Context.CLIPBOARD_SERVICE; + context = EmacsService.SERVICE; + manager + = (ClipboardManager) context.getSystemService (what); + manager.addPrimaryClipChangedListener (this); + } + + @Override + public synchronized void + onPrimaryClipChanged () + { + Log.d (TAG, ("onPrimaryClipChanged: " + + monitoredClipboardChangedCount + + " " + clipboardChangedCount)); + + /* Increment monitoredClipboardChangeCount. If it is now greater + than clipboardChangedCount, then Emacs no longer owns the + clipboard. */ + monitoredClipboardChangedCount++; + + if (monitoredClipboardChangedCount > clipboardChangedCount) + { + ownsClipboard = false; + + /* Reset both values back to 0. */ + monitoredClipboardChangedCount = 0; + clipboardChangedCount = 0; + } + } + + /* Set the clipboard text to CLIPBOARD, a string in UTF-8 + encoding. */ + + @Override + public synchronized void + setClipboard (byte[] bytes) + { + ClipData data; + String string; + + try + { + string = new String (bytes, "UTF-8"); + data = ClipData.newPlainText ("Emacs", string); + manager.setPrimaryClip (data); + ownsClipboard = true; + + /* onPrimaryClipChanged will be called again. Use this + variable to keep track of how many times the clipboard has + been changed. */ + ++clipboardChangedCount; + } + catch (UnsupportedEncodingException exception) + { + Log.w (TAG, "setClipboard: " + exception); + } + } + + /* Return whether or not Emacs owns the clipboard. Value is 1 if + Emacs does, 0 if Emacs does not, and -1 if that information is + unavailable. */ + + @Override + public synchronized int + ownsClipboard () + { + return ownsClipboard ? 1 : 0; + } + + /* Return whether or not clipboard content currently exists. */ + + @Override + public boolean + clipboardExists () + { + return manager.hasPrimaryClip (); + } + + /* Return the current content of the clipboard, as plain text, or + NULL if no content is available. */ + + @Override + public byte[] + getClipboard () + { + ClipData clip; + CharSequence text; + Context context; + + clip = manager.getPrimaryClip (); + + if (clip == null || clip.getItemCount () < 1) + return null; + + context = EmacsService.SERVICE; + + try + { + text = clip.getItemAt (0).coerceToText (context); + return text.toString ().getBytes ("UTF-8"); + } + catch (UnsupportedEncodingException exception) + { + Log.w (TAG, "getClipboard: " + exception); + } + + return null; + } +}; diff --git a/java/org/gnu/emacs/EmacsSdk8Clipboard.java b/java/org/gnu/emacs/EmacsSdk8Clipboard.java new file mode 100644 index 00000000000..34e66912562 --- /dev/null +++ b/java/org/gnu/emacs/EmacsSdk8Clipboard.java @@ -0,0 +1,116 @@ +/* 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; + +/* Importing the entire package avoids the deprecation warning. */ +import android.text.*; + +import android.content.Context; +import android.util.Log; + +import java.io.UnsupportedEncodingException; + +/* This class implements EmacsClipboard for Android 2.2 and other + similarly old systems. */ + +@SuppressWarnings ("deprecation") +public class EmacsSdk8Clipboard extends EmacsClipboard +{ + private static final String TAG = "EmacsSdk8Clipboard"; + private ClipboardManager manager; + + public + EmacsSdk8Clipboard () + { + String what; + Context context; + + what = Context.CLIPBOARD_SERVICE; + context = EmacsService.SERVICE; + manager + = (ClipboardManager) context.getSystemService (what); + } + + /* Set the clipboard text to CLIPBOARD, a string in UTF-8 + encoding. */ + + @Override + public void + setClipboard (byte[] bytes) + { + try + { + manager.setText (new String (bytes, "UTF-8")); + } + catch (UnsupportedEncodingException exception) + { + Log.w (TAG, "setClipboard: " + exception); + } + } + + /* Return whether or not Emacs owns the clipboard. Value is 1 if + Emacs does, 0 if Emacs does not, and -1 if that information is + unavailable. */ + + @Override + public int + ownsClipboard () + { + return -1; + } + + /* Return whether or not clipboard content currently exists. */ + + @Override + public boolean + clipboardExists () + { + return manager.hasText (); + } + + /* Return the current content of the clipboard, as plain text, or + NULL if no content is available. */ + + @Override + public byte[] + getClipboard () + { + String string; + CharSequence text; + + text = manager.getText (); + + if (text == null) + return null; + + string = text.toString (); + + try + { + return string.getBytes ("UTF-8"); + } + catch (UnsupportedEncodingException exception) + { + Log.w (TAG, "getClipboard: " + exception); + } + + return null; + } +}; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 74bbb7b3ecc..881cbc363ba 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -99,6 +99,9 @@ public class EmacsView extends ViewGroup yet responded to. 0 if there is no such outstanding event. */ public long pendingConfigure; + /* Whether or not this view is attached to a window. */ + public boolean isAttachedToWindow; + public EmacsView (EmacsWindow window) { @@ -140,6 +143,9 @@ public class EmacsView extends ViewGroup if (measuredWidth == 0 || measuredHeight == 0) return; + if (!isAttachedToWindow) + 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. */ @@ -547,6 +553,8 @@ public class EmacsView extends ViewGroup public synchronized void onDetachedFromWindow () { + isAttachedToWindow = false; + synchronized (this) { /* Recycle the bitmap and call GC. */ @@ -559,6 +567,21 @@ public class EmacsView extends ViewGroup } } + @Override + public synchronized void + onAttachedToWindow () + { + isAttachedToWindow = true; + + /* Dirty the bitmap, as it was destroyed when onDetachedFromWindow + was called. */ + bitmapDirty = true; + + /* Now expose the view contents again. */ + EmacsNative.sendExpose (this.window.handle, 0, 0, + measuredWidth, measuredHeight); + } + public void showOnScreenKeyboard () { diff --git a/lib/Makefile.in b/lib/Makefile.in index fd730b554e3..b84a1bf9741 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -21,7 +21,7 @@ srcdir = @srcdir@ VPATH = @srcdir@ # This is not empty if this is a Makefile that will be copied to -# xcompile/lib. +# cross/lib. XCONFIGURE = @XCONFIGURE@ # This is required to make sure symbol visibility is correct and @@ -64,7 +64,7 @@ endif ifeq ($(XCONFIGURE),android) # The next line is necessary to override -I$(srcdir), which will end # up pulling in lots of headers from the host. -ALL_CFLAGS += -I$(top_srcdir)/xcompile -I. +ALL_CFLAGS += -I$(top_srcdir)/cross -I. endif DEPDIR = deps diff --git a/lisp/term/android-win.el b/lisp/term/android-win.el index 43b75d39f50..6ab469dc0b0 100644 --- a/lisp/term/android-win.el +++ b/lisp/term/android-win.el @@ -59,5 +59,111 @@ DISPLAY is ignored on Android." ;; arguments on. (ignore)) + +;;; Selection support. + +(declare-function android-clipboard-exists-p "androidselect.c") +(declare-function android-get-clipboard "androidselect.c") +(declare-function android-set-clipboard "androidselect.c") +(declare-function android-clipboard-owner-p "androidselect.c") + +(defvar android-primary-selection nil + "The last string placed in the primary selection. +Nil if there was no such string. + +Android does not have a primary selection of its own, so Emacs +emulates one inside Lisp.") + +(defun android-get-clipboard-1 (data-type) + "Return the clipboard data. +DATA-TYPE is a selection conversion target; only STRING and +TARGETS are supported." + (or (and (eq data-type 'STRING) + (android-get-clipboard)) + (and (eq data-type 'TARGETS) + (android-clipboard-exists-p) + [TARGETS STRING]))) + +(defun android-get-primary (data-type) + "Return the last string placed in the primary selection, or nil. +Return nil if DATA-TYPE is anything other than STRING or TARGETS." + (when android-primary-selection + (or (and (eq data-type 'STRING) + android-primary-selection) + (and (eq data-type 'TARGETS) + [TARGETS])))) + +(defun android-selection-bounds (value) + "Return bounds of selection value VALUE. +The return value is a list (BEG END BUF) if VALUE is a cons of +two markers or an overlay. Otherwise, it is nil." + (cond ((bufferp value) + (with-current-buffer value + (when (mark t) + (list (mark t) (point) value)))) + ((and (consp value) + (markerp (car value)) + (markerp (cdr value))) + (when (and (marker-buffer (car value)) + (buffer-name (marker-buffer (car value))) + (eq (marker-buffer (car value)) + (marker-buffer (cdr value)))) + (list (marker-position (car value)) + (marker-position (cdr value)) + (marker-buffer (car value))))) + ((overlayp value) + (when (overlay-buffer value) + (list (overlay-start value) + (overlay-end value) + (overlay-buffer value)))))) + +(defun android-encode-select-string (value) + "Turn VALUE into a string suitable for placing in the clipboard. +VALUE should be something suitable for passing to +`gui-set-selection'." + (unless (stringp value) + (when-let ((bounds (android-selection-bounds value))) + (setq value (ignore-errors + (with-current-buffer (nth 2 bounds) + (buffer-substring (nth 0 bounds) + (nth 1 bounds))))))) + value) + +(cl-defmethod gui-backend-get-selection (type data-type + &context (window-system android)) + (cond ((eq type 'CLIPBOARD) + (android-get-clipboard-1 data-type)) + ((eq type 'PRIMARY) + (android-get-primary data-type)))) + +(cl-defmethod gui-backend-selection-exists-p (selection + &context (window-system android)) + (cond ((eq selection 'CLIPBOARD) + (android-clipboard-exists-p)) + ((eq selection 'PRIMARY) + (not (null android-primary-selection))))) + +(cl-defmethod gui-backend-selection-owner-p (selection + &context (window-system android)) + (cond ((eq selection 'CLIPBOARD) + (let ((ownership (android-clipboard-owner-p))) + ;; If ownership is `lambda', then Emacs couldn't determine + ;; whether or not it owns the clipboard. + (and (not (eq ownership 'lambda)) ownership))) + ((eq selection 'PRIMARY) + ;; Emacs always owns its own primary selection as long as it + ;; exists. + (not (null android-primary-selection))))) + +(cl-defmethod gui-backend-set-selection (type value + &context (window-system android)) + ;; First, try to turn value into a string. + ;; Don't set anything if that did not work. + (when-let ((string (android-encode-select-string value))) + (cond ((eq type 'CLIPBOARD) + (android-set-clipboard string)) + ((eq type 'PRIMARY) + (setq android-primary-selection string))))) + (provide 'android-win) ;; android-win.el ends here. diff --git a/m4/ndk-build.m4 b/m4/ndk-build.m4 new file mode 100644 index 00000000000..a35900884d9 --- /dev/null +++ b/m4/ndk-build.m4 @@ -0,0 +1,213 @@ +dnl Copyright (C) 2023 Free Software Foundation, Inc. +dnl This file is part of GNU Emacs. + +dnl GNU Emacs is free software: you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation, either version 3 of the License, or +dnl (at your option) any later version. + +dnl GNU Emacs is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. + +dnl You should have received a copy of the GNU General Public License +dnl along with GNU Emacs. If not, see . + +# Support for building Emacs with dependencies using the Android NDK +# build system. + +AC_ARG_WITH([ndk_path], + [AS_HELP_STRING([--with-ndk-path], + [find Android libraries in these directories])]) + +# ndk_INIT(ABI, API, DIR) +# -------- +# Initialize the Android NDK. ABI is the ABI being built for. +# API is the API version being built for. +# As a side effect, set the variable ndk_INITIALIZED to true. +# DIR should be a directory containing the Makefile.in actually +# implementing the Android NDK build system. + +AC_DEFUN_ONCE([ndk_INIT], +[ +# Look for Android.mk files. +ndk_module_files= +for file in $with_ndk_path; do + if test -f $file/Android.mk; then + ndk_module_files="$ndk_module_files$file/Android.mk " + fi +done + +ndk_ABI=$1 +ndk_MODULES= +ndk_MAKEFILES= +ndk_INITIALIZED=yes +ndk_API=$2 +ndk_DIR=$3 + +case "$ndk_ABI" in + *arm64* ) + ndk_ARCH=arm64 + ;; + *arm* ) + ndk_ARCH=arm + ;; + *x86_64* ) + ndk_ARCH=x86_64 + ;; + *x86* ) + ndk_ARCH=x86 + ;; + * ) + AC_MSG_ERROR([Failed to determine Android device architecture]) + ;; +esac + +# This is a map between pkg-config style package names and Android +# ones. + +ndk_package_map="libwebpdemux:webpdemux libxml-2.0:libxml2" + +# Replace ndk_module with the appropriate Android module name if it is +# found in ndk_package_map. + +ndk_replace_pkg_config_package () { + for ndk_stuff in $ndk_package_map; do + ndk_key=${ndk_stuff%%:*} + ndk_value=${ndk_stuff#*:} + + if test "$ndk_key" = "$ndk_module"; then + ndk_module="$ndk_value" + break + fi + done +} + +# Parse a pkg-config style list of modules. Place the resulting list +# in ndk_modules. + +ndk_parse_pkg_config_string () { + ndk_input=[$]1 + ndk_modules= + while test -n "$ndk_input"; do + ndk_str=$(printf "$ndk_input" | cut -f1 -d' ') + ndk_input="$(printf "$ndk_input" | cut -s -f2- -d' ')" + + if test "$ndk_str" = ">=" || test "$ndk_str" = "<=" \ + || test "$ndk_str" = ">" || test "$ndk_str" = "<"; then + ndk_input="$(printf "$ndk_input" | cut -s -f2- -d' ')" + else + ndk_modules="$ndk_modules$ndk_str " + fi + done +} + +# Look for a suitable ar in the same directory as the C compiler. +ndk_where_cc=$(which $CC) +ndk_ar_search_path=$PATH + +# First, try to find $host_alias-ar in PATH. +AC_PATH_PROGS([AR], [$host_alias-ar], [], [$ndk_ar_search_path]) + +if test -z "$AR"; then + # Next, try finding either that or llvm-ar in the directory holding + # CC. + ndk_ar_search_path="$(dirname $ndk_where_cc):$ndk_ar_search_path" + AC_PATH_PROGS([AR], [$host_alias-ar llvm-ar], [], [$ndk_ar_search_path]) +fi + +# These variables have now been found. +]) + +# ndk_SEARCH_MODULE(MODULE, NAME, ACTION-IF-FOUND, [ACTION-IF-NOT-FOUND]) +# -------------------------------------------------------------------- +# Search for a module named MODULE in `with_ndk_path'. Add the file +# name of the module's Android.mk file to the variable ndk_MODULES. +# Set NAME_CFLAGS and NAME_LIBS to the appropriate values. Then, +# call ACTION-IF-FOUND, or ACTION-IF-NOT-FOUND upon failure. +AC_DEFUN([ndk_SEARCH_MODULE], +[ +module_name= +ndk_module=$1 +ndk_replace_pkg_config_package +AC_MSG_CHECKING([for Android.mk that builds $ndk_module]) + +for ndk_android_mk in $ndk_module_files; do + # Read this Android.mk file. Set NDK_ROOT to /tmp: the Android in + # tree build system sets it to a meaning value, but build files just + # use it to test whether or not the NDK is being used. + ndk_commands=$(make -s -f build-aux/ndk-build-helper.mk EMACS_SRCDIR=. \ + EMACS_ABI=$ndk_ABI ANDROID_MAKEFILE="$ndk_android_mk" \ + ANDROID_MODULE_DIRECTORY=$(dirname "$ndk_android_mk") \ + NDK_BUILD_DIR="$ndk_DIR" NDK_ROOT="/tmp" \ + | awk -f build-aux/ndk-module-extract.awk \ + MODULE="$ndk_module") + + AS_IF([test -n "${ndk_commands//\n }"], [eval "$ndk_commands"]) + + if test -n "$module_name"; then + break + fi +done + +if test -z "$module_name"; then + AC_MSG_RESULT([no]) + $4 +else + $2[]_CFLAGS="[$]$2[]_CFLAGS $module_cflags $module_includes" + $2[]_LIBS="[$]$2[]_LIBS $module_ldflags" + ndk_MAKEFILES="$ndk_MAKEFILES $ndk_android_mk" + ndk_MODULES="$ndk_MODULES $module_target" + AC_MSG_RESULT([yes]) + $3 +fi +]) + +# ndk_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# -------------------------------------------------------------- +# Just like `PKG_CHECK_MODULES'. However, it uses the ndk-build +# system instead. + +AC_DEFUN([ndk_CHECK_MODULES], +[ + ndk_modules= + ndk_parse_pkg_config_string "$2" + ndk_found=no + + for module in $ndk_modules; do + ndk_SEARCH_MODULE([$module], [$1], [ndk_found=yes], [ndk_found=no]) + done + + AS_IF([test "$ndk_found" = "yes"],[$3],[$4]) +]) + +# ndk_CONFIG_FILES +# ------------------------------------------------------------- +# Write out the NDK build Makefile with the appropriate variables +# set if the NDK has been initialized. + +AC_DEFUN_ONCE([ndk_CONFIG_FILES], +[ + if test "$ndk_INITIALIZED" = "yes"; then + NDK_BUILD_ANDROID_MK="$ndk_MAKEFILES" + NDK_BUILD_ARCH=$ndk_ARCH + NDK_BUILD_ABI=$ndk_ABI + NDK_BUILD_SDK=$ndk_API + NDK_BUILD_CC=$CC + NDK_BUILD_AR=$AR + NDK_BUILD_MODULES="$ndk_MODULES" + + AC_SUBST([NDK_BUILD_ANDROID_MK]) + AC_SUBST([NDK_BUILD_ARCH]) + AC_SUBST([NDK_BUILD_ABI]) + AC_SUBST([NDK_BUILD_SDK]) + AC_SUBST([NDK_BUILD_CC]) + AC_SUBST([NDK_BUILD_AR]) + AC_SUBST([NDK_BUILD_MODULES]) + + AC_CONFIG_FILES([$ndk_DIR/Makefile]) + AC_CONFIG_FILES([$ndk_DIR/ndk-build.mk]) + fi +]) diff --git a/src/Makefile.in b/src/Makefile.in index 1b4a8a16c46..ddc0b89dda1 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -35,7 +35,7 @@ abs_top_srcdir=@abs_top_srcdir@ VPATH = $(srcdir) # This is not empty if this is a Makefile that will be copied to -# xcompile/src. +# cross/src. XCONFIGURE = @XCONFIGURE@ ifneq ($(XCONFIGURE),) @@ -138,6 +138,8 @@ LIB_PTHREAD=@LIB_PTHREAD@ LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ @WEBP_LIBS@ +GIF_CFLAGS=@GIF_CFLAGS@ + XCB_LIBS=@XCB_LIBS@ XFT_LIBS=@XFT_LIBS@ XRENDER_LIBS=@XRENDER_LIBS@ @@ -384,6 +386,7 @@ HAIKU_CFLAGS = @HAIKU_CFLAGS@ ANDROID_OBJ = @ANDROID_OBJ@ ANDROID_LIBS = @ANDROID_LIBS@ +ANDROID_LDFLAGS = @ANDROID_LDFLAGS@ ANDROID_CFLAGS = @ANDROID_CFLAGS@ DUMPING=@DUMPING@ @@ -428,7 +431,7 @@ 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) + $(ANDROID_CFLAGS) $(GIF_CFLAGS) ALL_CFLAGS = $(EMACS_CFLAGS) $(WARN_CFLAGS) $(CFLAGS) ALL_OBJC_CFLAGS = $(EMACS_CFLAGS) \ $(filter-out $(NON_OBJC_CFLAGS),$(WARN_CFLAGS)) $(CFLAGS) \ @@ -745,10 +748,18 @@ ifeq ($(XCONFIGURE),android) ## is not dumped here. Instead, it dumps itself the first time it ## starts on the user's device. +# Include ndk-build.mk in order to build Emacs dependencies. +old_top_builddir := $(top_builddir) +top_builddir := $(old_top_builddir)/.. +include $(old_top_builddir)/ndk-build/ndk-build.mk +top_builddir := $(old_top_builddir) + libemacs.so: $(ALLOBJS) $(LIBEGNU_ARCHIVE) $(EMACSRES) \ - $(MAKE_PDUMPER_FINGERPRINT) + $(MAKE_PDUMPER_FINGERPRINT) $(NDK_BUILD_SHARED) \ + $(NDK_BUILD_STATIC) $(AM_V_CCLD)$(CC) -o $@ $(ALL_CFLAGS) $(TEMACS_LDFLAGS) \ - $(LDFLAGS) -shared $(ALLOBJS) $(LIBEGNU_ARCHIVE) $(LIBES) + $(ANDROID_LDFLAGS) $(LDFLAGS) -shared $(ALLOBJS) \ + $(LIBEGNU_ARCHIVE) $(LIBES) $(AM_V_at)$(MAKE_PDUMPER_FINGERPRINT) $@ # There is also a binary named `android-emacs' which simply calls diff --git a/src/android.c b/src/android.c index a51d0518484..d788c6f19b4 100644 --- a/src/android.c +++ b/src/android.c @@ -1086,6 +1086,39 @@ android_hack_asset_fd (AAsset *asset) return fd; } +/* Read two bytes from FD and see if they are ``PK'', denoting ZIP + archive compressed data. + + If they are not, rewind the file descriptor to offset 0. + + If either operation fails, return -1 and close FD. Else, value is + FD. */ + +static int +android_check_compressed_file (int fd) +{ + char bytes[2]; + + if (read (fd, bytes, 2) != 2) + goto lseek_back; + + if (bytes[0] != 'P' || bytes[1] != 'K') + goto lseek_back; + + /* This could be compressed data! */ + return -1; + + lseek_back: + /* Seek back to offset 0. If this fails, return -1. */ + if (lseek (fd, 0, SEEK_SET) != 0) + { + close (fd); + return -1; + } + + return fd; +} + /* `open' and such are modified even though they exist on Android, because Emacs treats "/assets/" as a special directory that must contain all assets in the application package. */ @@ -1095,7 +1128,7 @@ android_open (const char *filename, int oflag, int mode) { const char *name; AAsset *asset; - int fd; + int fd, oldfd; off_t out_start, out_length; bool fd_hacked; @@ -1118,8 +1151,12 @@ android_open (const char *filename, int oflag, int mode) return -1; } + /* If given AASSET_MODE_BUFFER (which is what Emacs probably + does, given that a file descriptor is not always available), + the framework fails to uncompress the data before it returns + a file descriptor. */ asset = AAssetManager_open (asset_manager, name, - AASSET_MODE_BUFFER); + AASSET_MODE_STREAMING); if (!asset) { @@ -1132,6 +1169,11 @@ android_open (const char *filename, int oflag, int mode) fd = AAsset_openFileDescriptor (asset, &out_start, &out_length); + /* The platform sometimes returns a file descriptor to ZIP + compressed data. Detect that and fall back to creating a + shared memory file descriptor. */ + fd = android_check_compressed_file (fd); + if (fd == -1) { /* The asset can't be accessed for some reason. Try to @@ -1152,7 +1194,13 @@ android_open (const char *filename, int oflag, int mode) which will close the original file descriptor. */ if (!fd_hacked) - fd = fcntl (fd, F_DUPFD_CLOEXEC); + { + oldfd = fd; + fd = fcntl (oldfd, F_DUPFD_CLOEXEC); + + /* Close the original file descriptor. */ + close (oldfd); + } if (fd >= ANDROID_MAX_ASSET_FD || fd < 0) { @@ -1978,6 +2026,25 @@ NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object, return event_serial; } +extern JNIEXPORT jlong JNICALL +NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object, + jshort window, jint x, jint y, + jint width, jint height) +{ + union android_event event; + + event.xexpose.type = ANDROID_EXPOSE; + event.xexpose.serial = ++event_serial; + event.xexpose.window = window; + event.xexpose.x = x; + event.xexpose.y = y; + event.xexpose.width = width; + event.xexpose.height = height; + + android_write_event (&event); + return event_serial; +} + #ifdef __clang__ #pragma clang diagnostic pop #else @@ -4222,7 +4289,14 @@ android_readdir (struct android_dir *dir) dirent.d_ino = 0; dirent.d_off = 0; dirent.d_reclen = sizeof dirent; - dirent.d_type = DT_UNKNOWN; + + /* If this is not a directory, return DT_UNKNOWN. Otherwise, + return DT_DIR. */ + + if (android_is_directory (dir->asset_dir)) + dirent.d_type = DT_DIR; + else + dirent.d_type = DT_UNKNOWN; /* Note that dir->asset_dir is actually a NULL terminated string. */ diff --git a/src/androidgui.h b/src/androidgui.h index 3b9a74dc0b0..619c5e44f15 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -234,6 +234,7 @@ enum android_event_type ANDROID_ICONIFIED, ANDROID_DEICONIFIED, ANDROID_CONTEXT_MENU, + ANDROID_EXPOSE, }; struct android_any_event @@ -332,6 +333,15 @@ struct android_button_event unsigned int button; }; +struct android_expose_event +{ + enum android_event_type type; + unsigned long serial; + android_window window; + int x, y; + int width, height; +}; + struct android_touch_event { /* Type of the event. */ @@ -415,6 +425,7 @@ union android_event struct android_crossing_event xcrossing; struct android_motion_event xmotion; struct android_button_event xbutton; + struct android_expose_event xexpose; /* This has no parallel in X, since the X model of having monotonically increasing touch IDs can't work on Android. */ diff --git a/src/androidselect.c b/src/androidselect.c new file mode 100644 index 00000000000..4a71b376e28 --- /dev/null +++ b/src/androidselect.c @@ -0,0 +1,249 @@ +/* Communication module for Android terminals. + +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 "lisp.h" +#include "blockinput.h" +#include "coding.h" +#include "android.h" +#include "androidterm.h" + +/* Selection support on Android is confined to copying and pasting of + plain text from the clipboard. There is no primary selection. + + While newer versions of Android are supposed to have the necessary + interfaces for transferring other kinds of selection data, doing so + is too complicated, and involves registering ``content providers'' + and all kinds of other stuff. */ + + + +/* Structure describing the EmacsClipboard class. */ + +struct android_emacs_clipboard +{ + jclass class; + jmethodID set_clipboard; + jmethodID owns_clipboard; + jmethodID clipboard_exists; + jmethodID get_clipboard; + jmethodID make_clipboard; +}; + +/* Methods associated with the EmacsClipboard class. */ +static struct android_emacs_clipboard clipboard_class; + +/* Reference to the EmacsClipboard object. */ +static jobject clipboard; + + + +static void +android_init_emacs_clipboard (void) +{ + jclass old; + + clipboard_class.class + = (*android_java_env)->FindClass (android_java_env, + "org/gnu/emacs/EmacsClipboard"); + eassert (clipboard_class.class); + + old = clipboard_class.class; + clipboard_class.class + = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, + old); + ANDROID_DELETE_LOCAL_REF (old); + + if (!clipboard_class.class) + emacs_abort (); + +#define FIND_METHOD(c_name, name, signature) \ + clipboard_class.c_name \ + = (*android_java_env)->GetMethodID (android_java_env, \ + clipboard_class.class, \ + name, signature); \ + assert (clipboard_class.c_name); + + FIND_METHOD (set_clipboard, "setClipboard", "([B)V"); + FIND_METHOD (owns_clipboard, "ownsClipboard", "()I"); + FIND_METHOD (clipboard_exists, "clipboardExists", "()Z"); + FIND_METHOD (get_clipboard, "getClipboard", "()[B"); + + clipboard_class.make_clipboard + = (*android_java_env)->GetStaticMethodID (android_java_env, + clipboard_class.class, + "makeClipboard", + "()Lorg/gnu/emacs/" + "EmacsClipboard;"); + assert (clipboard_class.make_clipboard); + +#undef FIND_METHOD +} + + + + +DEFUN ("android-clipboard-owner-p", Fandroid_clipboard_owner_p, + Sandroid_clipboard_owner_p, 0, 0, 0, + doc: /* Return whether or not Emacs owns the clipboard. +Alternatively, return the symbol `lambda' if that could not be +determined. */) + (void) +{ + jint rc; + + block_input (); + rc = (*android_java_env)->CallIntMethod (android_java_env, + clipboard, + clipboard_class.owns_clipboard); + android_exception_check (); + unblock_input (); + + /* If rc is 0 or 1, then Emacs knows whether or not it owns the + clipboard. If rc is -1, then Emacs does not. */ + + if (rc < 0) + return Qlambda; + + return rc ? Qt : Qnil; +} + +DEFUN ("android-set-clipboard", Fandroid_set_clipboard, + Sandroid_set_clipboard, 1, 1, 0, + doc: /* Set the clipboard text to STRING. */) + (Lisp_Object string) +{ + jarray bytes; + + CHECK_STRING (string); + string = ENCODE_UTF_8 (string); + + bytes = (*android_java_env)->NewByteArray (android_java_env, + SBYTES (string)); + android_exception_check (); + + (*android_java_env)->SetByteArrayRegion (android_java_env, bytes, + 0, SBYTES (string), + (jbyte *) SDATA (string)); + (*android_java_env)->CallVoidMethod (android_java_env, + clipboard, + clipboard_class.set_clipboard, + bytes); + android_exception_check (); + + ANDROID_DELETE_LOCAL_REF (bytes); + return Qnil; +} + +DEFUN ("android-get-clipboard", Fandroid_get_clipboard, + Sandroid_get_clipboard, 0, 0, 0, + doc: /* Return the current contents of the clipboard. +Value is a multibyte string containing decoded clipboard +text. +Alternatively, return nil if the clipboard is empty. */) + (void) +{ + Lisp_Object string; + jarray bytes; + jmethodID method; + size_t length; + jbyte *data; + + method = clipboard_class.get_clipboard; + bytes + = (*android_java_env)->CallObjectMethod (android_java_env, + clipboard, + method); + + if (!bytes) + { + android_exception_check (); + return Qnil; + } + + length = (*android_java_env)->GetArrayLength (android_java_env, + bytes); + data = (*android_java_env)->GetByteArrayElements (android_java_env, + bytes, NULL); + android_exception_check (); + + string = make_unibyte_string ((char *) data, length); + + (*android_java_env)->ReleaseByteArrayElements (android_java_env, + bytes, data, + JNI_ABORT); + ANDROID_DELETE_LOCAL_REF (bytes); + + /* Now decode the resulting string. */ + return code_convert_string_norecord (string, Qutf_8, Qnil); +} + +DEFUN ("android-clipboard-exists-p", Fandroid_clipboard_exists_p, + Sandroid_clipboard_exists_p, 0, 0, 0, + doc: /* Return whether or not clipboard contents exist. */) + (void) +{ + jboolean rc; + jmethodID method; + + method = clipboard_class.clipboard_exists; + rc = (*android_java_env)->CallBooleanMethod (android_java_env, + clipboard, + method); + android_exception_check (); + + return rc ? Qt : Qnil; +} + + + +void +init_androidselect (void) +{ + jobject tem; + jmethodID make_clipboard; + + android_init_emacs_clipboard (); + + make_clipboard = clipboard_class.make_clipboard; + tem + = (*android_java_env)->CallStaticObjectMethod (android_java_env, + clipboard_class.class, + make_clipboard); + if (!tem) + emacs_abort (); + + clipboard = (*android_java_env)->NewGlobalRef (android_java_env, tem); + + if (!clipboard) + emacs_abort (); + + ANDROID_DELETE_LOCAL_REF (tem); +} + +void +syms_of_androidselect (void) +{ + defsubr (&Sandroid_clipboard_owner_p); + defsubr (&Sandroid_set_clipboard); + defsubr (&Sandroid_get_clipboard); + defsubr (&Sandroid_clipboard_exists_p); +} diff --git a/src/androidterm.c b/src/androidterm.c index 1141f50f266..d5dafe9b0d7 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -818,6 +818,28 @@ handle_one_android_event (struct android_display_info *dpyinfo, goto OTHER; + case ANDROID_EXPOSE: + + f = any; + + if (f) + { + if (!FRAME_VISIBLE_P (f)) + { + f->output_data.android->has_been_visible = true; + SET_FRAME_GARBAGED (f); + } + + if (!FRAME_GARBAGED_P (f)) + { + expose_frame (f, event->xexpose.x, event->xexpose.y, + event->xexpose.width, event->xexpose.height); + show_back_buffer (f); + } + } + + goto OTHER; + case ANDROID_BUTTON_PRESS: case ANDROID_BUTTON_RELEASE: /* If we decide we want to generate an event to be seen diff --git a/src/androidterm.h b/src/androidterm.h index 11b24d40b73..8cc31f1ab57 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -432,6 +432,15 @@ extern void sfntfont_android_shrink_scanline_buffer (void); extern void init_sfntfont_android (void); extern void syms_of_sfntfont_android (void); +/* Defined in androidselect.c */ + +#ifndef ANDROID_STUBIFY + +extern void init_androidselect (void); +extern void syms_of_androidselect (void); + +#endif + #define RGB_TO_ULONG(r, g, b) (((r) << 16) | ((g) << 8) | (b)) diff --git a/src/emacs.c b/src/emacs.c index 994a4d1db93..02016e53c45 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -2409,6 +2409,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem syms_of_fontset (); #if !defined ANDROID_STUBIFY syms_of_androidfont (); + syms_of_androidselect (); syms_of_sfntfont (); syms_of_sfntfont_android (); #endif /* !ANDROID_STUBIFY */ @@ -2514,6 +2515,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY init_androidfont (); + init_androidselect (); init_sfntfont (); init_sfntfont_android (); #endif -- cgit v1.3 From 4255d7f0514c5fa1badded6b0bc445ec2d2764c0 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 26 Jan 2023 15:37:04 +0800 Subject: Update Android port * .gitignore: Ignore lib/math.h. * INSTALL.android: Update accordingly. * build-aux/ndk-build-helper-1.mk: * build-aux/ndk-build-helper-2.mk: * build-aux/ndk-build-helper.mk: * build-aux/ndk-module-extract.awk: Handle C++ modules. * configure.ac: Enable libxml2 on Android. * cross/ndk-build/Makefile.in: * cross/ndk-build/ndk-build-shared-library.mk: * cross/ndk-build/ndk-build-static-library.mk: * cross/ndk-build/ndk-build.mk.in: * cross/ndk-build/ndk-resolve.mk: Fix dependency resolution of includes. * java/org/gnu/emacs/EmacsView.java (popupMenu): Fix minimum SDK version for actual popup menus. * lib/math.h: Delete file. * m4/ndk-build.m4 (ndk_SEARCH_MODULE, ndk_CHECK_MODULES): Look for nasm and C++ libraries. * src/android.c (faccessat): Rename to `android_faccessat'. * src/android.h: Update prototypes. * src/dired.c (file_name_completion_dirp): * src/fileio.c (file_access_p, Faccess_file, file_directory_p): * src/lisp.h: * src/lread.c (openp): * src/process.c (allocate_pty): Use sys_faccessat. * src/sysdep.c (sys_faccessat): New function. --- .gitignore | 2 + INSTALL.android | 127 +++++++++++++++++++- build-aux/ndk-build-helper-1.mk | 31 +++-- build-aux/ndk-build-helper-2.mk | 22 +++- build-aux/ndk-build-helper.mk | 35 +++++- build-aux/ndk-module-extract.awk | 30 ++++- configure.ac | 26 +++- cross/ndk-build/Makefile.in | 29 ++++- cross/ndk-build/ndk-build-shared-library.mk | 37 +++++- cross/ndk-build/ndk-build-static-library.mk | 30 +++++ cross/ndk-build/ndk-build.mk.in | 18 +++ cross/ndk-build/ndk-resolve.mk | 31 ++++- java/org/gnu/emacs/EmacsView.java | 5 +- m4/ndk-build.m4 | 180 +++++++++++++++++++++++++++- src/android.c | 24 ++-- src/android.h | 1 + src/dired.c | 3 +- src/fileio.c | 8 +- src/lisp.h | 1 + src/lread.c | 6 +- src/process.c | 3 +- src/sysdep.c | 10 ++ 22 files changed, 598 insertions(+), 61 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/.gitignore b/.gitignore index 6b624767c66..6ca011312e9 100644 --- a/.gitignore +++ b/.gitignore @@ -84,6 +84,7 @@ lib/limits.h lib/malloc/*.gl.h lib/signal.h lib/std*.h +lib/math.h !lib/std*.in.h !lib/stdio-impl.h lib/string.h @@ -115,6 +116,7 @@ cross/lib/signal.h cross/lib/std*.h !cross/lib/std*.in.h !cross/lib/stdio-impl.h +cross/lib/math.h cross/lib/string.h cross/lib/sys/ cross/lib/time.h diff --git a/INSTALL.android b/INSTALL.android index 975a6249bb2..ed0bd0f68dc 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -135,9 +135,19 @@ files, like so: ./configure "--with-ndk-path=directory1 directory2" +If `configure' complains about not being able to find +``libc++_shared.so'', then you must locate that file in your copy of +the NDK, and specify it like so: + + ./configure --with-ndk-cxx-shared=/path/to/sysroot/libc++_shared.so + Emacs will then read the ``Android.mk'' file in each directory, and automatically build and use those modules. +When building for Intel systems, some ``ndk-build'' modules require +the Netwide Assembler, usually installed under ``nasm'', to be present +on the system that is building Emacs. + Google, Inc. has adapted many common Emacs dependencies to use the `ndk-build' system. Here is a non-exhaustive list of what is known to work, along with what has to be patched to make them work: @@ -150,6 +160,16 @@ work, along with what has to be patched to make them work: libjpeg-turbo - https://android.googlesource.com/platform/external/libjpeg-turbo (You must add LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) before its Android.mk includes $(BUILD_SHARED_LIBRARY)) + libxml2 - https://android.googlesource.com/platform/external/libxml2/ + (You must also place the dependency icu4c in ``--with-ndk-path'', + and apply the patch at the end of this file.) + icu4c - https://android.googlesource.com/platform/external/icu/ + (You must apply the patch at the end of this file.) + +Many of these dependencies have been migrated over to the +``Android.bp'' build system now used to build Android itself. +However, the old ``Android.mk'' Makefiles are still present in older +branches, and can be easily adapte to newer versions. In addition, some Emacs dependencies provide `ndk-build' support themselves: @@ -259,10 +279,11 @@ most important of these are: BUILD_EXECUTABLE # build-aux/ndk-build-helper-3.mk CLEAR_VARS # build-aux/ndk-build-helper-4.mk -Then, ``Android.mk'' will include $(CLEAN_VARS) (to clear variables -previously set), set several variables describing each module to the -ndk-build system, and include one of $(BUILD_SHARED_LIBRARY), -$(BUILD_STATIC_LIBRARY) and $(BUILD_EXECUTABLE). +Then, ``Android.mk'' will include $(CLEAN_VARS), possibly other +``Android.mk'' files, (to clear variables previously set), set several +variables describing each module to the ndk-build system, and include +one of $(BUILD_SHARED_LIBRARY), $(BUILD_STATIC_LIBRARY) and +$(BUILD_EXECUTABLE). Each one of those three scripts will then read from the variables set by ``Android.mk'', resolve dependencies, and print out some text @@ -300,6 +321,11 @@ The output is arranged as follows: that will build the final shared object or library archive of this module, along with all of its dependencies. + - The seventh line is either empty, or the name of a dependency on + the C++ standard library. This is used to determine whether or + not Emacs will include the C++ standard library in the application + package. + The output from Make is given to an awk script, build-aux/ndk-module-extract.awk. This is responsible for parsing the that output and filtering out modules other than what is being built: @@ -315,6 +341,8 @@ module_includes="-I/opt/android/libpng" module_cflags="" module_ldflags=" -L/opt/emacs/cross/ndk-build -l:libpng_emacs.so" module_target="libpng_emacs.so" +module_cxx_deps="" +module_imports="" which is then evaluated by `configure'. Once the variable `module_name' is set, configure apends the remaining @@ -322,6 +350,37 @@ $(module_includes), $(module_cflags) and $(module_ldflags) to the module's CFLAGS and LIBS variables, and appends the list of Makefile targets specified to the variable NDK_BUILD_MODULES. +In some cases, an ``Android.mk'' file may chose to import a module +defined in ``--with-ndk-path'', but not defined inside its own +``Android.mk'' file. build-aux/ndk-build-helper.mk defines the +`import-module' function to add the modules being imported to a +variable, which is then printed out after ``ndk-build-helper.mk'' +completes. For example, libxml2 imports the ``libicucc'' module, +which results in the following text being printed: + +Building shared +libxml2 +/home/oldosfan/libxml2/SAX.c /home/oldosfan/libxml2/entities.c /home/oldosfan/libxml2/encoding.c /home/oldosfan/libxml2/error.c /home/oldosfan/libxml2/parserInternals.c /home/oldosfan/libxml2/parser.c /home/oldosfan/libxml2/tree.c /home/oldosfan/libxml2/hash.c /home/oldosfan/libxml2/list.c /home/oldosfan/libxml2/xmlIO.c /home/oldosfan/libxml2/xmlmemory.c /home/oldosfan/libxml2/uri.c /home/oldosfan/libxml2/valid.c /home/oldosfan/libxml2/xlink.c /home/oldosfan/libxml2/debugXML.c /home/oldosfan/libxml2/xpath.c /home/oldosfan/libxml2/xpointer.c /home/oldosfan/libxml2/xinclude.c /home/oldosfan/libxml2/DOCBparser.c /home/oldosfan/libxml2/catalog.c /home/oldosfan/libxml2/globals.c /home/oldosfan/libxml2/threads.c /home/oldosfan/libxml2/c14n.c /home/oldosfan/libxml2/xmlstring.c /home/oldosfan/libxml2/buf.c /home/oldosfan/libxml2/xmlregexp.c /home/oldosfan/libxml2/xmlschemas.c /home/oldosfan/libxml2/xmlschemastypes.c /home/oldosfan/libxml2/xmlunicode.c /home/oldosfan/libxml2/xmlreader.c /home/oldosfan/libxml2/relaxng.c /home/oldosfan/libxml2/dict.c /home/oldosfan/libxml2/SAX2.c /home/oldosfan/libxml2/xmlwriter.c /home/oldosfan/libxml2/legacy.c /home/oldosfan/libxml2/chvalid.c /home/oldosfan/libxml2/pattern.c /home/oldosfan/libxml2/xmlsave.c /home/oldosfan/libxml2/xmlmodule.c /home/oldosfan/libxml2/schematron.c /home/oldosfan/libxml2/SAX.c /home/oldosfan/libxml2/entities.c /home/oldosfan/libxml2/encoding.c /home/oldosfan/libxml2/error.c /home/oldosfan/libxml2/parserInternals.c /home/oldosfan/libxml2/parser.c /home/oldosfan/libxml2/tree.c /home/oldosfan/libxml2/hash.c /home/oldosfan/libxml2/list.c /home/oldosfan/libxml2/xmlIO.c /home/oldosfan/libxml2/xmlmemory.c /home/oldosfan/libxml2/uri.c /home/oldosfan/libxml2/valid.c /home/oldosfan/libxml2/xlink.c /home/oldosfan/libxml2/debugXML.c /home/oldosfan/libxml2/xpath.c /home/oldosfan/libxml2/xpointer.c /home/oldosfan/libxml2/xinclude.c /home/oldosfan/libxml2/DOCBparser.c /home/oldosfan/libxml2/catalog.c /home/oldosfan/libxml2/globals.c /home/oldosfan/libxml2/threads.c /home/oldosfan/libxml2/c14n.c /home/oldosfan/libxml2/xmlstring.c /home/oldosfan/libxml2/buf.c /home/oldosfan/libxml2/xmlregexp.c /home/oldosfan/libxml2/xmlschemas.c /home/oldosfan/libxml2/xmlschemastypes.c /home/oldosfan/libxml2/xmlunicode.c /home/oldosfan/libxml2/xmlreader.c /home/oldosfan/libxml2/relaxng.c /home/oldosfan/libxml2/dict.c /home/oldosfan/libxml2/SAX2.c /home/oldosfan/libxml2/xmlwriter.c /home/oldosfan/libxml2/legacy.c /home/oldosfan/libxml2/chvalid.c /home/oldosfan/libxml2/pattern.c /home/oldosfan/libxml2/xmlsave.c /home/oldosfan/libxml2/xmlmodule.c /home/oldosfan/libxml2/schematron.c + + + -L/home/oldosfan/emacs-dev/emacs-android/cross/ndk-build -l:libxml2_emacs.so -l:libicuuc_emacs.so +libxml2_emacs.so libicuuc_emacs.so +End +Start Imports +libicuuc +End Imports + +Upon encountering the ``Start Imports'' section, +build-aux/ndk-module-extract.awk collects all imports until it +encounters the line ``End Imports'', at which point it prints: + +module_imports="libicuuc" + +Then, if the list of imports is not empty, ndk_CHECK_MODULES +additionally calls itself for each import before appending the +module's own ``Android.mk'', ensuring that the module's imported +dependencies are included by $ndk_DIR/Makefile before itself. + Finally, immediately before generating src/Makefile.android, configure expands: @@ -473,3 +532,63 @@ 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 . + + + +PATCH FOR LIBXML2 + +This patch must be applied to the Android.mk in Google's version of +libxml2 before it can be built for Emacs. In addition, you must also +revert the commit `edb5870767fed8712a9b77ef34097209b61ab2db'. + +diff --git a/Android.mk b/Android.mk +index 07c7b372..24f67e49 100644 +--- a/Android.mk ++++ b/Android.mk +@@ -80,6 +80,7 @@ LOCAL_SHARED_LIBRARIES := libicuuc + LOCAL_MODULE:= libxml2 + LOCAL_CLANG := true + LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk ++LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH) + include $(BUILD_SHARED_LIBRARY) + + # For the host +@@ -94,3 +95,5 @@ LOCAL_MODULE := libxml2 + LOCAL_CLANG := true + LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk + include $(BUILD_HOST_STATIC_LIBRARY) ++ ++$(call import-module,libicuuc) + +PATCH FOR ICU + +This patch must be applied to icu4j/Android.mk in Google's version of +icu before it can be built for Emacs. + +diff --git a/icu4j/Android.mk b/icu4j/Android.mk +index d1ab3d5..69eff81 100644 +--- a/icu4j/Android.mk ++++ b/icu4j/Android.mk +@@ -69,7 +69,7 @@ include $(BUILD_STATIC_JAVA_LIBRARY) + # Path to the ICU4C data files in the Android device file system: + icu4c_data := /system/usr/icu + icu4j_config_root := $(LOCAL_PATH)/main/classes/core/src +-include external/icu/icu4j/adjust_icudt_path.mk ++include $(LOCAL_PATH)/adjust_icudt_path.mk + + include $(CLEAR_VARS) + LOCAL_SRC_FILES := $(icu4j_src_files) + +diff --git a/icu4c/source/common/Android.mk b/icu4c/source/common/Android.mk +index 8e5f757..44bb130 100644 +--- a/icu4c/source/common/Android.mk ++++ b/icu4c/source/common/Android.mk +@@ -231,7 +231,7 @@ include $(CLEAR_VARS) + LOCAL_SRC_FILES += $(src_files) + LOCAL_C_INCLUDES += $(c_includes) $(optional_android_logging_includes) + LOCAL_CFLAGS += $(local_cflags) -DPIC -fPIC +-LOCAL_SHARED_LIBRARIES += libdl $(optional_android_logging_libraries) ++LOCAL_SHARED_LIBRARIES += libdl libstdc++ $(optional_android_logging_libraries) + LOCAL_MODULE_TAGS := optional + LOCAL_MODULE := libicuuc + LOCAL_RTTI_FLAG := -frtti diff --git a/build-aux/ndk-build-helper-1.mk b/build-aux/ndk-build-helper-1.mk index 86e3ebd6558..4ffe0d423e4 100644 --- a/build-aux/ndk-build-helper-1.mk +++ b/build-aux/ndk-build-helper-1.mk @@ -30,13 +30,12 @@ NDK_A_NAMES = # on to recurse over libraries. NDK_$(LOCAL_MODULE)_STATIC_LIBRARIES := $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES) NDK_$(LOCAL_MODULE)_SHARED_LIBRARIES := $(LOCAL_SHARED_LIBRARIES) +NDK_$(LOCAL_MODULE)_EXPORT_INCLUDES := $(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES) $(info Building $(build_kind)) $(info $(LOCAL_MODULE)) -$(info $(addprefix $(ANDROID_MODULE_DIRECTORY)/,$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) +$(info $(addprefix $(LOCAL_PATH)/,$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) -$(info $(foreach dir,$(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES),-I$(dir))) -$(info $(LOCAL_EXPORT_CFLAGS)) ifeq ($(LOCAL_MODULE_FILENAME),) ifeq ($(findstring lib,$(LOCAL_MODULE)),lib) NDK_SO_NAMES = $(LOCAL_MODULE)_emacs.so @@ -62,15 +61,31 @@ $$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARI endif endef -# Resolve additional dependencies based on LOCAL_STATIC_LIBRARIES and -# LOCAL_SHARED_LIBRARIES. Static library dependencies can be ignored -# while building a shared library, as they will be linked in to the -# resulting shared object file later. +# Figure out includes from dependencies as well. +NDK_INCLUDES := $(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES) + +define add-includes +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)))) +endif +endef + +# Resolve additional dependencies and their export includes based on +# LOCAL_STATIC_LIBRARIES and LOCAL_SHARED_LIBRARIES. Static library +# dependencies can be ignored while building a shared library, as they +# will be linked in to the resulting shared object file later. -SYSTEM_LIBRARIES = z libz libc c +SYSTEM_LIBRARIES = z libz libc c libdl dl stdc++ libstdc++ $(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)))) + +$(info $(foreach dir,$(NDK_INCLUDES),-I$(dir))) +$(info $(LOCAL_EXPORT_CFLAGS)) $(info $(LOCAL_EXPORT_LDFLAGS) $(abspath $(addprefix $(NDK_BUILD_DIR)/,$(NDK_A_NAMES))) -L$(abspath $(NDK_BUILD_DIR)) $(foreach soname,$(NDK_SO_NAMES),-l:$(soname))) $(info $(NDK_SO_NAMES)) +$(info $(filter %stdc++,$(LOCAL_SHARED_LIBRARIES))) $(info End) diff --git a/build-aux/ndk-build-helper-2.mk b/build-aux/ndk-build-helper-2.mk index bd50e8b3efa..9c9e7cf09a9 100644 --- a/build-aux/ndk-build-helper-2.mk +++ b/build-aux/ndk-build-helper-2.mk @@ -24,13 +24,12 @@ NDK_A_NAMES = # on to recurse over libraries. NDK_$(LOCAL_MODULE)_STATIC_LIBRARIES := $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES) NDK_$(LOCAL_MODULE)_SHARED_LIBRARIES := $(LOCAL_SHARED_LIBRARIES) +NDK_$(LOCAL_MODULE)_EXPORT_INCLUDES := $(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES) $(info Building $(build_kind)) $(info $(LOCAL_MODULE)) -$(info $(addprefix $(ANDROID_MODULE_DIRECTORY)/,$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) +$(info $(addprefix $(LOCAL_PATH)/,$(LOCAL_SRC_FILES) $(LOCAL_SRC_FILES$(EMACS_ABI)))) -$(info $(foreach dir,$(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES),-I$(dir))) -$(info $(LOCAL_EXPORT_CFLAGS)) ifeq ($(LOCAL_MODULE_FILENAME),) ifeq ($(findstring lib,$(LOCAL_MODULE)),lib) @@ -74,14 +73,29 @@ $$(foreach module,$$(filter-out $$(SYSTEM_LIBRARIES), $$(NDK_$(1)_SHARED_LIBRARI endif endef +# Figure out includes from dependencies as well. +NDK_INCLUDES := $(LOCAL_EXPORT_C_INCLUDE_DIRS) $(LOCAL_EXPORT_C_INCLUDES) + +define add-includes +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)))) +endif +endef + # Resolve additional dependencies based on LOCAL_STATIC_LIBRARIES and # LOCAL_SHARED_LIBRARIES. -SYSTEM_LIBRARIES = z libz +SYSTEM_LIBRARIES = z libz libc c libdl dl libstdc++ stdc++ $(foreach module,$(filter-out $(SYSTEM_LIBRARIES), $(LOCAL_STATIC_LIBRARIES) $(LOCAL_WHOLE_STATIC_LIBRARIES)),$(eval $(call add-a-name,$(module)))) $(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)))) +$(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 $(filter %stdc++,$(LOCAL_SHARED_LIBRARIES))) $(info End) diff --git a/build-aux/ndk-build-helper.mk b/build-aux/ndk-build-helper.mk index beb32cae3f1..886459b1378 100644 --- a/build-aux/ndk-build-helper.mk +++ b/build-aux/ndk-build-helper.mk @@ -21,20 +21,38 @@ # See the text under ``NDK BUILD SYSTEM IMPLEMENTATION'' in # INSTALL.android for more details. +# NDK_LAST_MAKEFILE is the last Makefile that was included. +NDK_LAST_MAKEFILE = $(lastword $(filter %Android.mk,$(MAKEFILE_LIST))) + +# local-makefile is the current Makefile being loaded. +local-makefile = $(NDK_LAST_MAKEFILE) + # Make NDK_BUILD_DIR absolute. NDK_BUILD_DIR := $(absname $(NDK_BUILD_DIR)) -# my-dir is a function that returns the Android module directory. -my-dir = $(ANDROID_MODULE_DIRECTORY) +# Make EMACS_SRCDIR absolute. This must be absolute, or nested +# Android.mk files will not be able to find CLEAR_VARS. +EMACS_SRCDIR := $(absname $(EMACS_SRCDIR)) + +# my-dir is a function that returns the Android module directory. If +# no Android.mk has been loaded, use ANDROID_MODULE_DIRECTORY. +my-dir = $(or $(and $(local-makefile),$(dir $(local-makefile))),$(ANDROID_MODULE_DIRECTORY)) + +# Return all Android.mk files under the first arg. +all-makefiles-under = $(wildcard $(1)/*/Android.mk) -# all-subdir-makefiles is a function which returns all Android.mk -# files within this directory. -all-subdir-makefiles = $(shell find . -name "Android.mk") +# Return all Android.mk files in subdirectories of this Makefile's +# location. +all-subdir-makefiles = $(call all-makefiles-under,$(call my-dir)) # These functions are not implemented. parent-makefile = grand-parent-makefile = -import-module = + +NDK_IMPORTS := + +# Add the specified module (arg 1) to NDK_IMPORTS. +import-module = $(eval NDK_IMPORTS += $(1)) # Print out module information every time BUILD_SHARED_LIBRARY is # called. @@ -48,5 +66,10 @@ CLEAR_VARS=$(EMACS_SRCDIR)/build-aux/ndk-build-helper-4.mk include $(ANDROID_MAKEFILE) +# Finally, print out the imports. +$(info Start Imports) +$(info $(NDK_IMPORTS)) +$(info End Imports) + # Dummy target. all: diff --git a/build-aux/ndk-module-extract.awk b/build-aux/ndk-module-extract.awk index eaea3e96dd8..681f20237fa 100644 --- a/build-aux/ndk-module-extract.awk +++ b/build-aux/ndk-module-extract.awk @@ -2,13 +2,27 @@ kind = $2 } +/^Start Imports$/ { + imports = 1 +} + // { - if (!match ($0, /^End$/) && !match ($0, /^Building.+$/)) + if (imports && ++imports > 2) + { + if (!match ($0, /^End Imports$/)) + makefile_imports = makefile_imports " " $1 + } + else if (!match ($0, /^End$/) && !match ($0, /^Building.+$/)) { if (kind) { - if (ldflags_found) - target = $0 + if (target_found) + cxx_deps = $0 + else if (ldflags_found) + { + target = $0 + target_found = 1 + } else if (cflags_found) { ldflags = $0 @@ -48,6 +62,7 @@ printf "module_cflags=\"%s\"\n", cflags printf "module_ldflags=\"%s\"\n", ldflags printf "module_target=\"%s\"\n", target + printf "module_cxx_deps=\"%s\"\n", cxx_deps } src = "" @@ -61,4 +76,13 @@ includes_found = "" cflags_found = "" ldflags_found = "" + target_found = "" +} + +/^End Imports$/ { + imports = "" + # Strip off leading whitespace. + gsub (/^[ \t]+/, "", makefile_imports) + printf "module_imports=\"%s\"\n", makefile_imports + makefile_imports = "" } diff --git a/configure.ac b/configure.ac index 983fa3ebab1..95bb0cfca88 100644 --- a/configure.ac +++ b/configure.ac @@ -1041,12 +1041,16 @@ package will likely install on older systems but crash on startup.]) mv -f confdefs.h _confdefs.h mv -f config.log _config.log + # Make sure these files are removed upon exit. + trap "rm -rf _confdefs.h _config.log" 0 + # Figure out what --with-FOO options to pass through. passthrough="$passthrough --with-png=$with_png" passthrough="$passthrough --with-webp=$with_webp" passthrough="$passthrough --with-gif=$with_gif" passthrough="$passthrough --with-json=$with_json" passthrough="$passthrough --with-jpeg=$with_jpeg" + passthrough="$passthrough --with-xml2=$with_xml2" AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" \ ANDROID_SDK="$android_sdk" android_abi=$android_abi \ @@ -1115,9 +1119,9 @@ if test "$ANDROID" = "yes"; then with_gif=no with_json=no with_jpeg=no + with_xml2=no fi - with_xml2=no with_rsvg=no with_sqlite3=no with_lcms2=no @@ -2256,6 +2260,18 @@ AC_DEFUN([EMACS_CHECK_MODULES], m4_default([$3], [HAVE_$1=yes])], [m4_default([$4], [HAVE_$1=no])])])]) +dnl EMACS_CHECK_LIB(NAME, FUNCTION, ACTION-IF-FOUND, ACTION-IF-NOT-FOUND, +dnl OTHER-LIBRARIES, INCLUDES) +dnl --------------------------------------------------------------------- +dnl This is like AC_CHECK_LIB; however, there is no default action, and +dnl when cross-configuring for Android, AC_CHECK_DECLS is called with NAME +dnl and INCLUDES instead, as the library being checked against will likely +dnl be built together with Emacs. +AC_DEFUN([EMACS_CHECK_LIB], + [AS_IF([test -n "$ndk_INITIALIZED"], + [AC_CHECK_DECL([$2], [$3], [$4], [$6])], + [AC_CHECK_LIB([$1], [$2], [$3], [$4], [$5])])]) + HAVE_SOUND=no if test "${with_sound}" != "no"; then # Sound support for GNU/Linux, the free BSDs, MinGW, and Cygwin. @@ -3323,7 +3339,6 @@ if test "${with_webp}" != "no"; then fi if test $HAVE_WEBP = yes; then AC_DEFINE([HAVE_WEBP], [1], [Define to 1 if using libwebp.]) - CFLAGS="$CFLAGS $WEBP_CFLAGS" # Windows loads libwebp dynamically if test "${opsys}" = "mingw32"; then WEBP_LIBS= @@ -5400,10 +5415,13 @@ if test "${with_xml2}" != "no"; then fi if test "${HAVE_LIBXML2}" = "yes"; then if test "${opsys}" != "mingw32"; then - AC_CHECK_LIB([xml2], [htmlReadMemory], + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS $LIBXML2_CFLAGS" + EMACS_CHECK_LIB([xml2], [htmlReadMemory], [HAVE_LIBXML2=yes], [HAVE_LIBXML2=no], - [$LIBXML2_LIBS]) + [$LIBXML2_LIBS], [#include ]) + CFLAGS="$SAVE_CFLAGS" else LIBXML2_LIBS="" fi diff --git a/cross/ndk-build/Makefile.in b/cross/ndk-build/Makefile.in index ec8e6205680..125c825e506 100644 --- a/cross/ndk-build/Makefile.in +++ b/cross/ndk-build/Makefile.in @@ -30,6 +30,7 @@ NDK_BUILD_ANDROID_MK = @NDK_BUILD_ANDROID_MK@ NDK_BUILD_SDK = @NDK_BUILD_SDK@ NDK_BUILD_CC = @NDK_BUILD_CC@ NDK_BUILD_AR = @NDK_BUILD_AR@ + NDK_BUILD_NASM = @NDK_BUILD_NASM@ # This is a list of targets to build. NDK_BUILD_MODULES = @NDK_BUILD_MODULES@ @@ -49,13 +50,33 @@ endef # Remove duplicate files. NDK_BUILD_ANDROID_MK := $(call uniqify,$(NDK_BUILD_ANDROID_MK)) +# Remove duplicate modules as well. These can occur when a single +# module imports a module and also declares it in +# LOCAL_SHARED_LIBRARIES. +NDK_BUILD_MODULES := $(call uniqify,$(NDK_BUILD_MODULES)) + define subr-1 -# Define ndk-build functions. +# Define ndk-build functions. Many of these are identical to those in +# build-aux/ndk-build-helper.mk. -define my-dir -$(dir $(abspath $(1))) -endef +# NDK_LAST_MAKEFILE is the last Makefile that was included. +NDK_LAST_MAKEFILE = $$(lastword $$(filter %Android.mk,$$(MAKEFILE_LIST))) + +# local-makefile is the current Makefile being loaded. +local-makefile = $$(NDK_LAST_MAKEFILE) + +# my-dir is a function that returns the Android module directory. If +# no Android.mk has been loaded, use the directory of the Makefile +# being included. +my-dir = $$(or $$(and $$(local-makefile),$$(dir $$(local-makefile))),$(dir $(1))) + +# Return all Android.mk files under the first arg. +all-makefiles-under = $$(wildcard $$(1)/*/Android.mk) + +# Return all Android.mk files in subdirectories of this Makefile's +# location. +all-subdir-makefiles = $$(call all-makefiles-under,$$(call my-dir)) # NDK-defined include variables. diff --git a/cross/ndk-build/ndk-build-shared-library.mk b/cross/ndk-build/ndk-build-shared-library.mk index 6a607049a9c..a4b7b47f749 100644 --- a/cross/ndk-build/ndk-build-shared-library.mk +++ b/cross/ndk-build/ndk-build-shared-library.mk @@ -22,6 +22,9 @@ eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) objname = $(1)-$(subst /,_,$(2).o) +# Here are the default flags to link shared libraries with. +NDK_SO_DEFAULT_LDFLAGS := -lc -lm + define single-object-target ifeq (x$(suffix $(1)),x.c) @@ -29,6 +32,12 @@ ifeq (x$(suffix $(1)),x.c) $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) +else +ifeq (x$(suffix $(1)),x.$(or $(LOCAL_CPP_EXTENSION),cpp)) + +$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) + $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) $(NDK_CXXFLAGS_$(LOCAL_MODULE)) + else ifneq ($(or $(call eq,x$(suffix $(1)),x.s),$(call eq,x$(suffix $(1)),x.S)),) @@ -36,7 +45,19 @@ $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_ASFLAGS_$(LOCAL_MODULE)) else +ifneq (x$(suffix $(1)),x.asm) $$(error Unsupported suffix: $(suffix $(1))) +else +ifeq ($(findstring x86,$(NDK_BUILD_ARCH)),) +$$(error Trying to build nasm file on non-Intel platform!) +else + +$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) + $(NDK_BUILD_NASM) -o $$@ -i $(LOCAL_PATH) -i $$(dir $$<) $(NDK_ASFLAGS_$(LOCAL_MODULE)) $$< + +endif +endif +endif endif endif @@ -47,9 +68,21 @@ endef # Make sure to not add a prefix to local includes that already specify # $(LOCAL_PATH). NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(LOCAL_C_INCLUDES)) -NDK_CFLAGS_$(LOCAL_MODULE) += -fPIC -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS$(NDK_BUILD_ARCH)) +NDK_CFLAGS_$(LOCAL_MODULE) += -fPIC -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH)) NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) $(LOCAL_LDFLAGS) +NDK_CXXFLAGS_$(LOCAL_MODULE) := $(LOCAL_CPPFLAGS) $(LOCAL_RTTI_FLAG) + +# Now look for features in LOCAL_CPP_FEATURES and enable them. + +ifneq ($(findstring exceptions,$(LOCAL_CPPFLAGS)),) +NDK_CXXFLAGS_$(LOCAL_MODULE) += -fexceptions +endif + +ifneq ($(findstring rtti,$(LOCAL_CPPFLAGS)),) +NDK_CXXFLAGS_$(LOCAL_MODULE) += -frtti +endif + ALL_OBJECT_FILES$(LOCAL_MODULE) := ifeq ($(NDK_BUILD_ARCH)$(NDK_ARM_MODE),armarm) @@ -91,7 +124,7 @@ $(foreach source,$(ALL_SOURCE_FILES),$(eval $(call single-object-target,$(source 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_BUILD_CC) $(1) $(2) -o $$@ -shared $(NDK_LDFLAGS$(LOCAL_MODULE)) $(NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE)) $(NDK_SO_DEFAULT_LDFLAGS) 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 2e4893a997b..4d16d81330c 100644 --- a/cross/ndk-build/ndk-build-static-library.mk +++ b/cross/ndk-build/ndk-build-static-library.mk @@ -29,6 +29,12 @@ ifeq (x$(suffix $(1)),x.c) $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) +else +ifeq (x$(suffix $(1)),x.$(or $(LOCAL_CPP_EXTENSION),cpp)) + +$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) + $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) $(NDK_CXXFLAGS_$(LOCAL_MODULE)) + else ifneq ($(or $(call eq,x$(suffix $(1)),x.s),$(call eq,x$(suffix $(1)),x.S)),) @@ -36,7 +42,19 @@ $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_ASFLAGS_$(LOCAL_MODULE)) else +ifneq (x$(suffix $(1)),x.asm) $$(error Unsupported suffix: $(suffix $(1))) +else +ifeq ($(findstring x86,$(NDK_BUILD_ARCH)),) +$$(error Trying to build nasm file on non-Intel platform!) +else + +$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) + $(NDK_BUILD_NASM) -o $$@ -i $(LOCAL_PATH) -i $$(dir $$<) $(NDK_ASFLAGS_$(LOCAL_MODULE)) $$< + +endif +endif +endif endif endif @@ -47,8 +65,20 @@ NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(LOCAL_C_INCLUDES)) NDK_CFLAGS_$(LOCAL_MODULE) += -fPIC -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH)) NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) $(LOCAL_LDFLAGS) +NDK_CXXFLAGS_$(LOCAL_MODULE) := $(LOCAL_CPPFLAGS) $(LOCAL_RTTI_FLAG) ALL_OBJECT_FILES$(LOCAL_MODULE) := +# Now look for features in LOCAL_CPP_FEATURES and enable them. + +ifneq ($(findstring exceptions,$(LOCAL_CPPFLAGS)),) +NDK_CXXFLAGS_$(LOCAL_MODULE) += -fexceptions +endif + +ifneq ($(findstring rtti,$(LOCAL_CPPFLAGS)),) +NDK_CXXFLAGS_$(LOCAL_MODULE) += -frtti +endif + + ifeq ($(NDK_BUILD_ARCH)$(NDK_ARM_MODE),armarm) NDK_CFLAGS ::= -marm else diff --git a/cross/ndk-build/ndk-build.mk.in b/cross/ndk-build/ndk-build.mk.in index 6ad577ccc35..798dcf6c19e 100644 --- a/cross/ndk-build/ndk-build.mk.in +++ b/cross/ndk-build/ndk-build.mk.in @@ -20,9 +20,27 @@ # This file is included all over the place to build prerequisites. NDK_BUILD_MODULES = @NDK_BUILD_MODULES@ +NDK_BUILD_CXX_SHARED = @NDK_BUILD_CXX_SHARED@ +NDK_BUILD_ANY_CXX_MODULE = @NDK_BUILD_ANY_CXX_MODULE@ NDK_BUILD_SHARED = NDK_BUILD_STATIC = +define uniqify +$(if $1,$(firstword $1) $(call uniqify,$(filter-out $(firstword $1),$1))) +endef + +# Remove duplicate modules. These can occur when a single module +# imports a module and also declares it in LOCAL_SHARED_LIBRARIES. +NDK_BUILD_MODULES := $(call uniqify,$(NDK_BUILD_MODULES)) + +# The C++ standard library must be extracted from the Android NDK +# directories and included in the application package, if any module +# requires the C++ standard library. + +ifneq ($(NDK_BUILD_ANY_CXX_MODULE),) +NDK_BUILD_SHARED += $(NDK_BUILD_ANY_CXX_SHARED) +endif + define subr-1 $(top_builddir)/cross/ndk-build/$(1): diff --git a/cross/ndk-build/ndk-resolve.mk b/cross/ndk-build/ndk-resolve.mk index 7fe807e35cc..910be8dab53 100644 --- a/cross/ndk-build/ndk-resolve.mk +++ b/cross/ndk-build/ndk-resolve.mk @@ -25,9 +25,10 @@ NDK_LOCAL_STATIC_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_STATIC_LIBRARIES) $(LOCAL_ NDK_LOCAL_WHOLE_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_WHOLE_STATIC_LIBRARIES) NDK_LOCAL_SHARED_LIBRARIES_$(LOCAL_MODULE) := $(LOCAL_SHARED_LIBRARIES) NDK_LOCAL_EXPORT_CFLAGS_$(LOCAL_MODULE) := $(LOCAL_EXPORT_CFLAGS) -NDK_LOCAL_EXPORT_C_INCLUDES_$(LOCAL_MODULE) := $(LOCAL_EXPORT_C_INCLUDES) +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_SO_EXTRA_FLAGS_$(LOCAL_MODULE) := # List of all dependencies resolved for this module thus far. # Used to avoid infinite recursion. @@ -40,6 +41,34 @@ NDK_RESOLVED_$(LOCAL_MODULE) += $(1) NDK_CFLAGS_$(LOCAL_MODULE) += $(NDK_LOCAL_EXPORT_CFLAGS_$(1)) NDK_CFLAGS_$(LOCAL_MODULE) += $(addprefix -I,$(NDK_LOCAL_EXPORT_C_INCLUDES_$(1))) +# If the module happens to be zlib, then add -lz to the shared library +# flags. +ifneq ($(strip $(1)),libz) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lz +endif + +ifneq ($(strip $(1)),z) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lz +endif + +# Likewise for libdl. +ifneq ($(strip $(1)),libdl) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -ldl +endif + +ifneq ($(strip $(1)),dl) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -ldl +endif + +# Likewise for libstdc++. +ifneq ($(strip $(1)),libstdc++) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lstdc++ +endif + +ifneq ($(strip $(1)),dl) +NDK_SO_EXTRA_FLAGS_$(LOCAL_MODULE) += -lstdc++ +endif + ifneq ($(2),) ifneq ($(findstring lib,$(1)),) NDK_LOCAL_A_NAMES_$(LOCAL_MODULE) += $(1).a diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 881cbc363ba..a1953f683bd 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -531,8 +531,9 @@ public class EmacsView extends ViewGroup contextMenu = menu; popupActive = true; - /* On API 21 or later, use showContextMenu (float, float). */ - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) + /* Use showContextMenu (float, float) on N to get actual popup + behavior. */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) return showContextMenu ((float) xPosition, (float) yPosition); else return showContextMenu (); diff --git a/m4/ndk-build.m4 b/m4/ndk-build.m4 index d3614a9f490..27092e4b400 100644 --- a/m4/ndk-build.m4 +++ b/m4/ndk-build.m4 @@ -21,6 +21,10 @@ AC_ARG_WITH([ndk_path], [AS_HELP_STRING([--with-ndk-path], [find Android libraries in these directories])]) +AC_ARG_WITH([ndk_cxx_shared], + [AS_HELP_STRING([--with-ndk-cxx-shared], + [name of the C++ standard library included with the NDK])]) + # ndk_INIT(ABI, API, DIR) # -------- # Initialize the Android NDK. ABI is the ABI being built for. @@ -45,6 +49,7 @@ ndk_MAKEFILES= ndk_INITIALIZED=yes ndk_API=$2 ndk_DIR=$3 +ndk_ANY_CXX= case "$ndk_ABI" in *arm64* ) @@ -103,6 +108,62 @@ ndk_parse_pkg_config_string () { done } +# Resolve $1, a single import. Prepend its makefile to ndk_MAKEFILES +# if found. Also, prepend all includes to the variable +# ndk_import_includes. + +ndk_resolve_import_module () { + module_name= + ndk_module=[$]1 + + AC_MSG_CHECKING([for imported $ndk_module]) + + for ndk_android_mk in $ndk_module_files; do + # Read this Android.mk file. Set NDK_ROOT to /tmp: the Android in + # tree build system sets it to a meaning value, but build files + # just use it to test whether or not the NDK is being used. + ndk_commands=$($MAKE -s -f build-aux/ndk-build-helper.mk EMACS_SRCDIR=. \ + EMACS_ABI=$ndk_ABI ANDROID_MAKEFILE="$ndk_android_mk" \ + ANDROID_MODULE_DIRECTORY=$(dirname "$ndk_android_mk") \ + NDK_BUILD_DIR="$ndk_DIR" NDK_ROOT="/tmp" \ + | awk -f build-aux/ndk-module-extract.awk \ + MODULE="$ndk_module") + + AS_IF([test -n "${ndk_commands//\n }"], [eval "$ndk_commands"]) + + if test -n "$module_name"; then + break; + fi + done + + if test -z "$module_name"; then + AC_MSG_RESULT([no]) + AC_MSG_ERROR([The module currently being built depends on [$]1, but \ +that could not be found in the list of directories specified in \ +`--with-ndk-path'.]) + fi + + if test -n "$module_cxx_deps"; then + ndk_ANY_CXX=yes + fi + + if test "$ndk_ANY_CXX" = "yes" && test -z "$with_ndk_cxx_shared"; then + AC_MSG_ERROR([The module [$]1 requires the C++ standard library \ +(libc++_shared.so), but it was not found.]) + fi + + AC_MSG_RESULT([yes]) + + # Make sure the module is prepended. + ndk_MAKEFILES="$ndk_android_mk $ndk_MAKEFILES" + ndk_import_includes="$module_includes $ndk_import_includes" + + # Now recursively resolve this module's imports. + for ndk_module in $module_imports; do + ndk_resolve_import_module $ndk_module + done +} + # Look for a suitable ar in the same directory as the C compiler. ndk_where_cc=$(which $CC) ndk_ar_search_path=$PATH @@ -117,15 +178,102 @@ if test -z "$AR"; then AC_PATH_PROGS([AR], [$host_alias-ar llvm-ar], [], [$ndk_ar_search_path]) fi +NDK_BUILD_NASM= + +# Next, try to find nasm on x86. This doesn't ship with the NDK. +if test "$ndk_ARCH" = "x86" || test "$ndk_ARCH" = "x86_64"; then + AC_CHECK_PROGS([NDK_BUILD_NASM], [nasm]) +fi + +# Look for a file named ``libc++_shared.so'' in a subdirectory of +# $ndk_where_cc if it was not specified. +AC_MSG_CHECKING([for libc++_shared.so]) + +ndk_where_toolchain= +if test -z "$with_ndk_cxx_shared" && test -n "$ndk_where_cc"; then + # Find the NDK root directory. Go to $ndk_where_cc. + SAVE_PWD=`pwd` + cd $(dirname "$ndk_where_cc") + + # Now, keep moving backwards until pwd ends with ``toolchains''. + while :; do + if test "`pwd`" = "/"; then + cd "$SAVE_PWD" + break + fi + + if test "`basename $(pwd)`" = "toolchains"; then + ndk_where_toolchain=`pwd` + cd "$SAVE_PWD" + break + fi + + cd .. + done + + ndk_matching_libcxx_shared_so= + + # The toolchain directory should be in "$ndk_where_toolchain". + if test -n "$ndk_where_toolchain"; then + # Now, look in the directory behind it. + ndk_cxx_shared_so=`find "$ndk_where_toolchain" -name libc++_shared.so` + + # Look for one with the correct architecture. + for ndk_candidate in $ndk_cxx_shared_so; do + case "$ndk_candidate" in + *arm-linux-android* ) + if test "$ndk_ARCH" = "arm"; then + ndk_matching_libcxx_shared_so=$ndk_candidate + fi + ;; + *aarch64-linux-android* ) + if test "$ndk_ARCH" = "arm64"; then + ndk_matching_libcxx_shared_so=$ndk_candidate + fi + ;; + *i[[3-6]]86-linux-android* ) + if test "$ndk_ARCH" = "x86"; then + ndk_matching_libcxx_shared_so=$ndk_candidate + fi + ;; + *x86_64-linux-android* ) + if test "$ndk_ARCH" = "x86_64"; then + ndk_matching_libcxx_shared_so=$ndk_candidate + fi + ;; + esac + + if test -n "$ndk_matching_libcxx_shared_so"; then + with_ndk_cxx_shared=$ndk_matching_libcxx_shared_so + fi + done + fi +fi + +if test -z "$with_ndk_cxx_shared"; then + AC_MSG_RESULT([no]) + AC_MSG_WARN([The C++ standard library could not be found. \ +If you try to build Emacs with a dependency that requires the C++ standard \ +library, Emacs will not build correctly, unless you manually specify the \ +name of an appropriate ``libc++_shared.so'' binary.]) +else + AC_MSG_RESULT([$with_ndk_cxx_shared]) +fi + +ndk_CXX_SHARED=$with_ndk_cxx_shared + # These variables have now been found. ]) # ndk_SEARCH_MODULE(MODULE, NAME, ACTION-IF-FOUND, [ACTION-IF-NOT-FOUND]) -# -------------------------------------------------------------------- +# ----------------------------------------------------------------------- # Search for a module named MODULE in `with_ndk_path'. Add the file -# name of the module's Android.mk file to the variable ndk_MODULES. -# Set NAME_CFLAGS and NAME_LIBS to the appropriate values. Then, -# call ACTION-IF-FOUND, or ACTION-IF-NOT-FOUND upon failure. +# name of the module's Android.mk file to the variable ndk_MAKEFILES. +# Set NAME_CFLAGS and NAME_LIBS to the appropriate values. Then, call +# ACTION-IF-FOUND, or ACTION-IF-NOT-FOUND upon failure. +# +# Resolve any imports specified by MODULE, and expand AC_MSG_ERROR +# with a suitable error message if imports were not found. AC_DEFUN([ndk_SEARCH_MODULE], [ module_name= @@ -137,7 +285,7 @@ for ndk_android_mk in $ndk_module_files; do # Read this Android.mk file. Set NDK_ROOT to /tmp: the Android in # tree build system sets it to a meaning value, but build files just # use it to test whether or not the NDK is being used. - ndk_commands=$(make -s -f build-aux/ndk-build-helper.mk EMACS_SRCDIR=. \ + ndk_commands=$($MAKE -s -f build-aux/ndk-build-helper.mk EMACS_SRCDIR=. \ EMACS_ABI=$ndk_ABI ANDROID_MAKEFILE="$ndk_android_mk" \ ANDROID_MODULE_DIRECTORY=$(dirname "$ndk_android_mk") \ NDK_BUILD_DIR="$ndk_DIR" NDK_ROOT="/tmp" \ @@ -155,12 +303,29 @@ if test -z "$module_name"; then AC_MSG_RESULT([no]) $4 else + if test -n "$module_cxx_deps"; then + ndk_ANY_CXX=yes + fi + + if test "$ndk_ANY_CXX" = "yes" && test -z "$with_ndk_cxx_shared"; then + AC_MSG_ERROR([The module $1 requires the C++ standard library \ +(libc++_shared.so), but it was not found.]) + fi + $2[]_CFLAGS="[$]$2[]_CFLAGS $module_cflags $module_includes" $2[]_LIBS="[$]$2[]_LIBS $module_ldflags" ndk_MAKEFILES="$ndk_MAKEFILES $ndk_android_mk" ndk_MODULES="$ndk_MODULES $module_target" AC_MSG_RESULT([yes]) $3 + + # Now, resolve imports. Make sure the imports' Makefiles comes + # before ndk_MAKEFILES; likewise for its includes. + ndk_import_includes= + for ndk_module in $module_imports; do + ndk_resolve_import_module $ndk_module + $2[]_CFLAGS="$ndk_import_includes [$]$2[]_CFLAGS" + done fi ]) @@ -198,6 +363,8 @@ AC_DEFUN_ONCE([ndk_CONFIG_FILES], NDK_BUILD_CC=$CC NDK_BUILD_AR=$AR NDK_BUILD_MODULES="$ndk_MODULES" + NDK_BUILD_CXX_SHARED="$ndk_CXX_SHARED" + NDK_BUILD_ANY_CXX_MODULE=$ndk_ANY_CXX AC_SUBST([NDK_BUILD_ANDROID_MK]) AC_SUBST([NDK_BUILD_ARCH]) @@ -205,7 +372,10 @@ AC_DEFUN_ONCE([ndk_CONFIG_FILES], AC_SUBST([NDK_BUILD_SDK]) AC_SUBST([NDK_BUILD_CC]) AC_SUBST([NDK_BUILD_AR]) + AC_SUBST([NDK_BUILD_NASM]) AC_SUBST([NDK_BUILD_MODULES]) + AC_SUBST([NDK_BUILD_CXX_SHARED]) + AC_SUBST([NDK_BUILD_ANY_CXX_MODULE]) AC_CONFIG_FILES([$ndk_DIR/Makefile]) AC_CONFIG_FILES([$ndk_DIR/ndk-build.mk]) diff --git a/src/android.c b/src/android.c index 2f21a03b53f..52de153eefd 100644 --- a/src/android.c +++ b/src/android.c @@ -4214,30 +4214,34 @@ android_window_updated (android_window window, unsigned long serial) -#if __ANDROID_API__ >= 17 - -#undef faccessat +#if __ANDROID_API__ >= 16 /* Replace the system faccessat with one which understands AT_EACCESS. 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 + This replacement is only done when building for Android 16 or later, because earlier versions use the gnulib replacement that lacks these issues. */ int -faccessat (int dirfd, const char *pathname, int mode, int flags) +android_faccessat (int dirfd, const char *pathname, int mode, int flags) { - static int (*real_faccessat) (int, const char *, int, int); + return faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS); +} + +#else /* __ANDROID_API__ < 16 */ - if (!real_faccessat) - real_faccessat = dlsym (RTLD_NEXT, "faccessat"); +/* This is unnecessary on earlier API versions, as gnulib's + rpl_faccessat will be used instead. */ - return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS); +int +android_faccessat (int dirfd, const char *pathname, int mode, int flags) +{ + return faccessat (dirfd, pathname, mode, flags); } -#endif /* __ANDROID_API__ >= 17 */ +#endif diff --git a/src/android.h b/src/android.h index 6c20995e4a1..33fad512d4a 100644 --- a/src/android.h +++ b/src/android.h @@ -52,6 +52,7 @@ extern char *android_user_full_name (struct passwd *); extern int android_fstat (int, struct stat *); extern int android_fstatat (int, const char *restrict, struct stat *restrict, int); +extern int android_faccessat (int, const char *, int, int); extern int android_close (int); extern int android_fclose (FILE *); extern const char *android_get_home_directory (void); diff --git a/src/dired.c b/src/dired.c index ced08a35643..b38416e981a 100644 --- a/src/dired.c +++ b/src/dired.c @@ -894,7 +894,8 @@ file_name_completion_dirp (int fd, struct dirent *dp, ptrdiff_t len) return true; #endif - bool dirp = faccessat (fd, subdir_name, F_OK, AT_EACCESS) == 0; + bool dirp = sys_faccessat (fd, subdir_name, + F_OK, AT_EACCESS) == 0; SAFE_FREE (); return dirp; } diff --git a/src/fileio.c b/src/fileio.c index 492a43d74c3..6f25506dbc2 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -188,7 +188,7 @@ file_access_p (char const *file, int amode) return true; #endif - if (faccessat (AT_FDCWD, file, amode, AT_EACCESS) == 0) + if (sys_faccessat (AT_FDCWD, file, amode, AT_EACCESS) == 0) return true; #ifdef CYGWIN @@ -3024,7 +3024,8 @@ If there is no error, returns nil. */) return Qnil; #endif - if (faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK, AT_EACCESS) != 0) + if (sys_faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK, + AT_EACCESS) != 0) report_file_error (SSDATA (string), filename); return Qnil; @@ -3126,7 +3127,8 @@ file_directory_p (Lisp_Object file) { #ifdef DOS_NT /* This is cheaper than 'stat'. */ - bool retval = faccessat (AT_FDCWD, SSDATA (file), D_OK, AT_EACCESS) == 0; + bool retval = sys_faccessat (AT_FDCWD, SSDATA (file), + D_OK, AT_EACCESS) == 0; if (!retval && errno == EACCES) errno = ENOTDIR; /* like the non-DOS_NT branch below does */ return retval; diff --git a/src/lisp.h b/src/lisp.h index f64a27ce113..6fda1e95503 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -5071,6 +5071,7 @@ extern int emacs_fstatat (int, char const *, void *, int); #ifdef HAVE_SYS_STAT_H extern int sys_fstat (int, struct stat *); #endif +extern int sys_faccessat (int, const char *, int, int); #if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) extern int emacs_openat (int, char const *, int, int); #endif diff --git a/src/lread.c b/src/lread.c index d585dece392..6abcea556bf 100644 --- a/src/lread.c +++ b/src/lread.c @@ -1964,8 +1964,8 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, fd = -1; if (INT_MAX < XFIXNAT (predicate)) last_errno = EINVAL; - else if (faccessat (AT_FDCWD, pfn, XFIXNAT (predicate), - AT_EACCESS) + else if (sys_faccessat (AT_FDCWD, pfn, XFIXNAT (predicate), + AT_EACCESS) == 0) { if (file_directory_p (encoded_fn)) @@ -1985,7 +1985,7 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, it. Only open the file when we are sure that it exists. */ #ifdef WINDOWSNT - if (faccessat (AT_FDCWD, pfn, R_OK, AT_EACCESS)) + if (sys_faccessat (AT_FDCWD, pfn, R_OK, AT_EACCESS)) fd = -1; else #endif diff --git a/src/process.c b/src/process.c index 651b5fa035b..9a8b0d7fd85 100644 --- a/src/process.c +++ b/src/process.c @@ -880,7 +880,8 @@ allocate_pty (char pty_name[PTY_NAME_SIZE]) /* Check to make certain that both sides are available. This avoids a nasty yet stupid bug in rlogins. */ - if (faccessat (AT_FDCWD, pty_name, R_OK | W_OK, AT_EACCESS) != 0) + if (sys_faccessat (AT_FDCWD, pty_name, + R_OK | W_OK, AT_EACCESS) != 0) { emacs_close (fd); continue; diff --git a/src/sysdep.c b/src/sysdep.c index 4d89d4f25ae..ac26b12e9a5 100644 --- a/src/sysdep.c +++ b/src/sysdep.c @@ -2398,6 +2398,16 @@ sys_fstat (int fd, struct stat *statb) #endif } +int +sys_faccessat (int fd, const char *pathname, int mode, int flags) +{ +#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY) + return faccessat (fd, pathname, mode, flags); +#else + return android_faccessat (fd, pathname, mode, flags); +#endif +} + /* Assuming the directory DIRFD, open FILE for Emacs use, using open flags OFLAGS and mode MODE. Use binary I/O on systems that care about text vs binary I/O. -- 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/EmacsView.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 98c90135fe5f3729a3fa6e9a724d04a78b2cd514 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 5 Feb 2023 23:02:14 +0800 Subject: Update Android port * INSTALL.android: Explain how to build selinux. * configure.ac: Enable selinux on Android. * cross/ndk-build/ndk-build-shared-library.mk: ($(call objname,$(LOCAL_MODULE),$(basename $(1))))::($$(error Unsupported suffix)::(NDK_CFLAGS_$(LOCAL_MODULE)): * cross/ndk-build/ndk-build-static-library.mk: ($(call objname,$(LOCAL_MODULE),$(basename $(1))))::($$(error Unsupported suffix)::(NDK_CFLAGS_$(LOCAL_MODULE)): Correctly handle files with a .cc suffix, and clang-specific asflags. * cross/ndk-build/ndk-clear-vars.mk: Handle AOSP extensions LOCAL_ADDITIONAL_DEPENDENCIES, LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH) and LOCAL_IS_HOST_MODULE. * doc/emacs/android.texi (Android Startup): Explain emacsclient wrapper. * java/org/gnu/emacs/EmacsView.java (EmacsView): New flag `isCurrentlyTextEditor'. (showOnScreenKeyboard, hideOnScreenKeyboard): Set as appropriate. (onCheckIsTextEditor): Return its value. * lisp/touch-screen.el (touch-screen-handle-scroll): Don't ding at buffer limits. * m4/ndk-build.m4: Improve doc. * src/Makefile.in (LIBSELINUX_CFLAGS): New variable. (EMACS_CFLAGS): Add it. --- INSTALL.android | 70 +++++++++++++++++++++++ configure.ac | 23 ++++++-- cross/ndk-build/ndk-build-shared-library.mk | 9 ++- cross/ndk-build/ndk-build-static-library.mk | 9 ++- cross/ndk-build/ndk-clear-vars.mk | 4 ++ doc/emacs/android.texi | 16 ++++++ java/org/gnu/emacs/EmacsView.java | 15 +++++ lisp/touch-screen.el | 89 ++++++++++++++++------------- m4/ndk-build.m4 | 8 ++- src/Makefile.in | 3 +- 10 files changed, 194 insertions(+), 52 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/INSTALL.android b/INSTALL.android index eeeb14d78b3..6366b8d664d 100644 --- a/INSTALL.android +++ b/INSTALL.android @@ -171,6 +171,13 @@ work, along with what has to be patched to make them work: sqlite3 - https://android.googlesource.com/platform/external/sqlite/ (You must apply the patch at the end of this file, and add the `dist' directory to ``--with-ndk-path''.) + libselinux - https://android.googlesource.com/platform/external/libselinux + (You must apply the patches at the end of the file, and obtain + the following three dependencies.) + libpackagelistparser + https://android.googlesource.com/platform/system/core/+/refs/heads/nougat-mr1-dev/libpackagelistparser/ + libpcre - https://android.googlesource.com/platform/external/pcre + libcrypto - https://android.googlesource.com/platform/external/boringssl Many of these dependencies have been migrated over to the ``Android.bp'' build system now used to build Android itself. @@ -663,3 +670,66 @@ index c7bcb0f5..d4da1704 100644 else NEON := c endif + +PATCHES FOR SELINUX + +diff --git a/Android.mk b/Android.mk +index 659232e..1e64fd6 100644 +--- a/Android.mk ++++ b/Android.mk +@@ -116,3 +116,7 @@ LOCAL_STATIC_LIBRARIES := libselinux + LOCAL_WHOLE_STATIC_LIBRARIES := libpcre + LOCAL_C_INCLUDES := external/pcre + include $(BUILD_HOST_EXECUTABLE) ++ ++$(call import-module,libpcre) ++$(call import-module,libpackagelistparser) ++$(call import-module,libcrypto) + +diff --git a/src/android.c b/src/android.c +index 5206a9f..b351ffc 100644 +--- a/src/android.c ++++ b/src/android.c +@@ -21,8 +21,7 @@ + #include + #include + #include +-#include +-#include ++#include + #include "policy.h" + #include "callbacks.h" + #include "selinux_internal.h" +@@ -686,6 +685,7 @@ static int seapp_context_lookup(enum seapp_kind kind, + seinfo = parsedseinfo; + } + ++#if 0 + userid = uid / AID_USER; + isOwner = (userid == 0); + appid = uid % AID_USER; +@@ -702,9 +702,13 @@ static int seapp_context_lookup(enum seapp_kind kind, + username = "_app"; + appid -= AID_APP; + } else { ++#endif + username = "_isolated"; ++ appid = 0; ++#if 0 + appid -= AID_ISOLATED_START; + } ++#endif + + if (appid >= CAT_MAPPING_MAX_ID || userid >= CAT_MAPPING_MAX_ID) + goto err; +@@ -1662,8 +1666,10 @@ int selinux_log_callback(int type, const char *fmt, ...) + + va_start(ap, fmt); + if (vasprintf(&strp, fmt, ap) != -1) { ++#if 0 + LOG_PRI(priority, "SELinux", "%s", strp); + LOG_EVENT_STRING(AUDITD_LOG_TAG, strp); ++#endif + free(strp); + } + va_end(ap); diff --git a/configure.ac b/configure.ac index 0739c73703d..b8ce7840797 100644 --- a/configure.ac +++ b/configure.ac @@ -1063,6 +1063,7 @@ package will likely install on older systems but crash on startup.]) passthrough="$passthrough --with-sqlite3=$with_sqlite3" passthrough="$passthrough --with-gnutls=$with_gnutls" passthrough="$passthrough --with-tiff=$with_tiff" + passthrough="$passthrough --with-selinux=$with_selinux" AS_IF([XCONFIGURE=android ANDROID_CC="$ANDROID_CC" \ ANDROID_SDK="$android_sdk" android_abi=$android_abi \ @@ -1134,6 +1135,7 @@ if test "$ANDROID" = "yes"; then with_sqlite3=no with_gnutls=no with_tiff=no + with_selinux=no fi with_rsvg=no @@ -1148,7 +1150,6 @@ if test "$ANDROID" = "yes"; then with_gpm=no with_dbus=no with_gsettings=no - with_selinux=no with_modules=no with_threads=no @@ -3709,19 +3710,29 @@ if test "${HAVE_GTK}" = "yes"; then fi AC_SUBST([USE_STARTUP_NOTIFICATION]) -dnl SELinux is available for GNU/Linux only. +dnl SELinux is available for Linux kernel based systems only. +dnl These include GNU/Linux and Android. HAVE_LIBSELINUX=no LIBSELINUX_LIBS= +LIBSELINUX_CFLAGS= if test "${with_selinux}" = "yes"; then - AC_CHECK_LIB([selinux], [lgetfilecon], - [HAVE_LIBSELINUX=yes], - [HAVE_LIBSELINUX=no]) + if test "$REALLY_ANDROID" = "yes"; then + ndk_SEARCH_MODULE([libselinux], [LIBSELINUX], + [HAVE_LIBSELINUX=yes]) + else + AC_CHECK_LIB([selinux], [lgetfilecon], + [HAVE_LIBSELINUX=yes], + [HAVE_LIBSELINUX=no]) + fi if test "$HAVE_LIBSELINUX" = yes; then AC_DEFINE([HAVE_LIBSELINUX], [1], [Define to 1 if using SELinux.]) - LIBSELINUX_LIBS=-lselinux + if test "$REALLY_ANDROID" != "yes"; then + LIBSELINUX_LIBS=-lselinux + fi fi fi AC_SUBST([LIBSELINUX_LIBS]) +AC_SUBST([LIBSELINUX_CFLAGS]) HAVE_GNUTLS=no if test "${with_gnutls}" != "no" ; then diff --git a/cross/ndk-build/ndk-build-shared-library.mk b/cross/ndk-build/ndk-build-shared-library.mk index 5842486a1af..be4d16cb4ed 100644 --- a/cross/ndk-build/ndk-build-shared-library.mk +++ b/cross/ndk-build/ndk-build-shared-library.mk @@ -51,7 +51,14 @@ $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) else ifneq (x$(suffix $(1)),x.asm) +ifeq (x$(suffix $(1)),x.cc) + +$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) + $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) $(NDK_CXXFLAGS_$(LOCAL_MODULE)) + +else $$(error Unsupported suffix: $(suffix $(1))) +endif else ifneq (x$(LOCAL_ASM_RULE_DEFINED),x) # Call this function to define a rule that will generate $(1) from @@ -82,7 +89,7 @@ endef # $(LOCAL_PATH). NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(LOCAL_C_INCLUDES)) NDK_CFLAGS_$(LOCAL_MODULE) += -fPIC -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH)) -NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) +NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) $(and $(findstring clang,$(NDK_BUILD_CC)),$(LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH))) NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) $(LOCAL_LDFLAGS) NDK_CXXFLAGS_$(LOCAL_MODULE) := $(LOCAL_CPPFLAGS) $(LOCAL_RTTI_FLAG) diff --git a/cross/ndk-build/ndk-build-static-library.mk b/cross/ndk-build/ndk-build-static-library.mk index f6df38e4c0b..ebcc3cd531d 100644 --- a/cross/ndk-build/ndk-build-static-library.mk +++ b/cross/ndk-build/ndk-build-static-library.mk @@ -43,7 +43,14 @@ $(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) else ifneq (x$(suffix $(1)),x.asm) +ifeq (x$(suffix $(1)),x.cc) + +$(call objname,$(LOCAL_MODULE),$(basename $(1))): $(LOCAL_PATH)/$(1) + $(NDK_BUILD_CC) -c $$< -o $$@ $(NDK_CFLAGS_$(LOCAL_MODULE)) $(NDK_CXXFLAGS_$(LOCAL_MODULE)) + +else $$(error Unsupported suffix: $(suffix $(1))) +endif else ifneq (x$(LOCAL_ASM_RULE_DEFINED),x) # Call this function to define a rule that will generate $(1) from @@ -71,7 +78,7 @@ endef NDK_CFLAGS_$(LOCAL_MODULE) := $(addprefix -I,$(LOCAL_C_INCLUDES)) NDK_CFLAGS_$(LOCAL_MODULE) += -fPIC -iquote $(LOCAL_PATH) $(LOCAL_EXPORT_CFLAGS) $(LOCAL_CFLAGS) $(LOCAL_CFLAGS_$(NDK_BUILD_ARCH)) -NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) +NDK_ASFLAGS_$(LOCAL_MODULE) := $(LOCAL_ASFLAGS) $(LOCAL_ASFLAGS_$(NDK_BUILD_ARCH)) $(and $(findstring clang,$(NDK_BUILD_CC)),$(LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH))) NDK_LDFLAGS_$(LOCAL_MODULE) := $(LOCAL_LDLIBS) $(LOCAL_LDFLAGS) NDK_CXXFLAGS_$(LOCAL_MODULE) := $(LOCAL_CPPFLAGS) $(LOCAL_RTTI_FLAG) ALL_OBJECT_FILES$(LOCAL_MODULE) := diff --git a/cross/ndk-build/ndk-clear-vars.mk b/cross/ndk-build/ndk-clear-vars.mk index bce5bc895be..5e8eff9f0ab 100644 --- a/cross/ndk-build/ndk-clear-vars.mk +++ b/cross/ndk-build/ndk-clear-vars.mk @@ -43,9 +43,13 @@ undefine LOCAL_EXPORT_C_INCLUDE_DIRS undefine LOCAL_EXPORT_LDFLAGS undefine LOCAL_EXPORT_LDLIBS +# AOSP extensions. undefine LOCAL_SRC_FILES_$(NDK_BUILD_ARCH) undefine LOCAL_ASFLAGS_$(NDK_BUILD_ARCH) undefine LOCAL_CFLAGS_$(NDK_BUILD_ARCH) +undefine LOCAL_ADDITIONAL_DEPENDENCIES +undefine LOCAL_CLANG_ASFLAGS_$(NDK_BUILD_ARCH) +undefine LOCAL_IS_HOST_MODULE # Emacs extensions! undefine LOCAL_ASM_RULE_DEFINED diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 9dc6fddbb72..b1f01057a98 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -109,6 +109,22 @@ corrupted, Emacs can crash. If that happens, the dump file stored in the Emacs files directory can be erased through the same preferences screen. +@cindex emacsclient wrapper, android + Since there is no other way to start the @cmd{emacsclient} program +(@pxref{Emacs Server}) from another Android program, Emacs provides a +wrapper around the @cmd{emacsclient} program, which is registered with +the system as an application that can open all text files. + + When that wrapper is selected as the program with which to open a +file, it invokes @cmd{emacsclient} with the options +@cmd{--reuse-frame}, @cmd{--timeout=10}, @cmd{--no-wait}, and the name +of the file being opened. Then, upon success, the focus is +transferred to any open Emacs frame. + +It is sadly impossible to open certain kinds of files which are +provided by a ``content provider''. When that is the case, a dialog +is displayed with an explanation of the error. + @node Android File System @section What files Emacs can access under Android @cindex /assets directory, android diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 730ed02d4f1..873124c86d1 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -106,6 +106,10 @@ public class EmacsView extends ViewGroup /* Whether or not this view is attached to a window. */ public boolean isAttachedToWindow; + /* Whether or not this view should have the on screen keyboard + displayed whenever possible. */ + public boolean isCurrentlyTextEditor; + public EmacsView (EmacsWindow window) { @@ -597,6 +601,7 @@ public class EmacsView extends ViewGroup /* 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 void @@ -604,6 +609,7 @@ public class EmacsView extends ViewGroup { imManager.hideSoftInputFromWindow (this.getWindowToken (), 0); + isCurrentlyTextEditor = false; } public void @@ -636,4 +642,13 @@ public class EmacsView extends ViewGroup screen keyboard to work correctly. */ return null; } + + @Override + public boolean + onCheckIsTextEditor () + { + /* If value is true, then the system will display the on screen + keyboard. */ + return isCurrentlyTextEditor; + } }; diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el index fa98cbe5db9..8c4a73f326d 100644 --- a/lisp/touch-screen.el +++ b/lisp/touch-screen.el @@ -96,47 +96,54 @@ to the frame that they belong in." 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) - (pixel-scroll-precision-scroll-up-page (- dy))) - ;; Start conventional scrolling. First, determine the direction - ;; 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)) - (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. - (while (catch 'again - (let* ((line-height (window-default-line-height window))) - (if (and (< accumulator 0) - (>= (- accumulator) line-height)) - (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. - (throw 'again t))) - (when (and (> accumulator 0) - (>= accumulator line-height)) - (setq accumulator (- accumulator line-height)) - (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 vertical scrolling first. Do not ding at buffer limits. + ;; Show a message instead. + (condition-case nil + (if touch-screen-precision-scroll + (if (> dy 0) + (pixel-scroll-precision-scroll-down-page dy) + (pixel-scroll-precision-scroll-up-page (- dy))) + ;; Start conventional scrolling. First, determine the + ;; direction 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)) + (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. + (while (catch 'again + (let* ((line-height (window-default-line-height window))) + (if (and (< accumulator 0) + (>= (- accumulator) line-height)) + (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. + (throw 'again t))) + (when (and (> accumulator 0) + (>= accumulator line-height)) + (setq accumulator (- accumulator line-height)) + (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)))) + (beginning-of-buffer + (message (error-message-string '(beginning-of-buffer)))) + (end-of-buffer + (message (error-message-string '(end-of-buffer))))) ;; Perform horizontal scrolling by DX, as this does not signal at ;; the beginning of the buffer. diff --git a/m4/ndk-build.m4 b/m4/ndk-build.m4 index 0ab6197d735..888b587af02 100644 --- a/m4/ndk-build.m4 +++ b/m4/ndk-build.m4 @@ -90,6 +90,8 @@ ndk_replace_pkg_config_package () { done } +# ndk_parse_pkg_config_string PKG_CONFIG_STRING +# --------------------------------------------- # Parse a pkg-config style list of modules. Place the resulting list # in ndk_modules. @@ -109,8 +111,10 @@ ndk_parse_pkg_config_string () { done } -# Resolve $1, a single import. Prepend its makefile to ndk_MAKEFILES -# if found. Also, prepend all includes to the variable +# ndk_resolve_import_module MODULE +# -------------------------------- +# Resolve MODULE, a single import. Prepend its makefile to +# ndk_MAKEFILES if found. Also, prepend all includes to the variable # ndk_import_includes. ndk_resolve_import_module () { diff --git a/src/Makefile.in b/src/Makefile.in index f17e34be616..d5d979a9bd3 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -349,6 +349,7 @@ CM_OBJ=@CM_OBJ@ LIBGPM = @LIBGPM@ LIBSELINUX_LIBS = @LIBSELINUX_LIBS@ +LIBSELINUX_CFLAGS = @LIBSELINUX_CFLAGS@ LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@ LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@ @@ -437,7 +438,7 @@ EMACS_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \ $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \ $(WERROR_CFLAGS) $(HAIKU_CFLAGS) $(XCOMPOSITE_CFLAGS) $(XSHAPE_CFLAGS) \ $(ANDROID_CFLAGS) $(GIF_CFLAGS) $(JPEG_CFLAGS) $(SQLITE3_CFLAGS) \ - $(LIBGMP_CFLAGS) $(TIFF_CFLAGS) + $(LIBGMP_CFLAGS) $(TIFF_CFLAGS) $(LIBSELINUX_CFLAGS) ALL_CFLAGS = $(EMACS_CFLAGS) $(WARN_CFLAGS) $(CFLAGS) ALL_OBJC_CFLAGS = $(EMACS_CFLAGS) \ $(filter-out $(NON_OBJC_CFLAGS),$(WARN_CFLAGS)) $(CFLAGS) \ -- cgit v1.3 From 0bd4b7fdab2fdf437c4a759d53dfdc9f667aefb1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 8 Feb 2023 22:40:10 +0800 Subject: Update Android port * doc/lispref/frames.texi (On-Screen Keyboards): Describe return value of `frame-toggle-on-screen-keyboard'. * java/org/gnu/emacs/EmacsSurfaceView.java (surfaceChanged) (surfaceCreated, EmacsSurfaceView): Remove unuseful synchronization code. The framework doesn't seem to look at this at all. * java/org/gnu/emacs/EmacsView.java (EmacsView): (onLayout): Lay out the window after children. (swapBuffers): Properly implement `force'. (windowUpdated): Delete function. * lisp/frame.el (frame-toggle-on-screen-keyboard): Return whether or not the on screen keyboard might've been displayed. * lisp/minibuffer.el (minibuffer-on-screen-keyboard-timer): (minibuffer-on-screen-keyboard-displayed): (minibuffer-setup-on-screen-keyboard): (minibuffer-exit-on-screen-keyboard): Improve OSK dismissal when there are consecutive minibuffers. * lisp/touch-screen.el (touch-screen-window-selection-changed): New function. (touch-screen-handle-point-up): Register it as a window selection changed function. * src/android.c (struct android_emacs_window) (android_init_emacs_window): Remove references to `windowUpdated'. (android_window_updated): Delete function. * src/android.h (struct android_output): Remove `last_configure_serial'. * src/androidterm.c (handle_one_android_event) (android_frame_up_to_date): * src/androidterm.h (struct android_output): Remove frame synchronization, as that does not work on Android. --- doc/lispref/frames.texi | 4 +++ java/org/gnu/emacs/EmacsSurfaceView.java | 54 +++-------------------------- java/org/gnu/emacs/EmacsView.java | 58 ++++++++++++-------------------- lisp/frame.el | 7 +++- lisp/minibuffer.el | 35 +++++++++++++++++-- lisp/touch-screen.el | 26 +++++++++++++- src/android.c | 21 ------------ src/android.h | 1 - src/androidterm.c | 18 ---------- src/androidterm.h | 5 --- 10 files changed, 94 insertions(+), 135 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 497715bdb19..6ffefe8184f 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -3828,6 +3828,10 @@ 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. +It returns whether or not the on screen keyboard @strong{may} have +been displayed, which should be used to determine whether or not to +hide the on-screen keyboard later. + 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. diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 2fe9e103b2b..f6cb77bb2b8 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -45,14 +45,11 @@ public class EmacsSurfaceView extends SurfaceView surfaceChanged (SurfaceHolder holder, int format, int width, int height) { - Log.d (TAG, "surfaceChanged: " + view + ", " + view.pendingConfigure); + Canvas canvas; - /* Make sure not to swap buffers if there is pending - configuration, because otherwise the redraw callback will not - run correctly. */ + Log.d (TAG, "surfaceChanged: " + view + ", "); - if (view.pendingConfigure == 0) - view.swapBuffers (); + view.swapBuffers (true); } @Override @@ -67,7 +64,7 @@ public class EmacsSurfaceView extends SurfaceView /* Drop the lock when doing this, or a deadlock can result. */ - view.swapBuffers (); + view.swapBuffers (true); } @Override @@ -82,44 +79,6 @@ public class EmacsSurfaceView extends SurfaceView } } - /* 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) { @@ -128,10 +87,7 @@ public class EmacsSurfaceView extends SurfaceView 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 ()); + getHolder ().addCallback (new Callback ()); } public boolean diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 873124c86d1..fac11870ebf 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -96,13 +96,6 @@ public class EmacsView extends ViewGroup /* 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; - /* Whether or not this view is attached to a window. */ public boolean isAttachedToWindow; @@ -110,6 +103,14 @@ 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) { @@ -286,16 +287,10 @@ public class EmacsView extends ViewGroup int count, i; View child; Rect windowRect; + int wantedWidth, wantedHeight; count = getChildCount (); - if (changed || mustReportLayout) - { - mustReportLayout = false; - pendingConfigure - = window.viewLayout (left, top, right, bottom); - } - measuredWidth = right - left; measuredHeight = bottom - top; @@ -311,8 +306,6 @@ public class EmacsView extends ViewGroup Log.d (TAG, "onLayout: " + child); if (child == surfaceView) - /* The child is the surface view, so give it the entire - view. */ child.layout (0, 0, right - left, bottom - top); else if (child.getVisibility () != GONE) { @@ -326,6 +319,14 @@ public class EmacsView extends ViewGroup windowRect.right, windowRect.bottom); } } + + /* Now report the layout change to the window. */ + + if (changed || mustReportLayout) + { + mustReportLayout = false; + window.viewLayout (left, top, right, bottom); + } } public void @@ -352,7 +353,7 @@ public class EmacsView extends ViewGroup synchronized (damageRegion) { - if (damageRegion.isEmpty ()) + if (!force && damageRegion.isEmpty ()) return; bitmap = getBitmap (); @@ -363,7 +364,10 @@ public class EmacsView extends ViewGroup synchronized (surfaceView.surfaceChangeLock) { - damageRect = damageRegion.getBounds (); + if (!force) + damageRect = damageRegion.getBounds (); + else + damageRect = emptyRect; if (!surfaceView.isCreated ()) return; @@ -612,24 +616,6 @@ public class EmacsView extends ViewGroup isCurrentlyTextEditor = false; } - 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; - } - } - @Override public InputConnection onCreateInputConnection (EditorInfo info) diff --git a/lisp/frame.el b/lisp/frame.el index a88af24a152..d925a87153c 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2564,11 +2564,16 @@ 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. +Return whether or not the on screen keyboard may have been +displayed; that is, return t on systems with an on screen +keyboard, and nil on those without. + 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))))) + (android-toggle-on-screen-keyboard frame hide) t) + (t nil)))) ;;;; Frame geometry values diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 3b110e8416f..8c1a0d4b21c 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -4577,20 +4577,49 @@ is included in the return value." ;; Try to display the on screen keyboard whenever entering the ;; mini-buffer, and hide it whenever leaving. +(defvar minibuffer-on-screen-keyboard-timer nil + "Timer run upon exiting the minibuffer. +It will hide the on screen keyboard when necessary.") + +(defvar minibuffer-on-screen-keyboard-displayed nil + "Whether or not the on-screen keyboard has been displayed. +Set inside `minibuffer-setup-on-screen-keyboard'.") + (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." + ;; Don't hide the on screen keyboard later on. + (when minibuffer-on-screen-keyboard-timer + (cancel-timer minibuffer-on-screen-keyboard-timer) + (setq minibuffer-on-screen-keyboard-timer nil)) + (setq minibuffer-on-screen-keyboard-displayed nil) (when (not (memq (device-class last-event-frame last-event-device) '(keyboard core-keyboard))) - (frame-toggle-on-screen-keyboard (selected-frame) nil))) + (setq minibuffer-on-screen-keyboard-displayed + (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)) +Hide the on-screen keyboard in a timer set to run in 0.1 seconds. +It will be cancelled if the minibuffer is displayed again within +that timeframe. + +Do not hide the on screen keyboard inside a recursive edit. +Likewise, do not hide the on screen keyboard if point in the +window that will be selected after exiting the minibuffer is not +on read-only text. + +The latter is implemented in `touch-screen.el'." + (unless (or (not minibuffer-on-screen-keyboard-displayed) + (> (recursion-depth) 1)) + (when minibuffer-on-screen-keyboard-timer + (cancel-timer minibuffer-on-screen-keyboard-timer)) + (setq minibuffer-on-screen-keyboard-timer + (run-with-timer 0.1 nil #'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) diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el index 8c4a73f326d..01e56f1d499 100644 --- a/lisp/touch-screen.el +++ b/lisp/touch-screen.el @@ -351,6 +351,21 @@ then move point to the position of POINT." (goto-char (1+ (window-end nil t)))) (redisplay)))))))))))) +(defun touch-screen-window-selection-changed (frame) + "Notice that FRAME's selected window has changed. +If point is now on read only text, hide the on screen keyboard. +Otherwise, cancel any timer that is supposed to hide the keyboard +in response to the minibuffer being closed." + (with-selected-frame frame + (if (or buffer-read-only + (get-text-property (point) 'read-only)) + (frame-toggle-on-screen-keyboard (selected-frame) t) + ;; Prevent hiding the minibuffer from hiding the on screen + ;; keyboard. + (when minibuffer-on-screen-keyboard-timer + (cancel-timer minibuffer-on-screen-keyboard-timer) + (setq minibuffer-on-screen-keyboard-timer nil))))) + (defun touch-screen-handle-point-up (point) "Notice that POINT has been removed from the screen. POINT should be the point currently tracked as @@ -404,7 +419,16 @@ is not read-only." (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) + ;; Once the on-screen keyboard has been opened, + ;; add `touch-screen-window-selection-changed' + ;; as a window selection change function This + ;; allows the on screen keyboard to be hidden + ;; if the selected window's point becomes read + ;; only at some point in the future. + (progn + (add-hook 'window-selection-change-functions + #'touch-screen-window-selection-changed) + (frame-toggle-on-screen-keyboard (selected-frame) nil)) ;; Otherwise, hide the on screen keyboard now. (frame-toggle-on-screen-keyboard (selected-frame) t)))))))))) diff --git a/src/android.c b/src/android.c index a0e64471a05..ebd374addbf 100644 --- a/src/android.c +++ b/src/android.c @@ -125,7 +125,6 @@ struct android_emacs_window jclass class; jmethodID swap_buffers; jmethodID toggle_on_screen_keyboard; - jmethodID window_updated; }; /* The API level of the current device. */ @@ -1830,7 +1829,6 @@ android_init_emacs_window (void) 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 } @@ -4325,25 +4323,6 @@ android_toggle_on_screen_keyboard (android_window window, bool show) 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 (); -} - /* When calling the system's faccessat, make sure to clear the flag diff --git a/src/android.h b/src/android.h index da5c4379800..9006f5f34c5 100644 --- a/src/android.h +++ b/src/android.h @@ -88,7 +88,6 @@ 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); extern _Noreturn void android_restart_emacs (void); extern int android_get_current_api_level (void); diff --git a/src/androidterm.c b/src/androidterm.c index d5dafe9b0d7..4aee1a90b68 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -591,17 +591,7 @@ 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; @@ -1352,14 +1342,6 @@ 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 8cc31f1ab57..ac845187a66 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -241,11 +241,6 @@ 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 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/EmacsView.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 dc120c7ad62d5f79fe50f72431d3b9bb2d7f1558 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Feb 2023 23:03:43 +0800 Subject: Improve appearance of the Android preferences screen * .gitignore: Add org/gnu/emacs/R.java. * cross/Makefile.in (top_builddir): Include verbose.mk. Rewrite rules to print nice looking statements. * doc/emacs/android.texi (Android, Android Startup) (Android Environment, Android Windowing, Android Fonts): * doc/emacs/emacs.texi (Top): Add an extra ``Android Troubleshooting'' node and move troubleshooting details there. * java/Makefile.in: Generate R.java; improve appearance by using verbose.mk. * java/org/gnu/emacs/EmacsPreferencesActivity.java: Reimplement in terms of PreferencesActivity. * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap): Avoid flicker. * java/res/xml/preferences.xml: New file. * src/verbose.mk.in (AM_V_AAPT, AM_V_SILENT): New variables. --- .gitignore | 5 +- cross/Makefile.in | 61 +++++++---- doc/emacs/android.texi | 85 ++++++++++------ doc/emacs/emacs.texi | 13 +-- java/Makefile.in | 104 +++++++++++++------ java/org/gnu/emacs/EmacsPreferencesActivity.java | 123 ++++++++++++----------- java/org/gnu/emacs/EmacsView.java | 5 +- java/res/xml/preferences.xml | 28 ++++++ src/verbose.mk.in | 3 + 9 files changed, 277 insertions(+), 150 deletions(-) create mode 100644 java/res/xml/preferences.xml (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/.gitignore b/.gitignore index a0fd1339ebb..3950a46211b 100644 --- a/.gitignore +++ b/.gitignore @@ -57,12 +57,15 @@ src/emacs-module.h !INSTALL.android !verbose.mk.android -# Built by `java'. +# Built by `javac'. java/install_temp/* java/*.apk* java/*.dex java/org/gnu/emacs/*.class +# Built by `aapt'. +java/org/gnu/emacs/R.java + # Built by `config.status'. java/AndroidManifest.xml diff --git a/cross/Makefile.in b/cross/Makefile.in index a44550c6563..fc047fd57bd 100644 --- a/cross/Makefile.in +++ b/cross/Makefile.in @@ -20,6 +20,8 @@ top_srcdir = @top_srcdir@ top_builddir = @top_builddir@ +-include $(top_builddir)/src/verbose.mk + # Cross-compiling Emacs for Android. # The cross compiled binaries are built by having ``variant'' @@ -63,11 +65,11 @@ all: lib/libgnu.a src/libemacs.so src/android-emacs $(LIBSRC_BINARIES) # This file is used to tell lib/gnulib.mk when # $(top_srcdir)/config.status changes. config.status: $(top_srcdir)/config.status - touch config.status + $(AM_V_GEN) touch config.status src/verbose.mk: verbose.mk.android - mkdir -p src - cp -f verbose.mk.android src/verbose.mk + $(AM_V_GEN) mkdir -p src + $(AM_V_SILENT) cp -f verbose.mk.android src/verbose.mk # Gnulib, make-fingerprint and make-docfile must be built before # entering any of the rules below, or they will get the Android @@ -75,30 +77,35 @@ src/verbose.mk: verbose.mk.android .PHONY: $(top_builddir)/lib/libgnu.a $(top_builddir)/lib/libgnu.a: - + make -C $(top_builddir)/lib libgnu.a + $(MAKE) -C $(top_builddir)/lib libgnu.a .PHONY: $(top_builddir)/lib-src/make-fingerprint $(top_builddir)/lib-src/make-fingerprint: - make -C $(top_builddir)/lib-src make-fingerprint + $(MAKE) -C $(top_builddir)/lib-src make-fingerprint .PHONY: $(top_builddir)/lib-src/make-docfile $(top_builddir)/lib-src/make-docfile: - make -C $(top_builddir)/lib-src make-docfile + $(MAKE) -C $(top_builddir)/lib-src make-docfile PRE_BUILD_DEPS=$(top_builddir)/lib/libgnu.a \ $(top_builddir)/lib-src/make-fingerprint \ $(top_builddir)/lib-src/make-docfile lib/config.h: $(top_builddir)/src/config.h.android - cp -f -p $(top_builddir)/src/config.h.android lib/config.h + $(AM_V_GEN) cp -f -p $(top_builddir)/src/config.h.android \ + lib/config.h lib/gnulib.mk: $(top_builddir)/lib/gnulib.mk.android - cp -f -p $(top_builddir)/lib/gnulib.mk.android lib/gnulib.mk + $(AM_V_GEN) cp -f -p $(top_builddir)/lib/gnulib.mk.android \ + lib/gnulib.mk + $(AM_V_SILENT) \ sed -i 's/srcdir =.*$$/srcdir = $(subst /,\/,$(LIB_SRCDIR))/g' \ lib/gnulib.mk lib/Makefile: $(top_builddir)/lib/Makefile.android - cp -f -p $(top_builddir)/lib/Makefile.android lib/Makefile + $(AM_V_GEN) cp -f -p $(top_builddir)/lib/Makefile.android \ + lib/Makefile + $(AM_V_SILENT) \ sed -i 's/top_srcdir =.*$$/top_srcdir = $(subst /,\/,$(LIB_TOP_SRCDIR))/g' \ lib/Makefile @@ -108,53 +115,65 @@ LIB_DEPS = lib/config.h lib/gnulib.mk lib/Makefile .PHONY: lib/libgnu.a lib/libgnu.a: src/verbose.mk config.status $(LIB_DEPS) $(PRE_BUILD_DEPS) mkdir -p lib/deps lib/deps/malloc - +make -C lib libgnu.a + $(MAKE) -C lib libgnu.a src/Makefile src/config.h &: $(top_builddir)/src/config.h.android \ $(top_builddir)/src/Makefile.android - mkdir -p src src/deps + $(AM_V_GEN) + $(AM_V_SILENT) mkdir -p src src/deps # Copy config.h to src/ + $(AM_V_SILENT) \ cp -f -p $(top_builddir)/src/config.h.android src/config.h # And the Makefile. + $(AM_V_SILENT) \ cp -f -p $(top_builddir)/src/Makefile.android src/Makefile # Next, edit srcdir and top_srcdir to the right location. + $(AM_V_SILENT) \ sed -i 's/srcdir =.*$$/srcdir = $(subst /,\/,$(SRC_SRCDIR))/g' src/Makefile + $(AM_V_SILENT) \ sed -i 's/top_srcdir =.*$$/top_srcdir = $(subst /,\/,$(LIB_TOP_SRCDIR))/g' \ src/Makefile # Edit references to ../admin/unidata to read ../../admin/unidata. + $(AM_V_SILENT) \ sed -i 's/\.\.\/admin\/unidata/..\/..\/admin\/unidata/g' src/Makefile + $(AM_V_SILENT) \ sed -i 's/\.\.\/admin\/charsets/..\/..\/admin\/charsets/g' src/Makefile # Next, edit libsrc to the location at top_srcdir! It is important # that src/Makefile uses the binaries there, instead of any # cross-compiled binaries at ./lib-src. + $(AM_V_SILENT) \ sed -i 's/libsrc =.*$$/libsrc = \.\.\/\.\.\/lib-src/g' src/Makefile # Edit out anything saying -I($(top_srcdir)/lib); that should be # covered by -I$(lib) + $(AM_V_SILENT) \ sed -i 's/-I\$$(top_srcdir)\/lib//g' src/Makefile .PHONY: src/android-emacs src/libemacs.so src/android-emacs src/libemacs.so &: src/Makefile src/config.h \ src/verbose.mk lib/libgnu.a $(PRE_BUILD_DEPS) - +make -C src android-emacs libemacs.so + $(MAKE) -C src android-emacs libemacs.so lib-src/Makefile: $(top_builddir)/lib-src/Makefile.android - mkdir -p lib-src - cp -f -p $< $@ - sed -i 's/-I\$${srcdir}\/\.\.\/lib//g' lib-src/Makefile - -.PHONY: $(LIBSRC_BINARIES) -$(LIBSRC_BINARIES) &: src/verbose.mk $(top_builddir)/$@ lib/libgnu.a \ - src/config.h lib-src/Makefile $(PRE_BUILD_DEPS) - mkdir -p src lib-src + $(AM_V_GEN) mkdir -p src lib-src + $(AM_V_SILENT) mkdir -p lib-src + $(AM_V_SILENT) cp -f -p $< $@ + $(AM_V_SILENT) sed -i 's/-I\$${srcdir}\/\.\.\/lib//g' lib-src/Makefile # Next, edit srcdir and top_srcdir to the right location. + $(AM_V_SILENT) \ sed -i 's/srcdir=.*$$/srcdir = $(subst /,\/,$(LIB_SRC_SRCDIR))/g' \ lib-src/Makefile + $(AM_V_SILENT) \ sed -i 's/top_srcdir=.*$$/top_srcdir = $(subst /,\/,$(LIB_SRC_TOP_SRCDIR))/g' \ lib-src/Makefile # Edit out SCRIPTS, it interferes with the build. + $(AM_V_SILENT) \ sed -i 's/^SCRIPTS=.*$$/SCRIPTS=/g' lib-src/Makefile + +.PHONY: $(LIBSRC_BINARIES) +$(LIBSRC_BINARIES) &: src/verbose.mk $(top_builddir)/$@ lib/libgnu.a \ + src/config.h lib-src/Makefile $(PRE_BUILD_DEPS) # Finally, go into lib-src and make everything being built - +make -C lib-src $(foreach bin,$(LIBSRC_BINARIES),$(notdir $(bin))) + $(MAKE) -C lib-src $(foreach bin,$(LIBSRC_BINARIES),$(notdir $(bin))) .PHONY: clean maintainer-clean distclean clean: diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 5bbb2245a0c..8df26560f02 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -14,12 +14,13 @@ 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. -* Android Startup:: Starting up Emacs on Android. -* Android File System:: The Android file system. -* Android Environment:: Running Emacs under Android. -* Android Windowing:: The Android window system. -* Android Fonts:: Font selection under Android. +* What is Android?:: Preamble. +* Android Startup:: Starting up Emacs on Android. +* Android File System:: The Android file system. +* Android Environment:: Running Emacs under Android. +* Android Windowing:: The Android window system. +* Android Fonts:: Font selection under Android. +* Android Troubleshooting:: Dealing with problems. @end menu @node What is Android? @@ -83,32 +84,6 @@ 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 when starting Emacs. 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. - -@cindex dumping, android - The first time any given copy of Emacs starts on a device, it spends -a while loading the preloaded Lisp files which normally come with -Emacs. This produces a ``dump file'' (@pxref{Initial Options}) in the -files directory, containing an identifier unique to this copy of -Emacs. - - The next time that same copy of Emacs starts up, it simply loads the -data contained in that dump file, greatly improving start up time. - - However, if by some unforseen circumstance the dump file is -corrupted, Emacs can crash. If that happens, the dump file stored in -the Emacs files directory can be erased through the same preferences -screen. - @cindex emacsclient wrapper, android Since there is no other way to start the @command{emacsclient} program (@pxref{Emacs Server}) from another Android program, Emacs @@ -213,7 +188,7 @@ Android. From the perspective of users, Android is mostly a single user operating system; however, from the perspective of applications and -Emacs, the system has an overwhelming number of users! +Emacs, the system has an overwhelming number of users. Each application runs in its own user, with his own home directory, which is the app data directory (@pxref{Android File System}.) @@ -418,6 +393,7 @@ 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 Scroll bars are not supported, as they are close to useless on Android devices. @@ -484,3 +460,46 @@ 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. + +@node Android Troubleshooting +@section What to do when something goes wrong on Android +@cindex troubleshooting, android + +@cindex emacs -Q, android + Since Android has no command line, there is normally no way to +specify command-line arguments when starting Emacs. This is very +nasty when you make a mistake in your Emacs initialization files that +prevents Emacs from starting up at all, as the system normally +prevents other programs from accessing Emacs's home directory. + + 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. + +@cindex dumping, android + The first time any given copy of Emacs starts on a device, it spends +a while loading the preloaded Lisp files which normally come with +Emacs. This produces a ``dump file'' (@pxref{Initial Options}) in the +files directory, containing an identifier unique to this copy of +Emacs. + + The next time that same copy of Emacs starts up, it simply loads the +data contained in that dump file, greatly improving start up time. + + If by some unforseen circumstance the dump file is corrupted, Emacs +can crash. If that happens, the dump file stored in the Emacs files +directory can be erased through the same preferences screen. + +@cindex accessing Emacs directories, Android + Emacs supports an alternative method of rescuing broken Emacs +installations on Android 4.4 and later: Emacs exports a ``documents +provider'' which accesses the contents of Emacs's home directory, that +can then be accessed by any file manager program. + + If you can find out how to open that documents provider in the file +manager that comes with your device, you can rename, delete, or edit +your initialization or dump files from there instead. diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index 59e7f5bb64b..7af636da479 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -1264,12 +1264,13 @@ Emacs and Haiku Emacs and Android -* What is Android?:: Preamble. -* Android Startup:: Starting up Emacs on Android. -* Android File System:: The Android file system. -* Android Environment:: Running Emacs under Android. -* Android Windowing:: The Android window system. -* Android Fonts:: Font selection under Android. +* What is Android?:: Preamble. +* Android Startup:: Starting up Emacs on Android. +* Android File System:: The Android file system. +* Android Environment:: Running Emacs under Android. +* Android Windowing:: The Android window system. +* Android Fonts:: Font selection under Android. +* Android Troubleshooting:: Dealing with problems. Emacs and unconventional input devices diff --git a/java/Makefile.in b/java/Makefile.in index 155754b59d2..fc0d23980e4 100644 --- a/java/Makefile.in +++ b/java/Makefile.in @@ -54,18 +54,33 @@ JARSIGNER_FLAGS = endif SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 $(JARSIGNER_FLAGS) -SIGN_EMACS_V2 = sign --v2-signing-enabled --ks emacs.keystore \ +SIGN_EMACS_V2 = sign --v2-signing-enabled --ks emacs.keystore \ --debuggable-apk-permitted --ks-pass pass:emacs1 -JAVA_FILES = $(wildcard $(srcdir)/org/gnu/emacs/*.java) -RESOURCE_FILES = $(wildcard $(srcdir)/res/drawable/*) -CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) +JAVA_FILES := $(wildcard $(srcdir)/org/gnu/emacs/*.java) +RESOURCE_FILES := $(foreach file,$(wildcard $(srcdir)/res/*), \ + $(wildcard $(file)/*)) + +# R.java is a file generated by the `aapt' utility containing +# constants that can then be used to locate ``resource identifiers''. +# It is not a regular file and should not be compiled as Java source +# code. Instead, it is automatically included by the Java compiler. +RESOURCE_FILE := $(srcdir)/org/gnu/emacs/R.java + +# CLASS_FILES is what should actually be built and included in the +# resulting Emacs executable. The Java compiler might generate more +# than one class file for each source file, so this only serves as a +# list of dependencies for Make. +CLASS_FILES := $(foreach file,$(JAVA_FILES),$(basename $(file)).class) + +# Remove RESOURCE_FILE from JAVA_FILES, if it is already present. +JAVA_FILES := $(filter-out $(RESOURCE_FILE),$(JAVA_FILES)) # 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 +ANDROID_MIN_SDK := @ANDROID_MIN_SDK@ +APK_NAME := emacs-$(version)-$(ANDROID_MIN_SDK)-$(ANDROID_ABI).apk # How this stuff works. @@ -113,19 +128,25 @@ include $(top_builddir)/cross/ndk-build/ndk-build.mk $(libsrc)/asset-directory-tool: $(MAKE) -C $(libsrc) $(notdir $@) -emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ - AndroidManifest.xml $(NDK_BUILD_SHARED) $(RESOURCE_FILES) +# install_tmp is a directory used to generate emacs.apk-in. +# That is then packaged into $(APK_NAME). + +.PHONY: install_temp install_temp/assets/directory-tree +install_temp: $(CROSS_BINS) $(CROSS_LIBS) $(NDK_BUILD_SHARED) \ + $(RESOURCE_FILES) + $(AM_V_GEN) # Make the working directory for this stuff - rm -rf install_temp - mkdir -p install_temp/lib/$(ANDROID_ABI) - mkdir -p install_temp/assets/etc - mkdir -p install_temp/assets/lisp - mkdir -p install_temp/assets/info + $(AM_V_SILENT) rm -rf install_temp + $(AM_V_SILENT) mkdir -p install_temp/lib/$(ANDROID_ABI) + $(AM_V_SILENT) mkdir -p install_temp/assets/etc + $(AM_V_SILENT) mkdir -p install_temp/assets/lisp + $(AM_V_SILENT) mkdir -p install_temp/assets/info # Install architecture independents to assets/etc and assets/lisp - cp -r $(top_builddir)/lisp install_temp/assets - cp -r $(top_builddir)/etc install_temp/assets - cp -r $(top_builddir)/info install_temp/assets + $(AM_V_SILENT) cp -r $(top_builddir)/lisp install_temp/assets + $(AM_V_SILENT) cp -r $(top_builddir)/etc install_temp/assets + $(AM_V_SILENT) cp -r $(top_builddir)/info install_temp/assets # Remove undesirable files from those directories. + $(AM_V_SILENT) \ for subdir in `find install_temp -type d -print`; do \ chmod a+rx $${subdir} ; \ rm -rf $${subdir}/.gitignore ; \ @@ -139,34 +160,44 @@ emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) $(libsrc)/asset-directory-tool \ 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. + $(AM_V_SILENT) \ for file in $(CROSS_BINS); do \ if [ -x $$file ]; then \ filename=`basename $$file`; \ cp -f $$file install_temp/lib/$(ANDROID_ABI)/lib$${filename}.so; \ fi \ done + $(AM_V_SILENT) \ for file in $(CROSS_LIBS); do \ if [ -x $$file ]; then \ cp -f $$file install_temp/lib/$(ANDROID_ABI); \ fi \ done ifneq ($(NDK_BUILD_SHARED),) - cp -f $(NDK_BUILD_SHARED) install_temp/lib/$(ANDROID_ABI) + $(AM_V_SILENT) cp -f $(NDK_BUILD_SHARED) \ + install_temp/lib/$(ANDROID_ABI) endif + +install_temp/assets/directory-tree: $(libsrc)/asset-directory-tool install_temp + $(AM_V_GEN) $(libsrc)/asset-directory-tool install_temp/assets \ + install_temp/assets/directory-tree + +emacs.apk-in: install_temp install_temp/assets/directory-tree \ + AndroidManifest.xml # Package everything. Specifying the assets on this command line is # necessary for AAssetManager_getNextFileName to work on old versions -# of Android. Make sure not to generate R.java, as it's not required -# by Emacs. - $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f \ - -M AndroidManifest.xml -A install_temp/assets \ +# of Android. Make sure not to generate R.java, as it's already been +# generated. + $(AM_V_AAPT) $(AAPT) p -I "$(ANDROID_JAR)" -F $@ \ + -f -M AndroidManifest.xml -A install_temp/assets \ -S res -J install_temp - pushd install_temp; $(AAPT) add ../$@ `find lib -type f`; popd - rm -rf install_temp + $(AM_V_SILENT) pushd install_temp &> /dev/null; \ + $(AAPT) add ../$@ `find lib -type f`; \ + popd &> /dev/null + $(AM_V_SILENT) rm -rf install_temp # Makefile itself. .PRECIOUS: ../config.status Makefile @@ -180,6 +211,14 @@ AndroidManifest.xml: $(top_srcdir)/configure.ac $(top_srcdir)/m4/*.m4 \ AndroidManifest.xml.in pushd ..; ./config.status java/AndroidManifest.xml; popd +# R.java: +$(RESOURCE_FILE): $(RESOURCE_FILES) + $(AM_V_GEN) $(AAPT) p -I "$(ANDROID_JAR)" -f \ + -J $(dir $@) -M AndroidManifest.xml -S res + +# Make all class files depend on R.java being built. +$(CLASS_FILES): $(RESOURCE_FILE) + .SUFFIXES: .java .class .java.class &: $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $< @@ -199,17 +238,18 @@ classes.dex: $(CLASS_FILES) .PHONY: clean maintainer-clean $(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" - $(ZIPALIGN) -f 4 $@.unaligned $@ + $(AM_V_GEN) + $(AM_V_SILENT) cp -f emacs.apk-in $@.unaligned + $(AM_V_SILENT) $(AAPT) add $@.unaligned classes.dex + $(AM_V_SILENT) $(JARSIGNER) $(SIGN_EMACS) $@.unaligned "Emacs keystore" + $(AM_V_SILENT) $(ZIPALIGN) -f 4 $@.unaligned $@ # Signing must happen after alignment! - $(APKSIGNER) $(SIGN_EMACS_V2) $@ - rm -f $@.unaligned *.idsig + $(AM_V_SILENT) $(APKSIGNER) $(SIGN_EMACS_V2) $@ + $(AM_V_SILENT) rm -f $@.unaligned *.idsig clean: rm -f *.apk emacs.apk-in *.dex *.unaligned *.class *.idsig - rm -rf install-temp + rm -rf install-temp $(RESOURCE_FILE) find . -name '*.class' -delete maintainer-clean distclean bootstrap-clean: clean diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java index 6cef7c37516..85639fe9236 100644 --- a/java/org/gnu/emacs/EmacsPreferencesActivity.java +++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java @@ -22,27 +22,28 @@ package org.gnu.emacs; import java.io.File; 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; +import android.widget.Toast; + +import android.preference.*; /* 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. */ + command line on Android. -public class EmacsPreferencesActivity extends Activity -{ - /* The linear layout associated with the activity. */ - private LinearLayout layout; + Android provides a preferences activity, but it is deprecated. + Unfortunately, there is no alternative that looks the same way. */ +@SuppressWarnings ("deprecation") +public 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. */ @@ -59,72 +60,82 @@ public class EmacsPreferencesActivity extends Activity System.exit (0); } + /* Erase Emacs's dump file. */ + + private void + eraseDumpFile () + { + String wantedDumpFile; + File file; + Toast toast; + + wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint () + + ".pdmp"); + file = new File (getFilesDir (), wantedDumpFile); + + 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; + + /* Display a message stating that the dump file has been + erased. */ + toast = Toast.makeText (this, "Dump file removed", + Toast.LENGTH_SHORT); + toast.show (); + } + @Override public void onCreate (Bundle savedInstanceState) { - LinearLayout layout; - TextView textView; - LinearLayout.LayoutParams params; - int resid; + Preference tem; + Preference.OnPreferenceClickListener listener; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - setTheme (R.style.Theme_DeviceDefault_Settings); + setTheme (android.R.style.Theme_DeviceDefault_Settings); else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - setTheme (R.style.Theme_DeviceDefault); + setTheme (android.R.style.Theme_DeviceDefault); + + /* This must come before using any preference APIs. */ + super.onCreate (savedInstanceState); - layout = new LinearLayout (this); - layout.setOrientation (LinearLayout.VERTICAL); - setContentView (layout); + /* Add preferences from the XML file where they are defined. */ + addPreferencesFromResource (R.xml.preferences); - textView = new TextView (this); - textView.setPadding (8, 20, 20, 8); + /* Now, set up on click handlers for each of the preferences + items. */ - 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 () { + tem = findPreference ("start_quick"); + + listener = new Preference.OnPreferenceClickListener () { @Override - public void - onClick (View view) + public boolean + onPreferenceClick (Preference preference) { startEmacsQ (); + return true; } - }); - layout.addView (textView); + }; + + tem.setOnPreferenceClickListener (listener); - textView = new TextView (this); - textView.setPadding (8, 20, 20, 8); + tem = findPreference ("erase_dump"); - params = new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT); - textView.setLayoutParams (params); - textView.setText ("Erase dump file"); - textView.setOnClickListener (new View.OnClickListener () { + listener = new Preference.OnPreferenceClickListener () { @Override - public void - onClick (View view) + public boolean + onPreferenceClick (Preference preference) { - String wantedDumpFile; - File file; - - wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint () - + ".pdmp"); - file = new File (getFilesDir (), wantedDumpFile); - - 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; + eraseDumpFile (); + return true; } - }); - layout.addView (textView); + }; - super.onCreate (savedInstanceState); + tem.setOnPreferenceClickListener (listener); } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 0416301101c..4fc8104e31f 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -181,7 +181,10 @@ public class EmacsView extends ViewGroup if (oldBitmap != null) { oldBitmap.recycle (); - surfaceView.setBitmap (null, null); + + /* Make sure to set the view's bitmap to the new bitmap, or + ugly flicker can result. */ + surfaceView.setBitmap (bitmap, null); } /* Some Android versions still don't free the bitmap until the diff --git a/java/res/xml/preferences.xml b/java/res/xml/preferences.xml new file mode 100644 index 00000000000..f0c3abb52e7 --- /dev/null +++ b/java/res/xml/preferences.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/src/verbose.mk.in b/src/verbose.mk.in index f826bf18a0a..97799cee813 100644 --- a/src/verbose.mk.in +++ b/src/verbose.mk.in @@ -36,6 +36,7 @@ AM_V_JAVAC = AM_V_DX = AM_V_AAPT = AM_V_ZIPALIGN = +AM_V_SILENT = else # Whether $(info ...) works. This is to work around a bug in GNU Make @@ -84,4 +85,6 @@ AM_V_RC = @$(info $ RC $@) # These are used for the Android port. AM_V_JAVAC = @$(info $ JAVAC $@) AM_V_D8 = @$(info $ D8 $@) +AM_V_AAPT = @$(info $ AAPT $@) +AM_V_SILENT = @ endif -- 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/EmacsView.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 a158c1d5b964fda36f752998cef076d581dc4df4 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 15 Feb 2023 12:23:03 +0800 Subject: Update Android port * configure.ac (HAVE_TEXT_CONVERSION): Define on Android. * doc/emacs/input.texi (On-Screen Keyboards): Document ``text conversion'' slightly. * doc/lispref/commands.texi (Misc Events): Document new `text-conversion' event. * java/org/gnu/emacs/EmacsContextMenu.java (display): Use `syncRunnable'. * java/org/gnu/emacs/EmacsDialog.java (display): Likewise. * java/org/gnu/emacs/EmacsEditable.java: Delete file. * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection): Reimplement from scratch. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Add new functions. * java/org/gnu/emacs/EmacsService.java (EmacsService, getEmacsView) (getLocationOnScreen, sync, getClipboardManager, restartEmacs): Use syncRunnable. (syncRunnable): New function. (updateIC, resetIC): New functions. * java/org/gnu/emacs/EmacsView.java (EmacsView): New field `inputConnection' and `icMode'. (onCreateInputConnection): Update accordingly. (setICMode, getICMode): New functions. * lisp/bindings.el (global-map): Ignore text conversion events. * src/alloc.c (mark_frame): Mark text conversion data. * src/android.c (struct android_emacs_service): New fields `update_ic' and `reset_ic'. (event_serial): Export. (android_query_sem): New function. (android_init_events): Initialize new semaphore. (android_write_event): Export. (android_select): Check for UI thread code. (setEmacsParams, android_init_emacs_service): Initialize new methods. (android_check_query, android_begin_query, android_end_query) (android_run_in_emacs_thread): (android_update_ic, android_reset_ic): New functions for managing synchronous queries from one thread to another. * src/android.h: Export new functions. * src/androidgui.h (enum android_event_type): Add input method events. (enum android_ime_operation, struct android_ime_event) (union android_event, enum android_ic_mode): New structs and enums. * src/androidterm.c (android_window_to_frame): Allow DPYINFO to be NULL. (android_decode_utf16, android_handle_ime_event) (handle_one_android_event, android_sync_edit) (android_copy_java_string, beginBatchEdit, endBatchEdit) (commitCompletion, deleteSurroundingText, finishComposingText) (getSelectedtext, getTextAfterCursor, getTextBeforeCursor) (setComposingText, setComposingRegion, setSelection, getSelection) (performEditorAction, getExtractedText): New functions. (struct android_conversion_query_context): (android_perform_conversion_query): (android_text_to_string): (struct android_get_selection_context): (android_get_selection): (struct android_get_extracted_text_context): (android_get_extracted_text): (struct android_extracted_text_request_class): (struct android_extracted_text_class): (android_update_selection): (android_reset_conversion): (android_set_point): (android_compose_region_changed): (android_notify_conversion): (text_conversion_interface): New functions and structures. (android_term_init): Initialize text conversion. * src/coding.c (syms_of_coding): Define Qutf_16le on Android. * src/frame.c (make_frame): Clear conversion data. (delete_frame): Reset conversion state. * src/frame.h (enum text_conversion_operation) (struct text_conversion_action, struct text_conversion_state) (GCALIGNED_STRUCT): Update structures. * src/keyboard.c (read_char, readable_events, kbd_buffer_get_event) (syms_of_keyboard): Handle text conversion events. * src/lisp.h: * src/process.c: Fix includes. * src/textconv.c (enum textconv_batch_edit_flags, textconv_query) (reset_frame_state, detect_conversion_events) (restore_selected_window, really_commit_text) (really_finish_composing_text, really_set_composing_text) (really_set_composing_region, really_delete_surrounding_text) (really_set_point, complete_edit) (handle_pending_conversion_events_1) (handle_pending_conversion_events, start_batch_edit) (end_batch_edit, commit_text, finish_composing_text) (set_composing_text, set_composing_region, textconv_set_point) (delete_surrounding_text, get_extracted_text) (report_selected_window_change, report_point_change) (register_texconv_interface): New functions. * src/textconv.h (struct textconv_interface) (TEXTCONV_SKIP_CONVERSION_REGION): Update prototype. * src/xdisp.c (mark_window_display_accurate_1): * src/xfns.c (xic_string_conversion_callback): * src/xterm.c (init_xterm): Adjust accordingly. --- configure.ac | 2 +- doc/emacs/input.texi | 17 + doc/lispref/commands.texi | 9 + java/org/gnu/emacs/EmacsContextMenu.java | 15 +- java/org/gnu/emacs/EmacsDialog.java | 15 +- java/org/gnu/emacs/EmacsEditable.java | 300 -------- java/org/gnu/emacs/EmacsInputConnection.java | 187 ++--- java/org/gnu/emacs/EmacsNative.java | 46 ++ java/org/gnu/emacs/EmacsService.java | 117 +-- java/org/gnu/emacs/EmacsView.java | 55 +- lisp/bindings.el | 3 + src/alloc.c | 14 + src/android.c | 295 +++++++- src/android.h | 10 + src/androidgui.h | 60 ++ src/androidterm.c | 1037 +++++++++++++++++++++++++- src/coding.c | 2 +- src/frame.c | 17 + src/frame.h | 62 ++ src/keyboard.c | 50 ++ src/lisp.h | 3 + src/process.c | 1 + src/textconv.c | 906 +++++++++++++++++++++- src/textconv.h | 38 +- src/xdisp.c | 28 + src/xfns.c | 2 +- src/xterm.c | 2 +- 27 files changed, 2787 insertions(+), 506 deletions(-) delete mode 100644 java/org/gnu/emacs/EmacsEditable.java (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/configure.ac b/configure.ac index 44529260892..fc95bcd09b6 100644 --- a/configure.ac +++ b/configure.ac @@ -7217,7 +7217,7 @@ if test "$window_system" != "none"; then [Define if you poll periodically to detect C-g.]) WINDOW_SYSTEM_OBJ="fontset.o fringe.o image.o" - if test "$window_system" = "x11"; then + if test "$window_system" = "x11" || test "$REALLY_ANDROID" = "yes"; then AC_DEFINE([HAVE_TEXT_CONVERSION], [1], [Define if the window system has text conversion support.]) WINDOW_SYSTEM_OBJ="$WINDOW_SYSTEM_OBJ textconv.o" diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi index b306a63b6cb..2463a75edcd 100644 --- a/doc/emacs/input.texi +++ b/doc/emacs/input.texi @@ -109,3 +109,20 @@ Emacs quitting. @xref{Quitting}. 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. + +@cindex text conversion, keyboards + Most input methods designed to work with on-screen keyboards perform +buffer edits differently from desktop input methods. + + On a conventional desktop windowing system, an input method will +simply display the contents of any on going character compositions on +screen, and send the appropriate key events to Emacs after completion. + + However, on screen keyboard input methods directly perform edits to +the selected window of each frame; this is known as ``text +conversion'', or ``string conversion'' under the X Window System. + + Text conversion is performed asynchronously whenever Emacs receives +a request to perform the conversion from the input method. After the +conversion completes, a @code{text-conversion} event is sent. +@xref{Misc Events,,, elisp, the Emacs Reference Manual}. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 2c0787521a5..2807d3d61b2 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2200,6 +2200,15 @@ the buffer in which the xwidget will be displayed, using A few other event types represent occurrences within the system. @table @code +@cindex @code{text-conversion} event +@item text-conversion +This kind of event is sent @strong{after} a system-wide input method +performs an edit to one or more buffers. + +Once the event is sent, the input method may already have made changes +to multiple frames. @c TODO: allow querying which frames to which +@c changes have been made. + @cindex @code{delete-frame} event @item (delete-frame (@var{frame})) This kind of event indicates that the user gave the window manager diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 92429410d03..184c611bf9d 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -279,20 +279,7 @@ public class EmacsContextMenu } }; - synchronized (runnable) - { - EmacsService.SERVICE.runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } - + EmacsService.syncRunnable (runnable); return rc.thing; } diff --git a/java/org/gnu/emacs/EmacsDialog.java b/java/org/gnu/emacs/EmacsDialog.java index bd5e9ba8ee7..bc0333e99b9 100644 --- a/java/org/gnu/emacs/EmacsDialog.java +++ b/java/org/gnu/emacs/EmacsDialog.java @@ -317,20 +317,7 @@ public class EmacsDialog implements DialogInterface.OnDismissListener } }; - synchronized (runnable) - { - EmacsService.SERVICE.runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } - + EmacsService.syncRunnable (runnable); return rc.thing; } diff --git a/java/org/gnu/emacs/EmacsEditable.java b/java/org/gnu/emacs/EmacsEditable.java deleted file mode 100644 index 79af65a6ccd..00000000000 --- a/java/org/gnu/emacs/EmacsEditable.java +++ /dev/null @@ -1,300 +0,0 @@ -/* 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 index 897a393b984..3cf4419838b 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -25,6 +25,7 @@ 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; @@ -38,138 +39,156 @@ import android.util.Log; 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; + private EmacsView view; + private short windowHandle; public EmacsInputConnection (EmacsView view) { - super (view, false); + super (view, true); + this.view = view; - this.editable = new EmacsEditable (this); + this.windowHandle = view.window.handle; } @Override - public Editable - getEditable () + public boolean + beginBatchEdit () { - return editable; + Log.d (TAG, "beginBatchEdit"); + EmacsNative.beginBatchEdit (windowHandle); + return true; } @Override public boolean - setComposingText (CharSequence text, int newCursorPosition) + endBatchEdit () { - editable.compositionStart (); - super.setComposingText (text, newCursorPosition); + Log.d (TAG, "endBatchEdit"); + EmacsNative.endBatchEdit (windowHandle); return true; } @Override public boolean - setComposingRegion (int start, int end) + commitCompletion (CompletionInfo info) { - 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)); + Log.d (TAG, "commitCompletion: " + info); + EmacsNative.commitCompletion (windowHandle, + info.getText ().toString (), + info.getPosition ()); + return true; + } - lastCommitLength = 0; - } + @Override + public boolean + commitText (CharSequence text, int newCursorPosition) + { + Log.d (TAG, "commitText: " + text + " " + newCursorPosition); + EmacsNative.commitText (windowHandle, text.toString (), + newCursorPosition); + return true; + } - editable.compositionStart (); - super.setComposingRegion (start, end); + @Override + public boolean + deleteSurroundingText (int leftLength, int rightLength) + { + Log.d (TAG, ("deleteSurroundingText: " + + leftLength + " " + rightLength)); + EmacsNative.deleteSurroundingText (windowHandle, leftLength, + rightLength); return true; } - + @Override public boolean finishComposingText () { - editable.compositionEnd (); - return super.finishComposingText (); + Log.d (TAG, "finishComposingText"); + + EmacsNative.finishComposingText (windowHandle); + return true; + } + + @Override + public String + getSelectedText (int flags) + { + Log.d (TAG, "getSelectedText: " + flags); + + return EmacsNative.getSelectedText (windowHandle, flags); + } + + @Override + public String + getTextAfterCursor (int length, int flags) + { + Log.d (TAG, "getTextAfterCursor: " + length + " " + flags); + + return EmacsNative.getTextAfterCursor (windowHandle, length, + flags); + } + + @Override + public String + getTextBeforeCursor (int length, int flags) + { + Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags); + + return EmacsNative.getTextBeforeCursor (windowHandle, length, + flags); } @Override public boolean - beginBatchEdit () + setComposingText (CharSequence text, int newCursorPosition) { - editable.beginBatchEdit (); - return super.beginBatchEdit (); + Log.d (TAG, "setComposingText: " + newCursorPosition); + + EmacsNative.setComposingText (windowHandle, text.toString (), + newCursorPosition); + return true; } @Override public boolean - endBatchEdit () + setComposingRegion (int start, int end) { - editable.endBatchEdit (); - return super.endBatchEdit (); + Log.d (TAG, "setComposingRegion: " + start + " " + end); + + EmacsNative.setComposingRegion (windowHandle, start, end); + return true; } - + @Override public boolean - commitText (CharSequence text, int newCursorPosition) + performEditorAction (int editorAction) { - 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 + "\""); + Log.d (TAG, "performEditorAction: " + editorAction); + EmacsNative.performEditorAction (windowHandle, editorAction); 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. + @Override + public ExtractedText + getExtractedText (ExtractedTextRequest request, int flags) + { + Log.d (TAG, "getExtractedText: " + request + " " + flags); + + return EmacsNative.getExtractedText (windowHandle, request, + flags); + } - There must be another way to do this, but I can't find it. */ + + /* Override functions which are not implemented. */ - public int - largeSelectionOffset () + @Override + public TextSnapshot + takeSnapshot () { - switch (currentLargeOffset) - { - case 0: - currentLargeOffset = 100000; - return 100000; - - case 100000: - currentLargeOffset = 30000; - return 30000; - - case 30000: - currentLargeOffset = 0; - return 0; - } - - currentLargeOffset = 0; - return -1; + Log.d (TAG, "takeSnapshot"); + return null; } } diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index f0219843d35..aaac9446510 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -22,6 +22,8 @@ package org.gnu.emacs; import java.lang.System; import android.content.res.AssetManager; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; public class EmacsNative { @@ -161,6 +163,50 @@ public class EmacsNative descriptor, or NULL if there is none. */ public static native byte[] getProcName (int fd); + /* Notice that the Emacs thread will now start waiting for the main + thread's looper to respond. */ + public static native void beginSynchronous (); + + /* Notice that the Emacs thread will has finished waiting for the + main thread's looper to respond. */ + public static native void endSynchronous (); + + + + /* Input connection functions. These mostly correspond to their + counterparts in Android's InputConnection. */ + + public static native void beginBatchEdit (short window); + public static native void endBatchEdit (short window); + public static native void commitCompletion (short window, String text, + int position); + public static native void commitText (short window, String text, + int position); + public static native void deleteSurroundingText (short window, + int leftLength, + int rightLength); + public static native void finishComposingText (short window); + public static native String getSelectedText (short window, int flags); + public static native String getTextAfterCursor (short window, int length, + int flags); + public static native String getTextBeforeCursor (short window, int length, + int flags); + public static native void setComposingText (short window, String text, + int newCursorPosition); + public static native void setComposingRegion (short window, int start, + int end); + public static native void setSelection (short window, int start, int end); + public static native void performEditorAction (short window, + int editorAction); + public static native ExtractedText getExtractedText (short window, + ExtractedTextRequest req, + int flags); + + + /* Return the current value of the selection, or -1 upon + failure. */ + public static native int getSelection (short window); + static { /* Older versions of Android cannot link correctly with shared diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 2ec2ddf9bda..855a738a30f 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -80,6 +80,11 @@ public class EmacsService extends Service private EmacsThread thread; private Handler handler; + /* Keep this in synch with androidgui.h. */ + public static final int IC_MODE_NULL = 0; + public static final int IC_MODE_ACTION = 1; + public static final int IC_MODE_TEXT = 2; + /* Display metrics used by font backends. */ public DisplayMetrics metrics; @@ -258,20 +263,7 @@ public class EmacsService extends Service } }; - synchronized (runnable) - { - runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } - + syncRunnable (runnable); return view.thing; } @@ -292,19 +284,7 @@ public class EmacsService extends Service } }; - synchronized (runnable) - { - runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } + syncRunnable (runnable); } public void @@ -502,19 +482,7 @@ public class EmacsService extends Service } }; - synchronized (runnable) - { - runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } + syncRunnable (runnable); } @@ -594,20 +562,7 @@ public class EmacsService extends Service } }; - synchronized (runnable) - { - runOnUiThread (runnable); - - try - { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); - } - } - + syncRunnable (runnable); return manager.thing; } @@ -622,4 +577,58 @@ public class EmacsService extends Service startActivity (intent); System.exit (0); } + + /* Wait synchronously for the specified RUNNABLE to complete in the + UI thread. Must be called from the Emacs thread. */ + + public static void + syncRunnable (Runnable runnable) + { + EmacsNative.beginSynchronous (); + + synchronized (runnable) + { + SERVICE.runOnUiThread (runnable); + + while (true) + { + try + { + runnable.wait (); + break; + } + catch (InterruptedException e) + { + continue; + } + } + } + + EmacsNative.endSynchronous (); + } + + public void + updateIC (EmacsWindow window, int newSelectionStart, + int newSelectionEnd, int composingRegionStart, + int composingRegionEnd) + { + Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart + + " " + newSelectionEnd + " " + + composingRegionStart + " " + + composingRegionEnd)); + window.view.imManager.updateSelection (window.view, + newSelectionStart, + newSelectionEnd, + composingRegionStart, + composingRegionEnd); + } + + public void + resetIC (EmacsWindow window, int icMode) + { + Log.d (TAG, "resetIC: " + window); + + window.view.setICMode (icMode); + window.view.imManager.restartInput (window.view); + } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index bc3716f6da8..52da6d41f7d 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -103,6 +103,13 @@ public class EmacsView extends ViewGroup displayed whenever possible. */ public boolean isCurrentlyTextEditor; + /* The associated input connection. */ + private EmacsInputConnection inputConnection; + + /* The current IC mode. See `android_reset_ic' for more + details. */ + private int icMode; + public EmacsView (EmacsWindow window) { @@ -554,14 +561,46 @@ public class EmacsView extends ViewGroup public InputConnection onCreateInputConnection (EditorInfo info) { + int selection, mode; + + /* Figure out what kind of IME behavior Emacs wants. */ + mode = getICMode (); + /* Make sure the input method never displays a full screen input box that obscures Emacs. */ info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; /* Set a reasonable inputType. */ - info.inputType = InputType.TYPE_NULL; + info.inputType = InputType.TYPE_CLASS_TEXT; + + /* Obtain the current position of point and set it as the + selection. */ + selection = EmacsNative.getSelection (window.handle); + + Log.d (TAG, "onCreateInputConnection: current selection is: " + selection); + + /* If this fails or ANDROID_IC_MODE_NULL was requested, then don't + initialize the input connection. */ + if (selection == -1 || mode == EmacsService.IC_MODE_NULL) + { + info.inputType = InputType.TYPE_NULL; + return null; + } + + if (mode == EmacsService.IC_MODE_ACTION) + info.imeOptions |= EditorInfo.IME_ACTION_DONE; - return null; + /* Set the initial selection fields. */ + info.initialSelStart = selection; + info.initialSelEnd = selection; + + /* Create the input connection if necessary. */ + + if (inputConnection == null) + inputConnection = new EmacsInputConnection (this); + + /* Return the input connection. */ + return inputConnection; } @Override @@ -572,4 +611,16 @@ public class EmacsView extends ViewGroup keyboard. */ return isCurrentlyTextEditor; } + + public synchronized void + setICMode (int icMode) + { + this.icMode = icMode; + } + + public synchronized int + getICMode () + { + return icMode; + } }; diff --git a/lisp/bindings.el b/lisp/bindings.el index c77b64c05da..057c870958d 100644 --- a/lisp/bindings.el +++ b/lisp/bindings.el @@ -1521,6 +1521,9 @@ if `inhibit-field-text-motion' is non-nil." (define-key special-event-map [sigusr1] 'ignore) (define-key special-event-map [sigusr2] 'ignore) +;; Text conversion +(define-key global-map [text-conversion] 'ignore) + ;; Don't look for autoload cookies in this file. ;; Local Variables: ;; no-update-autoloads: t diff --git a/src/alloc.c b/src/alloc.c index bc43f22005d..6d8658e7bb0 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -6939,6 +6939,11 @@ static void mark_frame (struct Lisp_Vector *ptr) { struct frame *f = (struct frame *) ptr; +#ifdef HAVE_TEXT_CONVERSION + struct text_conversion_action *tem; +#endif + + mark_vectorlike (&ptr->header); mark_face_cache (f->face_cache); #ifdef HAVE_WINDOW_SYSTEM @@ -6950,6 +6955,15 @@ mark_frame (struct Lisp_Vector *ptr) mark_vectorlike (&font->header); } #endif + +#ifdef HAVE_TEXT_CONVERSION + mark_object (f->conversion.compose_region_start); + mark_object (f->conversion.compose_region_end); + mark_object (f->conversion.compose_region_overlay); + + for (tem = f->conversion.actions; tem; tem = tem->next) + mark_object (tem->data); +#endif } static void diff --git a/src/android.c b/src/android.c index 479eb9b10d4..8f446224dab 100644 --- a/src/android.c +++ b/src/android.c @@ -98,6 +98,8 @@ struct android_emacs_service jmethodID sync; jmethodID browse_url; jmethodID restart_emacs; + jmethodID update_ic; + jmethodID reset_ic; }; struct android_emacs_pixmap @@ -207,7 +209,7 @@ 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; +unsigned int event_serial; /* Unused pointer used to control compiler optimizations. */ void *unused_pointer; @@ -408,6 +410,10 @@ android_handle_sigusr1 (int sig, siginfo_t *siginfo, void *arg) #endif +/* Semaphore used to indicate completion of a query. + This should ideally be defined further down. */ +static sem_t android_query_sem; + /* Set up the global event queue by initializing the mutex and two condition variables, and the linked list of events. This must be called before starting the Emacs thread. Also, initialize the @@ -438,6 +444,7 @@ android_init_events (void) sem_init (&android_pselect_sem, 0, 0); sem_init (&android_pselect_start_sem, 0, 0); + sem_init (&android_query_sem, 0, 0); event_queue.events.next = &event_queue.events; event_queue.events.last = &event_queue.events; @@ -538,7 +545,7 @@ android_next_event (union android_event *event_return) pthread_mutex_unlock (&event_queue.mutex); } -static void +void android_write_event (union android_event *event) { struct android_event_container *container; @@ -576,6 +583,10 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds, static char byte; #endif + /* Check for and run anything the UI thread wants to run on the main + thread. */ + android_check_query (); + pthread_mutex_lock (&event_queue.mutex); if (event_queue.num_events) @@ -634,6 +645,10 @@ android_select (int nfds, fd_set *readfds, fd_set *writefds, if (nfds_return < 0) errno = EINTR; + /* Now check for and run anything the UI thread wants to run in the + main thread. */ + android_check_query (); + return nfds_return; } @@ -1431,7 +1446,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object, /* This may be called from multiple threads. setEmacsParams should only ever be called once. */ - if (__atomic_fetch_add (&emacs_initialized, -1, __ATOMIC_RELAXED)) + if (__atomic_fetch_add (&emacs_initialized, -1, __ATOMIC_SEQ_CST)) { ANDROID_THROW (env, "java/lang/IllegalArgumentException", "Emacs was already initialized!"); @@ -1705,6 +1720,10 @@ android_init_emacs_service (void) FIND_METHOD (browse_url, "browseUrl", "(Ljava/lang/String;)" "Ljava/lang/String;"); FIND_METHOD (restart_emacs, "restartEmacs", "()V"); + FIND_METHOD (update_ic, "updateIC", + "(Lorg/gnu/emacs/EmacsWindow;IIII)V"); + FIND_METHOD (reset_ic, "resetIC", + "(Lorg/gnu/emacs/EmacsWindow;I)V"); #undef FIND_METHOD } @@ -1834,7 +1853,7 @@ android_init_emacs_window (void) #undef FIND_METHOD } -extern JNIEXPORT void JNICALL +JNIEXPORT void JNICALL NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, jobject dump_file_object, jint api_level) { @@ -1928,19 +1947,19 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv, emacs_abort (); } -extern JNIEXPORT void JNICALL +JNIEXPORT void JNICALL NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object) { emacs_abort (); } -extern JNIEXPORT void JNICALL +JNIEXPORT void JNICALL NATIVE_NAME (quit) (JNIEnv *env, jobject object) { Vquit_flag = Qt; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, jshort window, jlong time, jint x, jint y, jint width, @@ -1961,7 +1980,7 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, jshort window, jlong time, jint state, jint keycode, @@ -1981,7 +2000,7 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, jshort window, jlong time, jint state, jint keycode, @@ -2001,7 +2020,7 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object, jshort window, jlong time) { @@ -2016,7 +2035,7 @@ NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object, jshort window, jlong time) { @@ -2031,7 +2050,7 @@ NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object, return ++event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object, jshort window, jint action) { @@ -2046,7 +2065,7 @@ NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -2064,7 +2083,7 @@ NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -2082,7 +2101,7 @@ NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time) @@ -2100,7 +2119,7 @@ NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -2121,7 +2140,7 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -2142,7 +2161,7 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -2161,7 +2180,7 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -2180,7 +2199,7 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint pointer_id) @@ -2199,7 +2218,7 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jlong time, jint state, @@ -2221,7 +2240,7 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object, jshort window) { @@ -2235,7 +2254,7 @@ NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object, jshort window) { @@ -2249,7 +2268,7 @@ NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object, jshort window, jint menu_event_id) { @@ -2264,7 +2283,7 @@ NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object, return event_serial; } -extern JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object, jshort window, jint x, jint y, jint width, jint height) @@ -2283,6 +2302,23 @@ NATIVE_NAME (sendExpose) (JNIEnv *env, jobject object, return event_serial; } +/* Forward declarations of deadlock prevention functions. */ + +static void android_begin_query (void); +static void android_end_query (void); + +JNIEXPORT void JNICALL +NATIVE_NAME (beginSynchronous) (JNIEnv *env, jobject object) +{ + android_begin_query (); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (endSynchronous) (JNIEnv *env, jobject object) +{ + android_end_query (); +} + #ifdef __clang__ #pragma clang diagnostic pop #else @@ -5155,6 +5191,215 @@ android_get_current_api_level (void) +/* Whether or not a query is currently being made. */ +static bool android_servicing_query; + +/* Function that is waiting to be run in the Emacs thread. */ +static void (*android_query_function) (void *); + +/* Context for that function. */ +static void *android_query_context; + +/* Deadlock protection. The UI thread and the Emacs thread must + sometimes make synchronous queries to each other, which are + normally answered inside each thread's respective event loop. + Deadlocks can happen when both threads simultaneously make such + synchronous queries and block waiting for each others responses. + + The Emacs thread can be interrupted to service any queries made by + the UI thread, but is not possible the other way around. + + To avoid such deadlocks, an atomic counter is provided. This + counter is incremented every time a query starts, and is set to + zerp every time one ends. If the UI thread tries to make a query + and sees that the counter is non-zero, it simply returns so that + its event loop can proceed to perform and respond to the query. If + the Emacs thread sees the same thing, then it stops to service all + queries being made by the input method, then proceeds to make its + query. */ + +/* Run any function that the UI thread has asked to run, and then + signal its completion. */ + +void +android_check_query (void) +{ + void (*proc) (void *); + void *closure; + + if (!__atomic_load_n (&android_servicing_query, __ATOMIC_SEQ_CST)) + return; + + /* First, load the procedure and closure. */ + __atomic_load (&android_query_context, &closure, __ATOMIC_SEQ_CST); + __atomic_load (&android_query_function, &proc, __ATOMIC_SEQ_CST); + + if (!proc) + return; + + proc (closure); + + /* Finish the query. */ + __atomic_store_n (&android_query_context, NULL, __ATOMIC_SEQ_CST); + __atomic_store_n (&android_query_function, NULL, __ATOMIC_SEQ_CST); + __atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST); + + /* Signal completion. */ + sem_post (&android_query_sem); +} + +/* Notice that the Emacs thread will start blocking waiting for a + response from the UI thread. Process any pending queries from the + UI thread. + + This function may be called from Java. */ + +static void +android_begin_query (void) +{ + if (__atomic_test_and_set (&android_servicing_query, + __ATOMIC_SEQ_CST)) + { + /* Answer the query that is currently being made. */ + assert (android_query_function != NULL); + android_check_query (); + + /* Wait for that query to complete. */ + while (__atomic_load_n (&android_servicing_query, + __ATOMIC_SEQ_CST)) + ;; + } +} + +/* Notice that a query has stopped. This function may be called from + Java. */ + +static void +android_end_query (void) +{ + __atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST); +} + +/* Synchronously ask the Emacs thread to run the specified PROC with + the given CLOSURE. Return if this fails, or once PROC is run. + + PROC may be run from inside maybe_quit. + + It is not okay to run Lisp code which signals or performs non + trivial tasks inside PROC. + + Return 1 if the Emacs thread is currently waiting for the UI thread + to respond and PROC could not be run, or 0 otherwise. */ + +int +android_run_in_emacs_thread (void (*proc) (void *), void *closure) +{ + union android_event event; + + event.xaction.type = ANDROID_WINDOW_ACTION; + event.xaction.serial = ++event_serial; + event.xaction.window = 0; + event.xaction.action = 0; + + /* Set android_query_function and android_query_context. */ + __atomic_store_n (&android_query_context, closure, __ATOMIC_SEQ_CST); + __atomic_store_n (&android_query_function, proc, __ATOMIC_SEQ_CST); + + /* Don't allow deadlocks to happen; make sure the Emacs thread is + not waiting for something to be done. */ + + if (__atomic_test_and_set (&android_servicing_query, + __ATOMIC_SEQ_CST)) + { + __atomic_store_n (&android_query_context, NULL, + __ATOMIC_SEQ_CST); + __atomic_store_n (&android_query_function, NULL, + __ATOMIC_SEQ_CST); + + return 1; + } + + /* Send a dummy event. `android_check_query' will be called inside + wait_reading_process_output after the event arrives. + + Otherwise, android_select will call android_check_thread the next + time it is entered. */ + android_write_event (&event); + + /* Start waiting for the function to be executed. */ + while (sem_wait (&android_query_sem) < 0) + ;; + + return 0; +} + + + +/* Input method related functions. */ + +/* Change WINDOW's active selection to the characters between + SELECTION_START and SELECTION_END. + + Also, update the composing region to COMPOSING_REGION_START and + COMPOSING_REGION_END. + + If any value cannot fit in jint, then the behavior of the input + method is undefined. */ + +void +android_update_ic (android_window window, ptrdiff_t selection_start, + ptrdiff_t selection_end, ptrdiff_t composing_region_start, + ptrdiff_t composing_region_end) +{ + jobject object; + + 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_exception_check (); +} + +/* Reinitialize any ongoing input method connection on WINDOW. + + Any input method that is connected to WINDOW will invalidate its + cache of the buffer contents. + + MODE controls certain aspects of the input method's behavior: + + - If MODE is ANDROID_IC_MODE_NULL, the input method will be + deactivated, and an ASCII only keyboard will be displayed + instead. + + - If MODE is ANDROID_IC_MODE_ACTION, the input method will + edit text normally, but send ``return'' as a key event. + This is useful inside the mini buffer. + + - If MODE is ANDROID_IC_MODE_TEXT, the input method is free + to behave however it wants. */ + +void +android_reset_ic (android_window window, enum android_ic_mode mode) +{ + jobject object; + + 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_exception_check (); +} + + + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ diff --git a/src/android.h b/src/android.h index 9006f5f34c5..ec4fa33dfc3 100644 --- a/src/android.h +++ b/src/android.h @@ -108,6 +108,16 @@ extern void android_closedir (struct android_dir *); extern Lisp_Object android_browse_url (Lisp_Object); + + +/* Event loop functions. */ + +extern void android_check_query (void); +extern int android_run_in_emacs_thread (void (*) (void *), void *); +extern void android_write_event (union android_event *); + +extern unsigned int event_serial; + #endif /* JNI functions should not be built when Emacs is stubbed out for the diff --git a/src/androidgui.h b/src/androidgui.h index 1f0d34e67aa..25dc6754fff 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -235,6 +235,7 @@ enum android_event_type ANDROID_DEICONIFIED, ANDROID_CONTEXT_MENU, ANDROID_EXPOSE, + ANDROID_INPUT_METHOD, }; struct android_any_event @@ -419,6 +420,52 @@ struct android_menu_event int menu_event_id; }; +enum android_ime_operation + { + ANDROID_IME_COMMIT_TEXT, + ANDROID_IME_DELETE_SURROUNDING_TEXT, + ANDROID_IME_FINISH_COMPOSING_TEXT, + ANDROID_IME_SET_COMPOSING_TEXT, + ANDROID_IME_SET_COMPOSING_REGION, + ANDROID_IME_SET_POINT, + ANDROID_IME_START_BATCH_EDIT, + ANDROID_IME_END_BATCH_EDIT, + }; + +struct android_ime_event +{ + /* Type of the event. */ + enum android_event_type type; + + /* The event serial. */ + unsigned long serial; + + /* The associated window. */ + android_window window; + + /* What operation is being performed. */ + enum android_ime_operation operation; + + /* The details of the operation. START and END provide buffer + indices, and may actually mean ``left'' and ``right''. */ + ptrdiff_t start, end, position; + + /* The number of characters in TEXT. */ + size_t length; + + /* TEXT is either NULL, or a pointer to LENGTH bytes of malloced + UTF-16 encoded text that must be decoded by Emacs. + + POSITION is where point should end up after the text is + committed, relative to TEXT. If POSITION is less than 0, it is + relative to TEXT's start; otherwise, it is relative to its + end. */ + unsigned short *text; + + /* Value to set the counter to after the operation completes. */ + unsigned long counter; +}; + union android_event { enum android_event_type type; @@ -447,6 +494,9 @@ union android_event /* This is only used to transmit selected menu items. */ struct android_menu_event menu; + + /* This is used to dispatch input method editing requests. */ + struct android_ime_event ime; }; enum @@ -463,6 +513,13 @@ enum android_lookup_status ANDROID_LOOKUP_BOTH, }; +enum android_ic_mode + { + ANDROID_IC_MODE_NULL = 0, + ANDROID_IC_MODE_ACTION = 1, + ANDROID_IC_MODE_TEXT = 2, + }; + extern int android_pending (void); extern void android_next_event (union android_event *); @@ -554,6 +611,9 @@ extern void android_sync (void); extern int android_wc_lookup_string (android_key_pressed_event *, wchar_t *, int, int *, enum android_lookup_status *); +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); #endif diff --git a/src/androidterm.c b/src/androidterm.c index a57d5623c66..a44bae954da 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -20,6 +20,9 @@ along with GNU Emacs. If not, see . */ #include #include #include +#include +#include +#include #include "lisp.h" #include "androidterm.h" @@ -28,6 +31,8 @@ along with GNU Emacs. If not, see . */ #include "android.h" #include "buffer.h" #include "window.h" +#include "textconv.h" +#include "coding.h" /* This is a chain of structures for all the X displays currently in use. */ @@ -59,6 +64,12 @@ enum ANDROID_EVENT_DROP, }; +/* Find the frame whose window has the identifier WDESC. + + This is like x_window_to_frame in xterm.c, except that DPYINFO may + be NULL, as there is only at most one Android display, and is only + specified in order to stay consistent with X. */ + static struct frame * android_window_to_frame (struct android_display_info *dpyinfo, android_window wdesc) @@ -73,7 +84,7 @@ android_window_to_frame (struct android_display_info *dpyinfo, { f = XFRAME (frame); - if (!FRAME_ANDROID_P (f) || FRAME_DISPLAY_INFO (f) != dpyinfo) + if (!FRAME_ANDROID_P (f)) continue; if (FRAME_ANDROID_WINDOW (f) == wdesc) @@ -527,6 +538,103 @@ android_find_tool (struct frame *f, int pointer_id) return NULL; } +/* Decode STRING, an array of N little endian UTF-16 characters, into + a Lisp string. Return Qnil if the string is too large, and the + encoded string otherwise. */ + +static Lisp_Object +android_decode_utf16 (unsigned short *utf16, size_t n) +{ + struct coding_system coding; + ptrdiff_t size; + + if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size)) + return Qnil; + + /* Set up the coding system. Decoding a UTF-16 string (with no BOM) + should not signal. */ + + memset (&coding, 0, sizeof coding); + + setup_coding_system (Qutf_16le, &coding); + coding.source = (const unsigned char *) utf16; + decode_coding_object (&coding, Qnil, 0, 0, size, + size, Qt); + + return coding.dst_object; +} + +/* Handle a single input method event EVENT, delivered to the frame + F. + + Perform the text conversion action specified inside. */ + +static void +android_handle_ime_event (union android_event *event, struct frame *f) +{ + Lisp_Object text; + + /* First, decode the text if necessary. */ + + switch (event->ime.operation) + { + case ANDROID_IME_COMMIT_TEXT: + case ANDROID_IME_FINISH_COMPOSING_TEXT: + case ANDROID_IME_SET_COMPOSING_TEXT: + text = android_decode_utf16 (event->ime.text, + event->ime.length); + xfree (event->ime.text); + break; + + default: + break; + } + + /* Finally, perform the appropriate conversion action. */ + + switch (event->ime.operation) + { + case ANDROID_IME_COMMIT_TEXT: + commit_text (f, text, event->ime.position, + event->ime.counter); + break; + + case ANDROID_IME_DELETE_SURROUNDING_TEXT: + delete_surrounding_text (f, event->ime.start, + event->ime.end, + event->ime.counter); + break; + + case ANDROID_IME_FINISH_COMPOSING_TEXT: + finish_composing_text (f, event->ime.counter); + break; + + case ANDROID_IME_SET_COMPOSING_TEXT: + set_composing_text (f, text, event->ime.position, + event->ime.counter); + break; + + case ANDROID_IME_SET_COMPOSING_REGION: + set_composing_region (f, event->ime.start, + event->ime.end, + event->ime.counter); + break; + + case ANDROID_IME_SET_POINT: + textconv_set_point (f, event->ime.position, + event->ime.counter); + break; + + case ANDROID_IME_START_BATCH_EDIT: + start_batch_edit (f, event->ime.counter); + break; + + case ANDROID_IME_END_BATCH_EDIT: + end_batch_edit (f, event->ime.counter); + break; + } +} + static int handle_one_android_event (struct android_display_info *dpyinfo, union android_event *event, int *finish, @@ -745,6 +853,17 @@ handle_one_android_event (struct android_display_info *dpyinfo, case ANDROID_WINDOW_ACTION: + /* This is a special event sent by android_run_in_emacs_thread + used to make Android run stuff. */ + + if (!event->xaction.window && !event->xaction.action) + { + /* Check for and run anything the UI thread wants to run on the main + thread. */ + android_check_query (); + goto OTHER; + } + f = any; if (event->xaction.action == 0) @@ -1334,6 +1453,19 @@ handle_one_android_event (struct android_display_info *dpyinfo, goto OTHER; + /* Input method events. textconv.c functions are called here to + queue events, which are then executed in a safe context + inside keyboard.c. */ + case ANDROID_INPUT_METHOD: + + if (!any) + /* Free any text allocated for this event. */ + xfree (event->ime.text); + else + android_handle_ime_event (event, any); + + goto OTHER; + default: goto OTHER; } @@ -4148,6 +4280,904 @@ android_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1) +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-prototypes" +#endif + +/* Input method related functions. Some of these are called from Java + within the UI thread. */ + +/* A counter used to decide when an editing request completes. */ +static unsigned long edit_counter; + +/* The last counter known to have completed. */ +static unsigned long last_edit_counter; + +/* Semaphore posted every time the counter increases. */ +static sem_t edit_sem; + +/* Try to synchronize with the UI thread, waiting a certain amount of + time for outstanding editing requests to complete. + + Every time one of the text retrieval functions is called and an + editing request is made, Emacs gives the main thread approximately + 50 ms to process it, in order to mostly keep the input method in + sync with the buffer contents. */ + +static void +android_sync_edit (void) +{ + struct timespec start, end, rem; + + if (__atomic_load_n (&last_edit_counter, + __ATOMIC_SEQ_CST) + == edit_counter) + return; + + start = current_timespec (); + end = timespec_add (start, make_timespec (0, 50000000)); + + while (true) + { + rem = timespec_sub (end, current_timespec ()); + + /* Timeout. */ + if (timespec_sign (rem) < 0) + break; + + if (__atomic_load_n (&last_edit_counter, + __ATOMIC_SEQ_CST) + == edit_counter) + break; + + sem_timedwait (&edit_sem, &end); + } +} + +/* Return a copy of the specified Java string and its length in + *LENGTH. Use the JNI environment ENV. Value is NULL if copying + *the string fails. */ + +static unsigned short * +android_copy_java_string (JNIEnv *env, jstring string, size_t *length) +{ + jsize size, i; + const jchar *java; + unsigned short *buffer; + + size = (*env)->GetStringLength (env, string); + buffer = malloc (size * sizeof *buffer); + + if (!buffer) + return NULL; + + java = (*env)->GetStringChars (env, string, NULL); + + if (!java) + { + free (buffer); + return NULL; + } + + for (i = 0; i < size; ++i) + buffer[i] = java[i]; + + *length = size; + (*env)->ReleaseStringChars (env, string, java); + return buffer; +} + +JNIEXPORT void JNICALL +NATIVE_NAME (beginBatchEdit) (JNIEnv *env, jobject object, jshort window) +{ + union android_event event; + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_START_BATCH_EDIT; + event.ime.start = 0; + event.ime.end = 0; + event.ime.length = 0; + event.ime.position = 0; + event.ime.text = NULL; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (endBatchEdit) (JNIEnv *env, jobject object, jshort window) +{ + union android_event event; + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_END_BATCH_EDIT; + event.ime.start = 0; + event.ime.end = 0; + event.ime.length = 0; + event.ime.position = 0; + event.ime.text = NULL; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (commitCompletion) (JNIEnv *env, jobject object, jshort window, + jstring completion_text, jint position) +{ + union android_event event; + unsigned short *text; + size_t length; + + /* First, obtain a copy of the Java string. */ + text = android_copy_java_string (env, completion_text, &length); + + if (!text) + return; + + /* Next, populate the event. Events will always eventually be + delivered on Android, so handle_one_android_event can be relied + on to free text. */ + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_COMMIT_TEXT; + event.ime.start = 0; + event.ime.end = 0; + event.ime.length = min (length, PTRDIFF_MAX); + event.ime.position = position; + event.ime.text = text; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (commitText) (JNIEnv *env, jobject object, jshort window, + jstring commit_text, jint position) +{ + union android_event event; + unsigned short *text; + size_t length; + + /* First, obtain a copy of the Java string. */ + text = android_copy_java_string (env, commit_text, &length); + + if (!text) + return; + + /* Next, populate the event. Events will always eventually be + delivered on Android, so handle_one_android_event can be relied + on to free text. */ + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_COMMIT_TEXT; + event.ime.start = 0; + event.ime.end = 0; + event.ime.length = min (length, PTRDIFF_MAX); + event.ime.position = position; + event.ime.text = text; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (deleteSurroundingText) (JNIEnv *env, jobject object, + jshort window, jint left_length, + jint right_length) +{ + union android_event event; + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_DELETE_SURROUNDING_TEXT; + event.ime.start = left_length; + event.ime.end = right_length; + event.ime.length = 0; + event.ime.position = 0; + event.ime.text = NULL; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (finishComposingText) (JNIEnv *env, jobject object, + jshort window) +{ + union android_event event; + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT; + event.ime.start = 0; + event.ime.end = 0; + event.ime.length = 0; + event.ime.position = 0; + event.ime.text = NULL; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + +/* Structure describing the context used for a text query. */ + +struct android_conversion_query_context +{ + /* The conversion request. */ + struct textconv_callback_struct query; + + /* The window the request is being made on. */ + android_window window; + + /* Whether or not the request was successful. */ + bool success; +}; + +/* 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. */ + +static void +android_perform_conversion_query (void *data) +{ + struct android_conversion_query_context *context; + struct frame *f; + + context = data; + + /* Find the frame associated with the window. */ + f = android_window_to_frame (NULL, context->window); + + if (!f) + return; + + textconv_query (f, &context->query, + TEXTCONV_SKIP_CONVERSION_REGION); + + /* context->query.text will have been set even if textconv_query + returns 1. */ + + context->success = true; +} + +/* Convert a string BUFFERS containing N characters in Emacs's + internal multibyte encoding to a Java string utilizing the + specified JNI environment. + + If N is equal to BYTES, then BUFFER is a single byte buffer. + Otherwise, BUFFER is a multibyte buffer. + + Make sure N and BYTES are absolutely correct, or you are asking for + trouble. + + Value is the string upon success, NULL otherwise. Any exceptions + generated are not cleared. */ + +static jstring +android_text_to_string (JNIEnv *env, char *buffer, ptrdiff_t n, + ptrdiff_t bytes) +{ + jchar *utf16; + size_t size, index; + jstring string; + int encoded; + + if (n == bytes) + { + /* This buffer holds no multibyte characters. */ + + if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size)) + return NULL; + + utf16 = malloc (size); + index = 0; + + if (!utf16) + return NULL; + + while (n--) + { + utf16[index] = buffer[index]; + index++; + } + + string = (*env)->NewString (env, utf16, bytes); + free (utf16); + + return string; + } + + /* Allocate enough to hold N characters. */ + + if (INT_MULTIPLY_WRAPV (n, sizeof *utf16, &size)) + return NULL; + + utf16 = malloc (size); + index = 0; + + if (!utf16) + return NULL; + + while (n--) + { + eassert (CHAR_HEAD_P (*buffer)); + encoded = STRING_CHAR ((unsigned char *) buffer); + + /* Now figure out how to save ENCODED into the string. + Emacs operates on multibyte characters, not UTF-16 + characters with surrogate pairs as Android does. + + However, character positions in Java are represented in 2 + byte units, meaning that the text position reported to + Android can become out of sync if characters are found in a + buffer that require surrogate pairs. + + The hack used by Emacs is to simply replace each multibyte + character that doesn't fit in a jchar with the NULL + character. */ + + if (encoded >= 65536) + encoded = 0; + + utf16[index++] = encoded; + buffer += BYTES_BY_CHAR_HEAD (*buffer); + } + + /* Create the string. */ + string = (*env)->NewString (env, utf16, index); + free (utf16); + return string; +} + +JNIEXPORT jstring JNICALL +NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object, + jshort window) +{ + return NULL; +} + +JNIEXPORT jstring JNICALL +NATIVE_NAME (getTextAfterCursor) (JNIEnv *env, jobject object, jshort window, + jint length, jint flags) +{ + struct android_conversion_query_context context; + jstring string; + + /* First, set up the conversion query. */ + context.query.position = 0; + context.query.direction = TEXTCONV_FORWARD_CHAR; + context.query.factor = min (length, 65535); + context.query.operation = TEXTCONV_RETRIEVAL; + + /* Next, set the rest of the context. */ + context.window = window; + context.success = false; + + /* Now try to perform the query. */ + android_sync_edit (); + if (android_run_in_emacs_thread (android_perform_conversion_query, + &context)) + return NULL; + + if (!context.success) + return NULL; + + /* context->query.text now contains the text in Emacs's internal + UTF-8 based encoding. + + Convert it to Java's UTF-16 encoding, which is the same as + UTF-16, except that NULL bytes are encoded as surrogate pairs. + + This assumes that `free' can free data allocated with xmalloc. */ + + string = android_text_to_string (env, context.query.text.text, + context.query.text.length, + context.query.text.bytes); + free (context.query.text.text); + + return string; +} + +JNIEXPORT jstring JNICALL +NATIVE_NAME (getTextBeforeCursor) (JNIEnv *env, jobject object, jshort window, + jint length, jint flags) +{ + struct android_conversion_query_context context; + jstring string; + + /* First, set up the conversion query. */ + context.query.position = 0; + context.query.direction = TEXTCONV_BACKWARD_CHAR; + context.query.factor = min (length, 65535); + context.query.operation = TEXTCONV_RETRIEVAL; + + /* Next, set the rest of the context. */ + context.window = window; + context.success = false; + + /* Now try to perform the query. */ + android_sync_edit (); + if (android_run_in_emacs_thread (android_perform_conversion_query, + &context)) + return NULL; + + if (!context.success) + return NULL; + + /* context->query.text now contains the text in Emacs's internal + UTF-8 based encoding. + + Convert it to Java's UTF-16 encoding, which is the same as + UTF-16, except that NULL bytes are encoded as surrogate pairs. + + This assumes that `free' can free data allocated with xmalloc. */ + + string = android_text_to_string (env, context.query.text.text, + context.query.text.length, + context.query.text.bytes); + free (context.query.text.text); + + return string; +} + +JNIEXPORT void JNICALL +NATIVE_NAME (setComposingText) (JNIEnv *env, jobject object, jshort window, + jstring composing_text, + jint new_cursor_position) +{ + union android_event event; + unsigned short *text; + size_t length; + + /* First, obtain a copy of the Java string. */ + text = android_copy_java_string (env, composing_text, &length); + + if (!text) + return; + + /* Next, populate the event. Events will always eventually be + delivered on Android, so handle_one_android_event can be relied + on to free text. */ + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_SET_COMPOSING_TEXT; + event.ime.start = 0; + event.ime.end = 0; + event.ime.length = min (length, PTRDIFF_MAX); + event.ime.position = new_cursor_position; + event.ime.text = text; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (setComposingRegion) (JNIEnv *env, jobject object, jshort window, + jint start, jint end) +{ + union android_event event; + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_SET_COMPOSING_REGION; + event.ime.start = start; + event.ime.end = end; + event.ime.length = 0; + event.ime.position = 0; + event.ime.text = NULL; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (setSelection) (JNIEnv *env, jobject object, jshort window, + jint start, jint end) +{ + union android_event event; + + /* While IMEs want access to the entire selection, Emacs only + supports setting the point. */ + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_SET_POINT; + event.ime.start = 0; + event.ime.end = 0; + event.ime.length = 0; + event.ime.position = start; + event.ime.text = NULL; + event.ime.counter = ++edit_counter; +} + +/* Structure describing the context for `getSelection'. */ + +struct android_get_selection_context +{ + /* The window in question. */ + android_window window; + + /* The position of the window's point when it was last + redisplayed. */ + ptrdiff_t point; +}; + +/* Function run on the main thread by `getSelection'. + Place the character position of point in PT. */ + +static void +android_get_selection (void *data) +{ + struct android_get_selection_context *context; + struct frame *f; + struct window *w; + + context = data; + + /* Look up the associated frame and its selected window. */ + f = android_window_to_frame (NULL, context->window); + + if (!f) + context->point = -1; + else + { + w = XWINDOW (f->selected_window); + + /* Return W's point at the time of the last redisplay. This is + rather important to keep the input method consistent with the + contents of the display. */ + context->point = w->last_point; + } +} + +JNIEXPORT jint JNICALL +NATIVE_NAME (getSelection) (JNIEnv *env, jobject object, jshort window) +{ + struct android_get_selection_context context; + + context.window = window; + + android_sync_edit (); + if (android_run_in_emacs_thread (android_get_selection, + &context)) + return -1; + + if (context.point == -1) + return -1; + + return min (context.point, TYPE_MAXIMUM (jint)); +} + +JNIEXPORT void JNICALL +NATIVE_NAME (performEditorAction) (JNIEnv *env, jobject object, + jshort window, int action) +{ + union android_event event; + + /* Undocumented behavior: performEditorAction is apparently expected + to finish composing any text. */ + + NATIVE_NAME (finishComposingText) (env, object, window); + + event.xkey.type = ANDROID_KEY_PRESS; + event.xkey.serial = ++event_serial; + event.xkey.window = window; + event.xkey.time = 0; + event.xkey.state = 0; + event.xkey.keycode = 66; + event.xkey.unicode_char = 0; + + android_write_event (&event); +} + +struct android_get_extracted_text_context +{ + /* The parameters of the request. */ + int hint_max_chars; + + /* Token for the request. */ + int token; + + /* The returned text, or NULL. */ + char *text; + + /* The size of that text in characters and bytes. */ + ptrdiff_t length, bytes; + + /* Offsets into that text. */ + ptrdiff_t start, offset; + + /* The window. */ + android_window window; +}; + +/* Return the extracted text in the extracted text context specified + by DATA. */ + +static void +android_get_extracted_text (void *data) +{ + struct android_get_extracted_text_context *request; + struct frame *f; + + request = data; + + /* Find the frame associated with the window. */ + f = android_window_to_frame (NULL, request->window); + + if (!f) + return; + + /* Now get the extracted text. */ + request->text + = get_extracted_text (f, min (request->hint_max_chars, 600), + &request->start, &request->offset, + &request->length, &request->bytes); +} + +/* Structure describing the `ExtractedTextRequest' class. + Valid only on the UI thread. */ + +struct android_extracted_text_request_class +{ + bool initialized; + jfieldID hint_max_chars; + jfieldID token; +}; + +/* Structure describing the `ExtractedText' class. + Valid only on the UI thread. */ + +struct android_extracted_text_class +{ + jclass class; + jmethodID constructor; + jfieldID partial_start_offset; + jfieldID partial_end_offset; + jfieldID selection_start; + jfieldID selection_end; + jfieldID start_offset; + jfieldID text; +}; + +JNIEXPORT jobject JNICALL +NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object, + jshort window, jobject request, + jint flags) +{ + struct android_get_extracted_text_context context; + static struct android_extracted_text_request_class request_class; + static struct android_extracted_text_class text_class; + jstring string; + jclass class; + jobject object; + + /* TODO: report changes to extracted text. */ + + /* Initialize both classes if necessary. */ + + if (!request_class.initialized) + { + class + = (*env)->FindClass (env, ("android/view/inputmethod" + "/ExtractedTextRequest")); + assert (class); + + request_class.hint_max_chars + = (*env)->GetFieldID (env, class, "hintMaxChars", "I"); + assert (request_class.hint_max_chars); + + request_class.token + = (*env)->GetFieldID (env, class, "token", "I"); + assert (request_class.token); + + request_class.initialized = true; + } + + if (!text_class.class) + { + text_class.class + = (*env)->FindClass (env, ("android/view/inputmethod" + "/ExtractedText")); + assert (text_class.class); + + class + = text_class.class + = (*env)->NewGlobalRef (env, text_class.class); + assert (text_class.class); + + text_class.partial_start_offset + = (*env)->GetFieldID (env, class, "partialStartOffset", "I"); + text_class.partial_end_offset + = (*env)->GetFieldID (env, class, "partialEndOffset", "I"); + text_class.selection_start + = (*env)->GetFieldID (env, class, "selectionStart", "I"); + text_class.selection_end + = (*env)->GetFieldID (env, class, "selectionEnd", "I"); + text_class.start_offset + = (*env)->GetFieldID (env, class, "startOffset", "I"); + text_class.text + = (*env)->GetFieldID (env, class, "text", "Ljava/lang/CharSequence;"); + text_class.constructor + = (*env)->GetMethodID (env, class, "", "()V"); + } + + context.hint_max_chars + = (*env)->GetIntField (env, request, request_class.hint_max_chars); + context.token + = (*env)->GetIntField (env, request, request_class.token); + context.text = NULL; + context.window = window; + + android_sync_edit (); + if (android_run_in_emacs_thread (android_get_extracted_text, + &context)) + return NULL; + + if (!context.text) + return NULL; + + /* Encode the returned text. */ + string = android_text_to_string (env, context.text, context.length, + context.bytes); + free (context.text); + + if (!string) + return NULL; + + /* Create an ExtractedText object containing this information. */ + object = (*android_java_env)->NewObject (env, text_class.class, + text_class.constructor); + if (!object) + return NULL; + + (*env)->SetIntField (env, object, text_class.partial_start_offset, -1); + (*env)->SetIntField (env, object, text_class.partial_end_offset, -1); + (*env)->SetIntField (env, object, text_class.selection_start, + min (context.offset, TYPE_MAXIMUM (jint))); + (*env)->SetIntField (env, object, text_class.selection_end, + min (context.offset, TYPE_MAXIMUM (jint))); + (*env)->SetIntField (env, object, text_class.start_offset, + min (context.start, TYPE_MAXIMUM (jint))); + (*env)->SetObjectField (env, object, text_class.text, string); + return object; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#else +#pragma GCC diagnostic pop +#endif + + + +/* Tell the input method where the composing region and selection of + F's selected window is located. W should be F's selected window; + if it is NULL, then F->selected_window is used in its place. */ + +static void +android_update_selection (struct frame *f, struct window *w) +{ + ptrdiff_t start, end, point; + + if (MARKERP (f->conversion.compose_region_start)) + { + eassert (MARKERP (f->conversion.compose_region_end)); + + start = marker_position (f->conversion.compose_region_start); + end = marker_position (f->conversion.compose_region_end); + } + else + start = -1, end = -1; + + /* Now constrain START and END to the maximium size of a Java + integer. */ + start = min (start, TYPE_MAXIMUM (jint)); + end = min (end, TYPE_MAXIMUM (jint)); + + if (!w) + w = XWINDOW (f->selected_window); + + /* Figure out where the point is. */ + point = min (w->last_point, TYPE_MAXIMUM (jint)); + + /* Send the update. */ + android_update_ic (FRAME_ANDROID_WINDOW (f), point, point, + start, end); +} + +/* Notice that the input method connection to F should be reset as a + result of a change to its contents. */ + +static void +android_reset_conversion (struct frame *f) +{ + /* Reset the input method. + + Pick an appropriate ``input mode'' based on whether or not the + minibuffer window is selected; this controls whether or not + ``RET'' inserts a newline or sends an actual key event. */ + android_reset_ic (FRAME_ANDROID_WINDOW (f), + (EQ (f->selected_window, + f->minibuffer_window) + ? ANDROID_IC_MODE_ACTION + : ANDROID_IC_MODE_TEXT)); + + /* Move its selection to the specified position. */ + android_update_selection (f, NULL); +} + +/* Notice that point has moved in the F's selected window's selected + buffer. W is the window, and BUFFER is that buffer. */ + +static void +android_set_point (struct frame *f, struct window *w, + struct buffer *buffer) +{ + android_update_selection (f, w); +} + +/* Notice that the composition region on F's old selected window has + changed. */ + +static void +android_compose_region_changed (struct frame *f) +{ + android_update_selection (f, XWINDOW (f->old_selected_window)); +} + +/* Notice that the text conversion has completed. */ + +static void +android_notify_conversion (unsigned long counter) +{ + int sval; + + if (last_edit_counter < counter) + __atomic_store_n (&last_edit_counter, counter, + __ATOMIC_SEQ_CST); + + sem_getvalue (&edit_sem, &sval); + + if (sval <= 0) + sem_post (&edit_sem); +} + +/* Android text conversion interface. */ + +static struct textconv_interface text_conversion_interface = + { + android_reset_conversion, + android_set_point, + android_compose_region_changed, + android_notify_conversion, + }; + + + extern frame_parm_handler android_frame_parm_handlers[]; #endif /* !ANDROID_STUBIFY */ @@ -4327,6 +5357,11 @@ android_term_init (void) /* Set the baud rate to the same value it gets set to on X. */ baud_rate = 19200; + +#ifndef ANDROID_STUBIFY + sem_init (&edit_sem, false, 0); + register_textconv_interface (&text_conversion_interface); +#endif } diff --git a/src/coding.c b/src/coding.c index be5a0252ede..3ac4ada2939 100644 --- a/src/coding.c +++ b/src/coding.c @@ -11759,7 +11759,7 @@ syms_of_coding (void) DEFSYM (Qutf_8_unix, "utf-8-unix"); DEFSYM (Qutf_8_emacs, "utf-8-emacs"); -#if defined (WINDOWSNT) || defined (CYGWIN) +#if defined (WINDOWSNT) || defined (CYGWIN) || defined HAVE_ANDROID /* No, not utf-16-le: that one has a BOM. */ DEFSYM (Qutf_16le, "utf-16le"); #endif diff --git a/src/frame.c b/src/frame.c index a1d73d0799d..874e8c4cac1 100644 --- a/src/frame.c +++ b/src/frame.c @@ -997,6 +997,16 @@ make_frame (bool mini_p) f->select_mini_window_flag = false; /* This one should never be zero. */ f->change_stamp = 1; + +#ifdef HAVE_TEXT_CONVERSION + f->conversion.compose_region_start = Qnil; + f->conversion.compose_region_end = Qnil; + f->conversion.compose_region_overlay = Qnil; + f->conversion.batch_edit_count = 0; + f->conversion.batch_edit_flags = 0; + f->conversion.actions = NULL; +#endif + root_window = make_window (); rw = XWINDOW (root_window); if (mini_p) @@ -2264,6 +2274,13 @@ delete_frame (Lisp_Object frame, Lisp_Object force) f->terminal = 0; /* Now the frame is dead. */ unblock_input (); + /* Clear markers and overlays set by F on behalf of an input + method. */ +#ifdef HAVE_TEXT_CONVERSION + if (FRAME_WINDOW_P (f)) + reset_frame_state (f); +#endif + /* If needed, delete the terminal that this frame was on. (This must be done after the frame is killed.) */ terminal->reference_count--; diff --git a/src/frame.h b/src/frame.h index 4405c2df860..27484936ff1 100644 --- a/src/frame.h +++ b/src/frame.h @@ -76,6 +76,63 @@ enum ns_appearance_type #endif #endif /* HAVE_WINDOW_SYSTEM */ +#ifdef HAVE_TEXT_CONVERSION + +enum text_conversion_operation + { + TEXTCONV_START_BATCH_EDIT, + TEXTCONV_END_BATCH_EDIT, + TEXTCONV_COMMIT_TEXT, + TEXTCONV_FINISH_COMPOSING_TEXT, + TEXTCONV_SET_COMPOSING_TEXT, + TEXTCONV_SET_COMPOSING_REGION, + TEXTCONV_SET_POINT, + TEXTCONV_DELETE_SURROUNDING_TEXT, + }; + +/* Structure describing a single edit being performed by the input + method that should be executed in the context of + kbd_buffer_get_event. */ + +struct text_conversion_action +{ + /* The next text conversion action. */ + struct text_conversion_action *next; + + /* Any associated data. */ + Lisp_Object data; + + /* The operation being performed. */ + enum text_conversion_operation operation; + + /* Counter value. */ + unsigned long counter; +}; + +/* Structure describing the text conversion state associated with a + frame. */ + +struct text_conversion_state +{ + /* List of text conversion actions associated with this frame. */ + struct text_conversion_action *actions; + + /* Markers representing the composing region. */ + Lisp_Object compose_region_start, compose_region_end; + + /* Overlay representing the composing region. */ + Lisp_Object compose_region_overlay; + + /* The number of ongoing ``batch edits'' that are causing point + reporting to be delayed. */ + int batch_edit_count; + + /* Mask containing what must be updated after batch edits end. */ + int batch_edit_flags; +}; + +#endif + /* The structure representing a frame. */ struct frame @@ -664,6 +721,11 @@ struct frame enum ns_appearance_type ns_appearance; bool_bf ns_transparent_titlebar; #endif + +#ifdef HAVE_TEXT_CONVERSION + /* Text conversion state used by certain input methods. */ + struct text_conversion_state conversion; +#endif } GCALIGNED_STRUCT; /* Most code should use these functions to set Lisp fields in struct frame. */ diff --git a/src/keyboard.c b/src/keyboard.c index 11fa1e62c89..538fdffc199 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -44,6 +44,11 @@ along with GNU Emacs. If not, see . */ #include "atimer.h" #include "process.h" #include "menu.h" + +#ifdef HAVE_TEXT_CONVERSION +#include "textconv.h" +#endif + #include #ifdef HAVE_PTHREAD @@ -3020,6 +3025,10 @@ read_char (int commandflag, Lisp_Object map, { struct buffer *prev_buffer = current_buffer; last_input_event = c; + + /* All a `text-conversion' event does is prevent Emacs from + staying idle. It is not useful. */ + call4 (Qcommand_execute, tem, Qnil, Fvector (1, &last_input_event), Qt); if (CONSP (c) && !NILP (Fmemq (XCAR (c), Vwhile_no_input_ignore_events)) @@ -3582,6 +3591,11 @@ readable_events (int flags) return 1; #endif +#ifdef HAVE_TEXT_CONVERSION + if (detect_conversion_events ()) + return 1; +#endif + if (!(flags & READABLE_EVENTS_IGNORE_SQUEEZABLES) && some_mouse_moved ()) return 1; if (single_kboard) @@ -3914,6 +3928,11 @@ kbd_buffer_get_event (KBOARD **kbp, had_pending_selection_requests = false; #endif +#ifdef HAVE_TEXT_CONVERSION + bool had_pending_conversion_events; + + had_pending_conversion_events = false; +#endif #ifdef subprocesses if (kbd_on_hold_p () && kbd_buffer_nr_stored () < KBD_BUFFER_SIZE / 4) @@ -3977,6 +3996,13 @@ kbd_buffer_get_event (KBOARD **kbp, had_pending_selection_requests = true; break; } +#endif +#ifdef HAVE_TEXT_CONVERSION + if (detect_conversion_events ()) + { + had_pending_conversion_events = true; + break; + } #endif if (end_time) { @@ -4024,6 +4050,14 @@ kbd_buffer_get_event (KBOARD **kbp, x_handle_pending_selection_requests (); #endif +#ifdef HAVE_TEXT_CONVERSION + /* Handle pending ``text conversion'' requests from an input + method. */ + + if (had_pending_conversion_events) + handle_pending_conversion_events (); +#endif + if (CONSP (Vunread_command_events)) { Lisp_Object first; @@ -4380,12 +4414,25 @@ kbd_buffer_get_event (KBOARD **kbp, #ifdef HAVE_X_WINDOWS else if (had_pending_selection_requests) obj = Qnil; +#endif +#ifdef HAVE_TEXT_CONVERSION + /* This is an internal event used to prevent Emacs from becoming + idle immediately after a text conversion operation. */ + else if (had_pending_conversion_events) + obj = Qtext_conversion; #endif else /* We were promised by the above while loop that there was something for us to read! */ emacs_abort (); +#ifdef HAVE_TEXT_CONVERSION + /* While not implemented as keyboard commands, changes made by the + input method still mean that Emacs is no longer idle. */ + if (had_pending_conversion_events) + timer_stop_idle (); +#endif + input_pending = readable_events (0); Vlast_event_frame = internal_last_event_frame; @@ -12902,6 +12949,9 @@ See also `pre-command-hook'. */); DEFSYM (Qcoding, "coding"); DEFSYM (Qtouchscreen, "touchscreen"); +#ifdef HAVE_TEXT_CONVERSION + DEFSYM (Qtext_conversion, "text-conversion"); +#endif Fset (Qecho_area_clear_hook, Qnil); diff --git a/src/lisp.h b/src/lisp.h index 894b939b7b9..2dc51d32481 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -5230,7 +5230,10 @@ extern char *emacs_root_dir (void); #ifdef HAVE_TEXT_CONVERSION /* Defined in textconv.c. */ +extern void reset_frame_state (struct frame *); extern void report_selected_window_change (struct frame *); +extern void report_point_change (struct frame *, struct window *, + struct buffer *); #endif #ifdef HAVE_NATIVE_COMP diff --git a/src/process.c b/src/process.c index 9a8b0d7fd85..e7ccb2c604e 100644 --- a/src/process.c +++ b/src/process.c @@ -121,6 +121,7 @@ static struct rlimit nofile_limit; #ifdef HAVE_ANDROID #include "android.h" +#include "androidterm.h" #endif #ifdef HAVE_WINDOW_SYSTEM diff --git a/src/textconv.c b/src/textconv.c index d5db6d11717..b62ed7d1365 100644 --- a/src/textconv.c +++ b/src/textconv.c @@ -25,7 +25,10 @@ along with GNU Emacs. If not, see . */ ability to ``undo'' or ``edit'' previously composed text. This is most commonly seen in input methods for CJK laguages for X Windows, and is extensively used throughout Android by input methods for all - kinds of scripts. */ + kinds of scripts. + + In addition, these input methods may also need to make detailed + edits to the content of a buffer. That is also handled here. */ #include @@ -44,6 +47,15 @@ along with GNU Emacs. If not, see . */ static struct textconv_interface *text_interface; +/* Flags used to determine what must be sent after a batch edit + ends. */ + +enum textconv_batch_edit_flags + { + PENDING_POINT_CHANGE = 1, + PENDING_COMPOSE_CHANGE = 2, + }; + /* Copy the portion of the current buffer described by BEG, BEG_BYTE, @@ -94,14 +106,18 @@ copy_buffer (ptrdiff_t beg, ptrdiff_t beg_byte, Then, either delete that text from the buffer if QUERY->operation is TEXTCONV_SUBSTITUTION, or return 0. + If FLAGS & TEXTCONV_SKIP_CONVERSION_REGION, then first move PT past + the conversion region in the specified direction if it is inside. + Value is 0 if QUERY->operation was not TEXTCONV_SUBSTITUTION or if deleting the text was successful, and 1 otherwise. */ int -textconv_query (struct frame *f, struct textconv_callback_struct *query) +textconv_query (struct frame *f, struct textconv_callback_struct *query, + int flags) { specpdl_ref count; - ptrdiff_t pos, pos_byte, end, end_byte; + ptrdiff_t pos, pos_byte, end, end_byte, start; ptrdiff_t temp, temp1; char *buffer; @@ -113,14 +129,46 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query) /* Inhibit quitting. */ specbind (Qinhibit_quit, Qt); - /* Temporarily switch to F's selected window. */ - Fselect_window (f->selected_window, Qt); + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + Fselect_window ((WINDOW_LIVE_P (f->old_selected_window) + ? f->old_selected_window + : f->selected_window), Qt); /* Now find the appropriate text bounds for QUERY. First, move point QUERY->position steps forward or backwards. */ pos = PT; + /* Next, if POS lies within the conversion region and the caller + asked for it to be moved away, move it away from the conversion + region. */ + + if (flags & TEXTCONV_SKIP_CONVERSION_REGION + && MARKERP (f->conversion.compose_region_start)) + { + start = marker_position (f->conversion.compose_region_start); + end = marker_position (f->conversion.compose_region_end); + + 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 - 1); + break; + } + } + } + /* If pos is outside the accessible part of the buffer or if it overflows, move back to point or to the extremes of the accessible region. */ @@ -287,6 +335,828 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query) return 0; } +/* Reset F's text conversion state. Delete any overlays or + markers inside. */ + +void +reset_frame_state (struct frame *f) +{ + struct text_conversion_action *last, *next; + + /* Make the composition region markers point elsewhere. */ + + if (!NILP (f->conversion.compose_region_start)) + { + Fset_marker (f->conversion.compose_region_start, Qnil, Qnil); + Fset_marker (f->conversion.compose_region_end, Qnil, Qnil); + f->conversion.compose_region_start = Qnil; + f->conversion.compose_region_end = Qnil; + } + + /* Delete the composition region overlay. */ + + if (!NILP (f->conversion.compose_region_overlay)) + Fdelete_overlay (f->conversion.compose_region_overlay); + + /* Delete each text conversion action queued up. */ + + next = f->conversion.actions; + while (next) + { + last = next; + next = next->next; + + /* Say that the conversion is finished. */ + if (text_interface && text_interface->notify_conversion) + text_interface->notify_conversion (last->counter); + + xfree (last); + } + f->conversion.actions = NULL; + + /* Clear batch edit state. */ + f->conversion.batch_edit_count = 0; + f->conversion.batch_edit_flags = 0; +} + +/* Return whether or not there are pending edits from an input method + on any frame. */ + +bool +detect_conversion_events (void) +{ + Lisp_Object tail, frame; + + FOR_EACH_FRAME (tail, frame) + { + if (XFRAME (frame)->conversion.actions) + return true; + } + + return false; +} + +/* Restore the selected window WINDOW. */ + +static void +restore_selected_window (Lisp_Object window) +{ + /* FIXME: not sure what to do if WINDOW has been deleted. */ + Fselect_window (window, Qt); +} + +/* Commit the given text in the composing region. If there is no + composing region, then insert the text after F's selected window's + last point instead. Finally, remove the composing region. */ + +static void +really_commit_text (struct frame *f, EMACS_INT position, + Lisp_Object text) +{ + specpdl_ref count; + ptrdiff_t wanted, start, end; + + /* If F's old selected window is no longer live, fail. */ + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return; + + count = SPECPDL_INDEX (); + record_unwind_protect (restore_selected_window, + selected_window); + + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + Fselect_window (f->old_selected_window, Qt); + + /* Now detect whether or not there is a composing region. + If there is, then replace it with TEXT. Don't do that + otherwise. */ + + if (MARKERP (f->conversion.compose_region_start)) + { + /* Replace its contents. */ + start = marker_position (f->conversion.compose_region_start); + end = marker_position (f->conversion.compose_region_end); + safe_del_range (start, end); + Finsert (1, &text); + + /* Move to a the position specified in POSITION. */ + + if (position < 0) + { + wanted + = marker_position (f->conversion.compose_region_start); + + if (INT_SUBTRACT_WRAPV (wanted, position, &wanted) + || wanted < BEGV) + wanted = BEGV; + + if (wanted > ZV) + wanted = ZV; + + set_point (wanted); + } + else + { + wanted + = marker_position (f->conversion.compose_region_end); + + if (INT_ADD_WRAPV (wanted, position - 1, &wanted) + || wanted > ZV) + wanted = ZV; + + if (wanted < BEGV) + wanted = BEGV; + + set_point (wanted); + } + + /* Make the composition region markers point elsewhere. */ + + if (!NILP (f->conversion.compose_region_start)) + { + Fset_marker (f->conversion.compose_region_start, Qnil, Qnil); + Fset_marker (f->conversion.compose_region_end, Qnil, Qnil); + f->conversion.compose_region_start = Qnil; + f->conversion.compose_region_end = Qnil; + } + + /* Delete the composition region overlay. */ + + if (!NILP (f->conversion.compose_region_overlay)) + Fdelete_overlay (f->conversion.compose_region_overlay); + } + else + { + /* Otherwise, move the text and point to an appropriate + location. */ + wanted = PT; + Finsert (1, &text); + + if (position < 0) + { + if (INT_SUBTRACT_WRAPV (wanted, position, &wanted) + || wanted < BEGV) + wanted = BEGV; + + if (wanted > ZV) + wanted = ZV; + + set_point (wanted); + } + else + { + wanted = PT; + + if (INT_ADD_WRAPV (wanted, position - 1, &wanted) + || wanted > ZV) + wanted = ZV; + + if (wanted < BEGV) + wanted = BEGV; + + set_point (wanted); + } + } + + unbind_to (count, Qnil); +} + +/* Remove the composition region on the frame F, while leaving its + contents intact. */ + +static void +really_finish_composing_text (struct frame *f) +{ + if (!NILP (f->conversion.compose_region_start)) + { + Fset_marker (f->conversion.compose_region_start, Qnil, Qnil); + Fset_marker (f->conversion.compose_region_end, Qnil, Qnil); + f->conversion.compose_region_start = Qnil; + f->conversion.compose_region_end = Qnil; + } + + /* Delete the composition region overlay. */ + + if (!NILP (f->conversion.compose_region_overlay)) + Fdelete_overlay (f->conversion.compose_region_overlay); +} + +/* Set the composing text on F to TEXT. Then, move point to an + appropriate position relative to POSITION, and call + `compose_region_changed' in the text conversion interface should + point not have been changed relative to F's old selected window's + last point. */ + +static void +really_set_composing_text (struct frame *f, ptrdiff_t position, + Lisp_Object text) +{ + specpdl_ref count; + ptrdiff_t start, wanted, end; + struct window *w; + + /* If F's old selected window is no longer live, fail. */ + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return; + + count = SPECPDL_INDEX (); + record_unwind_protect (restore_selected_window, + selected_window); + + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + w = XWINDOW (f->old_selected_window); + Fselect_window (f->old_selected_window, Qt); + + /* Now set up the composition region if necessary. */ + + if (!MARKERP (f->conversion.compose_region_start)) + { + f->conversion.compose_region_start = Fmake_marker (); + f->conversion.compose_region_end = Fmake_marker (); + Fset_marker (f->conversion.compose_region_start, + Fpoint (), Qnil); + Fset_marker (f->conversion.compose_region_end, + Fpoint (), Qnil); + Fset_marker_insertion_type (f->conversion.compose_region_end, + Qt); + } + else + { + /* Delete the text between the start of the composition region + and its end. TODO: avoid this widening. */ + record_unwind_protect (save_restriction_restore, + save_restriction_save ()); + Fwiden (); + start = marker_position (f->conversion.compose_region_start); + end = marker_position (f->conversion.compose_region_end); + safe_del_range (start, end); + set_point (start); + } + + /* Insert the new text. */ + Finsert (1, &text); + + /* Now move point to an appropriate location. */ + if (position < 0) + { + wanted = start; + + if (INT_SUBTRACT_WRAPV (wanted, position, &wanted) + || wanted < BEGV) + wanted = BEGV; + + if (wanted > ZV) + wanted = ZV; + } + else + { + end = marker_position (f->conversion.compose_region_end); + wanted = end; + + /* end should be PT after the edit. */ + eassert (end == PT); + + if (INT_ADD_WRAPV (wanted, position - 1, &wanted) + || wanted > ZV) + wanted = ZV; + + if (wanted < BEGV) + wanted = BEGV; + } + + set_point (wanted); + + /* If PT hasn't changed, the conversion region definitely has. + Otherwise, redisplay will update the input method instead. */ + + if (PT == w->last_point + && text_interface + && text_interface->compose_region_changed) + { + if (f->conversion.batch_edit_count > 0) + f->conversion.batch_edit_flags |= PENDING_COMPOSE_CHANGE; + else + text_interface->compose_region_changed (f); + } + + unbind_to (count, Qnil); +} + +/* Set the composing region to START by END. Make it that it is not + already set. */ + +static void +really_set_composing_region (struct frame *f, ptrdiff_t start, + ptrdiff_t end) +{ + specpdl_ref count; + + /* If F's old selected window is no longer live, fail. */ + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return; + + /* If MAX (0, start) == end, then this should behave the same as + really_finish_composing_text. */ + + if (max (0, start) == max (0, end)) + { + really_finish_composing_text (f); + return; + } + + count = SPECPDL_INDEX (); + record_unwind_protect (restore_selected_window, + selected_window); + + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + Fselect_window (f->old_selected_window, Qt); + + /* Now set up the composition region if necessary. */ + + if (!MARKERP (f->conversion.compose_region_start)) + { + f->conversion.compose_region_start = Fmake_marker (); + f->conversion.compose_region_end = Fmake_marker (); + Fset_marker_insertion_type (f->conversion.compose_region_end, + Qt); + } + + Fset_marker (f->conversion.compose_region_start, + make_fixnum (start), Qnil); + Fset_marker (f->conversion.compose_region_end, + make_fixnum (end), Qnil); + + unbind_to (count, Qnil); +} + +/* Delete LEFT and RIGHT chars around point. */ + +static void +really_delete_surrounding_text (struct frame *f, ptrdiff_t left, + ptrdiff_t right) +{ + specpdl_ref count; + ptrdiff_t start, end; + + /* If F's old selected window is no longer live, fail. */ + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return; + + count = SPECPDL_INDEX (); + record_unwind_protect (restore_selected_window, + selected_window); + + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + Fselect_window (f->old_selected_window, Qt); + + start = max (BEGV, PT - left); + end = min (ZV, PT + right); + + safe_del_range (start, end); + unbind_to (count, Qnil); +} + +/* Set point in F to POSITION. + + If it has not changed, signal an update through the text input + interface, which is necessary for the IME to acknowledge that the + change has completed. */ + +static void +really_set_point (struct frame *f, ptrdiff_t point) +{ + specpdl_ref count; + + /* If F's old selected window is no longer live, fail. */ + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return; + + count = SPECPDL_INDEX (); + record_unwind_protect (restore_selected_window, + selected_window); + + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + Fselect_window (f->old_selected_window, Qt); + + if (point == PT) + { + if (f->conversion.batch_edit_count > 0) + f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE; + else + text_interface->point_changed (f, + XWINDOW (f->old_selected_window), + current_buffer); + } + else + /* Set the point. */ + Fgoto_char (make_fixnum (point)); + + unbind_to (count, Qnil); +} + +/* Complete the edit specified by the counter value inside *TOKEN. */ + +static void +complete_edit (void *token) +{ + if (text_interface && text_interface->notify_conversion) + text_interface->notify_conversion (*(unsigned long *) token); +} + +/* Process and free the text conversion ACTION. F must be the frame + on which ACTION will be performed. */ + +static void +handle_pending_conversion_events_1 (struct frame *f, + struct text_conversion_action *action) +{ + Lisp_Object data; + enum text_conversion_operation operation; + struct buffer *buffer; + struct window *w; + specpdl_ref count; + unsigned long token; + + /* Next, process this action and free it. */ + + data = action->data; + operation = action->operation; + token = action->counter; + xfree (action); + + /* Make sure completion is signalled. */ + count = SPECPDL_INDEX (); + record_unwind_protect_ptr (complete_edit, &token); + + switch (operation) + { + case TEXTCONV_START_BATCH_EDIT: + f->conversion.batch_edit_count++; + break; + + case TEXTCONV_END_BATCH_EDIT: + if (f->conversion.batch_edit_count > 0) + f->conversion.batch_edit_count--; + + if (!WINDOW_LIVE_P (f->old_selected_window)) + break; + + if (f->conversion.batch_edit_flags & PENDING_POINT_CHANGE) + { + w = XWINDOW (f->old_selected_window); + buffer = XBUFFER (WINDOW_BUFFER (w)); + + text_interface->point_changed (f, w, buffer); + } + + if (f->conversion.batch_edit_flags & PENDING_COMPOSE_CHANGE) + text_interface->compose_region_changed (f); + + f->conversion.batch_edit_flags = 0; + break; + + case TEXTCONV_COMMIT_TEXT: + really_commit_text (f, XFIXNUM (XCAR (data)), XCDR (data)); + break; + + case TEXTCONV_FINISH_COMPOSING_TEXT: + really_finish_composing_text (f); + break; + + case TEXTCONV_SET_COMPOSING_TEXT: + really_set_composing_text (f, XFIXNUM (XCAR (data)), + XCDR (data)); + break; + + case TEXTCONV_SET_COMPOSING_REGION: + really_set_composing_region (f, XFIXNUM (XCAR (data)), + XFIXNUM (XCDR (data))); + break; + + case TEXTCONV_SET_POINT: + really_set_point (f, XFIXNUM (data)); + break; + + case TEXTCONV_DELETE_SURROUNDING_TEXT: + really_delete_surrounding_text (f, XFIXNUM (XCAR (data)), + XFIXNUM (XCDR (data))); + break; + } + + unbind_to (count, Qnil); +} + +/* Process any outstanding text conversion events. + This may run Lisp or signal. */ + +void +handle_pending_conversion_events (void) +{ + struct frame *f; + Lisp_Object tail, frame; + struct text_conversion_action *action, *next; + bool handled; + + handled = false; + + FOR_EACH_FRAME (tail, frame) + { + f = XFRAME (frame); + + /* Test if F has any outstanding conversion events. Then + process them in bottom to up order. */ + for (action = f->conversion.actions; action; action = next) + { + /* Redisplay in between if there is more than one + action. */ + + if (handled) + redisplay (); + + /* Unlink this action. */ + next = action->next; + f->conversion.actions = next; + + /* Handle and free the action. */ + handle_pending_conversion_events_1 (f, action); + handled = true; + } + } +} + +/* Start a ``batch edit'' in F. During a batch edit, point_changed + will not be called until the batch edit ends. + + Process the actual operation in the event loop in keyboard.c; then, + call `notify_conversion' in the text conversion interface with + COUNTER. */ + +void +start_batch_edit (struct frame *f, unsigned long counter) +{ + struct text_conversion_action *action, **last; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_START_BATCH_EDIT; + action->data = Qnil; + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + +/* End a ``batch edit''. It is ok to call this function even if a + batch edit has not yet started, in which case it does nothing. + + COUNTER means the same as in `start_batch_edit'. */ + +void +end_batch_edit (struct frame *f, unsigned long counter) +{ + struct text_conversion_action *action, **last; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_END_BATCH_EDIT; + action->data = Qnil; + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + +/* Insert the specified STRING into F's current buffer's composition + region, and set point to POSITION relative to STRING. + + COUNTER means the same as in `start_batch_edit'. */ + +void +commit_text (struct frame *f, Lisp_Object string, + ptrdiff_t position, unsigned long counter) +{ + struct text_conversion_action *action, **last; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_COMMIT_TEXT; + action->data = Fcons (make_fixnum (position), string); + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + +/* Remove the composition region and its overlay from F's current + buffer. Leave the text being composed intact. + + COUNTER means the same as in `start_batch_edit'. */ + +void +finish_composing_text (struct frame *f, unsigned long counter) +{ + struct text_conversion_action *action, **last; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_FINISH_COMPOSING_TEXT; + action->data = Qnil; + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + +/* Insert the given STRING and make it the currently active + composition. + + If there is currently no composing region, then the new value of + point is used as the composing region. + + Then, the composing region is replaced with the text in the + specified string. + + Finally, move point to new_point, which is relative to either the + start or the end of OBJECT depending on whether or not it is less + than zero. + + COUNTER means the same as in `start_batch_edit'. */ + +void +set_composing_text (struct frame *f, Lisp_Object object, + ptrdiff_t new_point, unsigned long counter) +{ + struct text_conversion_action *action, **last; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_SET_COMPOSING_TEXT; + action->data = Fcons (make_fixnum (new_point), + object); + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + +/* Make the region between START and END the currently active + ``composing region''. + + The ``composing region'' is a region of text in the buffer that is + about to undergo editing by the input method. */ + +void +set_composing_region (struct frame *f, ptrdiff_t start, + ptrdiff_t end, unsigned long counter) +{ + struct text_conversion_action *action, **last; + + if (start > MOST_POSITIVE_FIXNUM) + start = MOST_POSITIVE_FIXNUM; + + if (end > MOST_POSITIVE_FIXNUM) + end = MOST_POSITIVE_FIXNUM; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_SET_COMPOSING_REGION; + action->data = Fcons (make_fixnum (start), + make_fixnum (end)); + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + +/* Move point in F's selected buffer to POINT. + + COUNTER means the same as in `start_batch_edit'. */ + +void +textconv_set_point (struct frame *f, ptrdiff_t point, + unsigned long counter) +{ + struct text_conversion_action *action, **last; + + if (point > MOST_POSITIVE_FIXNUM) + point = MOST_POSITIVE_FIXNUM; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_SET_POINT; + action->data = make_fixnum (point); + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + +/* Delete LEFT and RIGHT characters around point in F's old selected + window. */ + +void +delete_surrounding_text (struct frame *f, ptrdiff_t left, + ptrdiff_t right, unsigned long counter) +{ + struct text_conversion_action *action, **last; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_DELETE_SURROUNDING_TEXT; + action->data = Fcons (make_fixnum (left), + make_fixnum (right)); + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + +/* Return N characters of text around point in F's old selected + window. + + Set *N to the actual number of characters returned, *START_RETURN + to the position of the first character returned, *OFFSET to the + offset of point within that text, *LENGTH to the actual number of + characters returned, and *BYTES to the actual number of bytes + returned. + + Value is NULL upon failure, and a malloced string upon success. */ + +char * +get_extracted_text (struct frame *f, ptrdiff_t n, + ptrdiff_t *start_return, + ptrdiff_t *offset, ptrdiff_t *length, + ptrdiff_t *bytes) +{ + specpdl_ref count; + ptrdiff_t start, end, start_byte, end_byte; + char *buffer; + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return NULL; + + /* Save the excursion, as there will be extensive changes to the + selected window. */ + count = SPECPDL_INDEX (); + record_unwind_protect_excursion (); + + /* Inhibit quitting. */ + specbind (Qinhibit_quit, Qt); + + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + Fselect_window (f->old_selected_window, Qt); + + /* Figure out the bounds of the text to return. */ + start = PT - n / 2; + end = PT + n - n / 2; + start = max (start, BEGV); + end = min (end, ZV); + buffer = NULL; + + /* Detect overflow. */ + + if (!(start <= PT <= end)) + goto finish; + + /* Convert the character positions to byte positions. */ + start_byte = CHAR_TO_BYTE (start); + end_byte = CHAR_TO_BYTE (end); + + /* Extract the text from the buffer. */ + buffer = xmalloc (end_byte - start_byte); + copy_buffer (start, start_byte, end, end_byte, + buffer); + + /* Return the offsets. */ + *start_return = start; + *offset = PT - start; + *length = end - start; + *bytes = end_byte - start_byte; + + finish: + unbind_to (count, Qnil); + return buffer; +} + /* Window system interface. These are called from the rest of @@ -298,16 +1168,40 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query) void report_selected_window_change (struct frame *f) { + reset_frame_state (f); + if (!text_interface) return; text_interface->reset (f); } +/* Notice that the point in F's selected window's current buffer has + changed. + + F is the frame whose selected window was changed, W is the window + in question, and BUFFER is that window's current buffer. + + Tell the text conversion interface about the change; it will likely + pass the information on to the system input method. */ + +void +report_point_change (struct frame *f, struct window *window, + struct buffer *buffer) +{ + if (!text_interface || !text_interface->point_changed) + return; + + if (f->conversion.batch_edit_count > 0) + f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE; + else + text_interface->point_changed (f, window, buffer); +} + /* Register INTERFACE as the text conversion interface. */ void -register_texconv_interface (struct textconv_interface *interface) +register_textconv_interface (struct textconv_interface *interface) { text_interface = interface; } diff --git a/src/textconv.h b/src/textconv.h index f6e7eb7925f..ce003847637 100644 --- a/src/textconv.h +++ b/src/textconv.h @@ -34,6 +34,20 @@ struct textconv_interface happen if the window is deleted or switches buffers, or an unexpected buffer change occurs.) */ void (*reset) (struct frame *); + + /* Notice that point has moved in the specified frame's selected + window's selected buffer. The second argument is the window + whose point changed, and the third argument is the buffer. */ + void (*point_changed) (struct frame *, struct window *, + struct buffer *); + + /* Notice that the preconversion region has changed without point + being moved. */ + void (*compose_region_changed) (struct frame *); + + /* Notice that an asynch conversion identified by COUNTER has + completed. */ + void (*notify_conversion) (unsigned long); }; @@ -103,7 +117,27 @@ struct textconv_callback_struct struct textconv_conversion_text text; }; -extern int textconv_query (struct frame *, struct textconv_callback_struct *); -extern void register_texconv_interface (struct textconv_interface *); +#define TEXTCONV_SKIP_CONVERSION_REGION (1 << 0) + +extern int textconv_query (struct frame *, struct textconv_callback_struct *, + int); +extern bool detect_conversion_events (void); +extern void handle_pending_conversion_events (void); +extern void start_batch_edit (struct frame *, unsigned long); +extern void end_batch_edit (struct frame *, unsigned long); +extern void commit_text (struct frame *, Lisp_Object, ptrdiff_t, + unsigned long); +extern void finish_composing_text (struct frame *, unsigned long); +extern void set_composing_text (struct frame *, Lisp_Object, + ptrdiff_t, unsigned long); +extern void set_composing_region (struct frame *, ptrdiff_t, ptrdiff_t, + unsigned long); +extern void textconv_set_point (struct frame *, ptrdiff_t, unsigned long); +extern void delete_surrounding_text (struct frame *, ptrdiff_t, + ptrdiff_t, unsigned long); +extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *, + ptrdiff_t *, ptrdiff_t *, ptrdiff_t *); + +extern void register_textconv_interface (struct textconv_interface *); #endif /* _TEXTCONV_H_ */ diff --git a/src/xdisp.c b/src/xdisp.c index 8bbb80b1c8c..4b91fcf31ca 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -17267,6 +17267,9 @@ static void mark_window_display_accurate_1 (struct window *w, bool accurate_p) { struct buffer *b = XBUFFER (w->contents); +#ifdef HAVE_TEXT_CONVERSION + ptrdiff_t prev_point; +#endif w->last_modified = accurate_p ? BUF_MODIFF (b) : 0; w->last_overlay_modified = accurate_p ? BUF_OVERLAY_MODIFF (b) : 0; @@ -17296,11 +17299,36 @@ mark_window_display_accurate_1 (struct window *w, bool accurate_p) w->last_cursor_vpos = w->cursor.vpos; w->last_cursor_off_p = w->cursor_off_p; +#ifdef HAVE_TEXT_CONVERSION + prev_point = w->last_point; +#endif + if (w == XWINDOW (selected_window)) w->last_point = BUF_PT (b); else w->last_point = marker_position (w->pointm); +#ifdef HAVE_TEXT_CONVERSION + /* Point motion is only propagated to the input method for use + in text conversion during a redisplay. While this can lead + to inconsistencies when point has moved but the change has + not yet been displayed, it leads to better results most of + the time, as point often changes within calls to + `save-excursion', and the only way to detect such calls is to + observe that the next redisplay never ends with those changes + applied. + + Changes to buffer text are immediately propagated to the + input method, and the position of point is also updated + during such a change, so the consequences are not that + severe. */ + + if (prev_point != w->last_point + && FRAME_WINDOW_P (WINDOW_XFRAME (w)) + && w == XWINDOW (WINDOW_XFRAME (w)->selected_window)) + report_point_change (WINDOW_XFRAME (w), w, b); +#endif + w->window_end_valid = true; w->update_mode_line = false; w->preserve_vscroll_p = false; diff --git a/src/xfns.c b/src/xfns.c index 9e004f6a678..0e4a25de04a 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -3861,7 +3861,7 @@ xic_string_conversion_callback (XIC ic, XPointer client_data, request.operation = TEXTCONV_RETRIEVAL; /* Now perform the string conversion. */ - rc = textconv_query (f, &request); + rc = textconv_query (f, &request, 0); if (rc) { diff --git a/src/xterm.c b/src/xterm.c index ccaacec2c57..49b88de2759 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -31615,7 +31615,7 @@ init_xterm (void) #endif #ifdef HAVE_X_I18N - register_texconv_interface (&text_conversion_interface); + register_textconv_interface (&text_conversion_interface); #endif } -- cgit v1.3 From 2dcce30290dc7782e9de3b4adf59f38b42408c98 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 16 Feb 2023 23:57:01 +0800 Subject: Update Android port * doc/emacs/android.texi (Android Fonts): * doc/emacs/input.texi (On-Screen Keyboards): * doc/lispref/commands.texi (Misc Events): Update documentation. * java/org/gnu/emacs/EmacsInputConnection.java (setSelection): New function. * java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView) (reconfigureFrontBuffer): Make bitmap references weak references. * java/org/gnu/emacs/EmacsView.java (handleDirtyBitmap): Don't clear surfaceView bitmap. * lisp/comint.el (comint-mode): * lisp/international/fontset.el (script-representative-chars) (setup-default-fontset): Improve detection of CJK fonts. * lisp/isearch.el (set-text-conversion-style): New variable. (isearch-mode, isearch-done): Save and restore the text conversion style. * lisp/minibuffer.el (minibuffer-mode): Set an appropriate text conversion style. * lisp/simple.el (analyze-text-conversion): Run post-self-insert-hook properly. * lisp/subr.el (read-char-from-minibuffer): Disable text conversion when reading character. * src/androidterm.c (show_back_buffer): Don't check that F is not garbaged. (android_update_selection, android_reset_conversion): Use the ephemeral last point and handle text conversion being disabled. * src/buffer.c (syms_of_buffer): Convert old style DEFVAR. * src/keyboard.c (kbd_buffer_get_event): Handle text conversion first. * src/lisp.h: Update prototypes. * src/lread.c (read_filtered_event): Temporarily disable text conversion. * src/sfnt.c (sfnt_decompose_glyph_1, sfnt_decompose_glyph_2): New functions. (sfnt_decompose_glyph, sfnt_decompose_instructed_outline): Refactor contour decomposition to those two functions. (main): Update tests. * src/sfntfont-android.c (system_font_directories): Add empty field. (Fandroid_enumerate_fonts, init_sfntfont_android): Enumerate fonts in a user fonts directory. * src/sfntfont.c (struct sfnt_font_desc): New field `num_glyphs'. (sfnt_enum_font_1): Set num_glyphs and avoid duplicate fonts. (sfntfont_glyph_valid): New function. (sfntfont_lookup_char, sfntfont_list_1): Make sure glyphs found are valid. * src/textconv.c (sync_overlay, really_commit_text) (really_set_composing_text, really_set_composing_region) (really_delete_surrounding_text, really_set_point_and_mark) (handle_pending_conversion_events_1) (handle_pending_conversion_events, conversion_disabled_p) (disable_text_conversion, resume_text_conversion) (Fset_text_conversion_style, syms_of_textconv): Update to respect new options. * src/textconv.h: * src/window.h (GCALIGNED_STRUCT): New field `ephemeral_last_point'. * src/xdisp.c (mark_window_display_accurate_1): Set it. --- doc/emacs/android.texi | 8 +- doc/emacs/input.texi | 7 + doc/lispref/commands.texi | 11 +- java/org/gnu/emacs/EmacsInputConnection.java | 14 +- java/org/gnu/emacs/EmacsSurfaceView.java | 17 +- java/org/gnu/emacs/EmacsView.java | 8 +- lisp/comint.el | 3 + lisp/international/fontset.el | 14 +- lisp/isearch.el | 17 + lisp/minibuffer.el | 5 +- lisp/simple.el | 11 +- lisp/subr.el | 25 +- src/androidterm.c | 25 +- src/buffer.c | 22 +- src/keyboard.c | 39 +- src/lisp.h | 2 + src/lread.c | 26 +- src/sfnt.c | 625 ++++++++++++++------------- src/sfntfont-android.c | 13 +- src/sfntfont.c | 87 +++- src/textconv.c | 234 +++++++++- src/textconv.h | 1 + src/window.h | 15 + src/xdisp.c | 3 + 24 files changed, 804 insertions(+), 428 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 308bd70c59c..35f0ba55cf4 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -446,10 +446,10 @@ application via cut-and-paste. 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. +directory @file{/system/fonts}, and the @file{fonts} directory inside +the Emacs home directory. Emacs assumes there will always be a font +named ``Droid Sans Mono'', and then defaults to using this font. +These fonts are then displayed by the @code{sfnt-android} font driver. When running on Android, Emacs currently lacks support for OpenType fonts. This means that only a subset of the fonts installed on the diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi index 154b7025ff4..87ed1f4f8a9 100644 --- a/doc/emacs/input.texi +++ b/doc/emacs/input.texi @@ -129,3 +129,10 @@ derivatives of @code{text-mode} and @code{prog-mode}. a request to perform the conversion from the input method. After the conversion completes, a @code{text-conversion} event is sent. @xref{Misc Events,,, elisp, the Emacs Reference Manual}. + +@vindex text-conversion-face + If the input method needs to work on a region of the buffer, then +the region becomes known as the ``composing region'' (or +``preconversion region''.) The variable @code{text-conversion-face} +describes whether or not to display the composing region in a specific +face. diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index 5fb2217a03b..898cc9a944b 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2231,7 +2231,7 @@ buffer-local variable @code{text-conversion-style}, which determines how an input method that wishes to make edits to buffer contents will behave. -This variable can have one three values: +This variable can have one of three values: @table @code @item nil @@ -2245,11 +2245,16 @@ be sent wherever the input method wanted to insert a new line. @item t This, or any other value, means that the input method will be enabled and make edits terminated by @code{text-conversion} events. -@end itemize +@end table +@findex disable-text-conversion Changes to the value of this variable will only take effect upon the next redisplay after the buffer becomes the selected buffer -of a frame. +of a frame. If you need to disable text conversion in a way +that takes immediate effect, call the function +@code{set-text-conversion-style} instead. This can potentially +lock up the input method for a significant amount of time, so do +not do this lightly! @cindex @code{delete-frame} event @item (delete-frame (@var{frame})) diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 5eb56d5aa71..e2a15894695 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -173,7 +173,8 @@ public class EmacsInputConnection extends BaseInputConnection setComposingText (CharSequence text, int newCursorPosition) { if (EmacsService.DEBUG_IC) - Log.d (TAG, "setComposingText: " + newCursorPosition); + Log.d (TAG, ("setComposingText: " + + text + " ## " + newCursorPosition)); EmacsNative.setComposingText (windowHandle, text.toString (), newCursorPosition); @@ -213,6 +214,17 @@ public class EmacsInputConnection extends BaseInputConnection flags); } + @Override + public boolean + setSelection (int start, int end) + { + if (EmacsService.DEBUG_IC) + Log.d (TAG, "setSelection: " + start + " " + end); + + EmacsNative.setSelection (windowHandle, start, end); + return true; + } + /* Override functions which are not implemented. */ diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index 2d80be0881a..62e927094e4 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -28,6 +28,8 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.Paint; +import java.lang.ref.WeakReference; + /* 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 @@ -39,7 +41,7 @@ public class EmacsSurfaceView extends View private EmacsView view; private Bitmap frontBuffer; private Canvas bitmapCanvas; - private Bitmap bitmap; + private WeakReference bitmap; private Paint bitmapPaint; public @@ -49,10 +51,11 @@ public class EmacsSurfaceView extends View this.view = view; this.bitmapPaint = new Paint (); + this.bitmap = new WeakReference (null); } private void - copyToFrontBuffer (Rect damageRect) + copyToFrontBuffer (Bitmap bitmap, Rect damageRect) { if (damageRect != null) bitmapCanvas.drawBitmap (bitmap, damageRect, damageRect, @@ -73,7 +76,7 @@ public class EmacsSurfaceView extends View bitmapCanvas = null; } - this.bitmap = bitmap; + this.bitmap = new WeakReference (bitmap); /* Next, create the new front buffer if necessary. */ @@ -92,20 +95,20 @@ public class EmacsSurfaceView extends View bitmapCanvas = new Canvas (frontBuffer); /* And copy over the bitmap contents. */ - copyToFrontBuffer (null); + copyToFrontBuffer (bitmap, null); } else if (bitmap != null) /* Just copy over the bitmap contents. */ - copyToFrontBuffer (null); + copyToFrontBuffer (bitmap, null); } public synchronized void setBitmap (Bitmap bitmap, Rect damageRect) { - if (bitmap != this.bitmap) + if (bitmap != this.bitmap.get ()) reconfigureFrontBuffer (bitmap); else if (bitmap != null) - copyToFrontBuffer (damageRect); + copyToFrontBuffer (bitmap, damageRect); if (bitmap != null) { diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 52da6d41f7d..5ea8b0dcc0e 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -186,13 +186,7 @@ public class EmacsView extends ViewGroup /* Explicitly free the old bitmap's memory. */ if (oldBitmap != null) - { - oldBitmap.recycle (); - - /* Make sure to set the view's bitmap to the new bitmap, or - ugly flicker can result. */ - surfaceView.setBitmap (bitmap, null); - } + oldBitmap.recycle (); /* Some Android versions still don't free the bitmap until the next GC. */ diff --git a/lisp/comint.el b/lisp/comint.el index 9d2c245247f..e1786e7e670 100644 --- a/lisp/comint.el +++ b/lisp/comint.el @@ -694,6 +694,9 @@ Entry to this mode runs the hooks on `comint-mode-hook'." (setq-local comint-last-input-start (point-min-marker)) (setq-local comint-last-input-end (point-min-marker)) (setq-local comint-last-output-start (make-marker)) + ;; It is ok to let the input method edit prompt text, but RET must + ;; be processed by Emacs. + (setq text-conversion-style 'action) (make-local-variable 'comint-last-prompt) (make-local-variable 'comint-prompt-regexp) ; Don't set; default (make-local-variable 'comint-input-ring-size) ; ...to global val. diff --git a/lisp/international/fontset.el b/lisp/international/fontset.el index eb1c7f53d36..8fa67d99477 100644 --- a/lisp/international/fontset.el +++ b/lisp/international/fontset.el @@ -200,7 +200,7 @@ (symbol . [#x201C #x2200 #x2500]) (braille #x2800) (ideographic-description #x2FF0) - (cjk-misc #x300E) + (cjk-misc #x300E #xff0c) (kana #x304B) (bopomofo #x3105) (kanbun #x319D) @@ -683,7 +683,11 @@ (nil . "JISX0213.2000-2") (nil . "JISX0213.2004-1") ,(font-spec :registry "iso10646-1" :lang 'ja) - ,(font-spec :registry "iso10646-1" :lang 'zh)) + ,(font-spec :registry "iso10646-1" :lang 'zh) + ;; This is required, as otherwise many TrueType fonts with + ;; CJK characters but no corresponding ``design language'' + ;; declaration can't be found. + ,(font-spec :registry "iso10646-1" :script 'han)) (cjk-misc (nil . "GB2312.1980-0") (nil . "JISX0208*") @@ -702,7 +706,11 @@ (nil . "JISX0213.2000-1") (nil . "JISX0213.2000-2") ,(font-spec :registry "iso10646-1" :lang 'ja) - ,(font-spec :registry "iso10646-1" :lang 'zh)) + ,(font-spec :registry "iso10646-1" :lang 'zh) + ;; This is required, as otherwise many TrueType fonts + ;; with CJK characters but no corresponding ``design + ;; language'' declaration can't be found. + ,(font-spec :registry "iso10646-1" :script 'cjk-misc)) (hangul (nil . "KSC5601.1987-0") ,(font-spec :registry "iso10646-1" :lang 'ko)) diff --git a/lisp/isearch.el b/lisp/isearch.el index 80c7a3b3d3f..a17b22fd627 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -244,6 +244,10 @@ If you use `add-function' to modify this variable, you can use the `isearch-message-prefix' advice property to specify the prefix string displayed in the search message.") +(defvar isearch-text-conversion-style nil + "Value of `text-conversion-style' before Isearch mode +was enabled in this buffer.") + ;; Search ring. (defvar search-ring nil @@ -1221,6 +1225,8 @@ active region is added to the search string." ;; isearch-forward-regexp isearch-backward-regexp) ;; "List of commands for which isearch-mode does not recursive-edit.") +(declare-function set-text-conversion-style "textconv.c") + (defun isearch-mode (forward &optional regexp op-fun recursive-edit regexp-function) "Start Isearch minor mode. It is called by the function `isearch-forward' and other related functions. @@ -1342,6 +1348,13 @@ used to set the value of `isearch-regexp-function'." 'keyboard))) (frame-toggle-on-screen-keyboard (selected-frame) nil)) + ;; Disable text conversion so that isearch can behave correctly. + + (when (fboundp 'set-text-conversion-style) + (setq isearch-text-conversion-style + text-conversion-style) + (set-text-conversion-style 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. @@ -1475,6 +1488,10 @@ NOPUSH is t and EDIT is t." (setq isearch-tool-bar-old-map nil)) (kill-local-variable 'tool-bar-map)) + ;; Restore the previous text conversion style. + (when (fboundp 'set-text-conversion-style) + (set-text-conversion-style isearch-text-conversion-style)) + (force-mode-line-update) ;; If we ended in the middle of some intangible text, diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 8c1a0d4b21c..ef0eb1ca108 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -2918,7 +2918,10 @@ For customizing this mode, it is better to use `minibuffer-setup-hook' and `minibuffer-exit-hook' rather than the mode hook of this mode." :syntax-table nil - :interactive nil) + :interactive nil + ;; Enable text conversion, but always make sure `RET' does + ;; something. + (setq text-conversion-style 'action)) ;;; Completion tables. diff --git a/lisp/simple.el b/lisp/simple.el index 6a12585a55d..e5bf90a8055 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -10887,8 +10887,9 @@ For each insertion: line breaking of the previous line when `auto-fill-mode' is enabled. - - Look for the insertion of a new line, and indent this new - line if `electric-indent-mode' is enabled." + - Run `post-self-insert-functions' for the last character of + any inserted text so that modes such as `electric-pair-mode' + can work." (interactive) (dolist (edit text-conversion-edits) ;; Filter out ephemeral edits and deletions. @@ -10912,9 +10913,9 @@ For each insertion: (when (and auto-fill-function auto-fill-p) (progn (goto-char (nth 2 edit)) (funcall auto-fill-function))))) - (when (and electric-indent-mode newline-p) - (goto-char (nth 2 edit)) - (indent-according-to-mode))))))) + (goto-char (nth 2 edit)) + (let ((last-command-event end)) + (run-hooks 'post-self-insert-hook))))))) diff --git a/lisp/subr.el b/lisp/subr.el index f4f041ff32d..8e594fafa8c 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -3424,6 +3424,9 @@ an error message." (minibuffer-message "Wrong answer") (sit-for 2))) +;; Defined in textconv.c. +(defvar overriding-text-conversion-style) + (defun read-char-from-minibuffer (prompt &optional chars history) "Read a character from the minibuffer, prompting for it with PROMPT. Like `read-char', but uses the minibuffer to read and return a character. @@ -3438,7 +3441,15 @@ while calling this function, then pressing `help-char' causes it to evaluate `help-form' and display the result. There is no need to explicitly add `help-char' to CHARS; `help-char' is bound automatically to `help-form-show'." - (let* ((map (if (consp chars) + + ;; If text conversion is enabled in this buffer, then it will only + ;; be disabled the next time `force-mode-line-update' happens. + (when (and overriding-text-conversion-style + text-conversion-style) + (force-mode-line-update)) + + (let* ((overriding-text-conversion-style nil) + (map (if (consp chars) (or (gethash (list help-form (cons help-char chars)) read-char-from-minibuffer-map-hash) (let ((map (make-sparse-keymap)) @@ -3450,15 +3461,15 @@ There is no need to explicitly add `help-char' to CHARS; ;; being a command char. (when help-form (define-key map (vector help-char) - (lambda () - (interactive) - (let ((help-form msg)) ; lexically bound msg - (help-form-show))))) + (lambda () + (interactive) + (let ((help-form msg)) ; lexically bound msg + (help-form-show))))) (dolist (char chars) (define-key map (vector char) - #'read-char-from-minibuffer-insert-char)) + #'read-char-from-minibuffer-insert-char)) (define-key map [remap self-insert-command] - #'read-char-from-minibuffer-insert-other) + #'read-char-from-minibuffer-insert-other) (puthash (list help-form (cons help-char chars)) map read-char-from-minibuffer-map-hash) map)) diff --git a/src/androidterm.c b/src/androidterm.c index 0c990d3d2d2..c6f75ec9219 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -258,10 +258,6 @@ 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; @@ -5128,7 +5124,8 @@ android_update_selection (struct frame *f, struct window *w) /* Figure out where the point and mark are. If the mark is not active, then point is set to equal mark. */ b = XBUFFER (w->contents); - point = min (w->last_point, TYPE_MAXIMUM (jint)); + point = min (w->ephemeral_last_point, + TYPE_MAXIMUM (jint)); mark = ((!NILP (BVAR (b, mark_active)) && w->last_mark != -1) ? min (w->last_mark, TYPE_MAXIMUM (jint)) @@ -5150,6 +5147,7 @@ android_reset_conversion (struct frame *f) enum android_ic_mode mode; struct window *w; struct buffer *buffer; + Lisp_Object style; /* Reset the input method. @@ -5160,19 +5158,20 @@ android_reset_conversion (struct frame *f) w = XWINDOW (f->selected_window); buffer = XBUFFER (WINDOW_BUFFER (w)); - if (NILP (BVAR (buffer, text_conversion_style))) + style = (EQ (find_symbol_value (Qoverriding_text_conversion_style), + Qlambda) + ? BVAR (buffer, text_conversion_style) + : find_symbol_value (Qoverriding_text_conversion_style)); + + if (NILP (style) || conversion_disabled_p ()) mode = ANDROID_IC_MODE_NULL; - else if (EQ (BVAR (buffer, text_conversion_style), - Qaction)) + else if (EQ (style, Qaction) || EQ (f->selected_window, + f->minibuffer_window)) mode = ANDROID_IC_MODE_ACTION; else mode = ANDROID_IC_MODE_TEXT; - android_reset_ic (FRAME_ANDROID_WINDOW (f), - (EQ (f->selected_window, - f->minibuffer_window) - ? ANDROID_IC_MODE_ACTION - : mode)); + android_reset_ic (FRAME_ANDROID_WINDOW (f), mode); /* Move its selection to the specified position. */ android_update_selection (f, NULL); diff --git a/src/buffer.c b/src/buffer.c index af4aa583c96..393b8c5340a 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -5862,15 +5862,19 @@ Use Custom to set this variable and update the display. */); DEFVAR_PER_BUFFER ("text-conversion-style", &BVAR (current_buffer, text_conversion_style), Qnil, - "How the on screen keyboard's input method should insert in this buffer.\n\ -When nil, the input method will be disabled and an ordinary keyboard\n\ -will be displayed in its place.\n\ -When the symbol `action', the input method will insert text directly, but\n\ -will send `return' key events instead of inserting new line characters.\n\ -Any other value means that the input method will insert text directly.\n\ -\n\ -This variable does not take immediate effect when set; rather, it takes\n\ -effect upon the next redisplay after the selected window or buffer changes."); + doc: /* How the on screen keyboard's input method should insert in this buffer. +When nil, the input method will be disabled and an ordinary keyboard +will be displayed in its place. +When the symbol `action', the input method will insert text directly, but +will send `return' key events instead of inserting new line characters. +Any other value means that the input method will insert text directly. + +If you need to make non-buffer local changes to this variable, use +`overriding-text-conversion-style', which see. + +This variable does not take immediate effect when set; rather, it +takes effect upon the next redisplay after the selected window or +buffer changes. */); DEFVAR_LISP ("kill-buffer-query-functions", Vkill_buffer_query_functions, doc: /* List of functions called with no args to query before killing a buffer. diff --git a/src/keyboard.c b/src/keyboard.c index 538fdffc199..a8062adc468 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4050,14 +4050,6 @@ kbd_buffer_get_event (KBOARD **kbp, x_handle_pending_selection_requests (); #endif -#ifdef HAVE_TEXT_CONVERSION - /* Handle pending ``text conversion'' requests from an input - method. */ - - if (had_pending_conversion_events) - handle_pending_conversion_events (); -#endif - if (CONSP (Vunread_command_events)) { Lisp_Object first; @@ -4067,6 +4059,24 @@ kbd_buffer_get_event (KBOARD **kbp, return first; } +#ifdef HAVE_TEXT_CONVERSION + /* There are pending text conversion operations. Text conversion + events should be generated before processing any other keyboard + input. */ + if (had_pending_conversion_events) + { + handle_pending_conversion_events (); + obj = Qtext_conversion; + + /* See the comment in handle_pending_conversion_events_1. + Note that in addition, text conversion events are not + generated if no edits were actually made. */ + if (conversion_disabled_p () + || NILP (Vtext_conversion_edits)) + obj = Qnil; + } + else +#endif /* At this point, we know that there is a readable event available somewhere. If the event queue is empty, then there must be a mouse movement enabled and available. */ @@ -4414,25 +4424,12 @@ kbd_buffer_get_event (KBOARD **kbp, #ifdef HAVE_X_WINDOWS else if (had_pending_selection_requests) obj = Qnil; -#endif -#ifdef HAVE_TEXT_CONVERSION - /* This is an internal event used to prevent Emacs from becoming - idle immediately after a text conversion operation. */ - else if (had_pending_conversion_events) - obj = Qtext_conversion; #endif else /* We were promised by the above while loop that there was something for us to read! */ emacs_abort (); -#ifdef HAVE_TEXT_CONVERSION - /* While not implemented as keyboard commands, changes made by the - input method still mean that Emacs is no longer idle. */ - if (had_pending_conversion_events) - timer_stop_idle (); -#endif - input_pending = readable_events (0); Vlast_event_frame = internal_last_event_frame; diff --git a/src/lisp.h b/src/lisp.h index a39ca8cc541..56ef338a5b1 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -5234,6 +5234,8 @@ extern void reset_frame_state (struct frame *); extern void report_selected_window_change (struct frame *); extern void report_point_change (struct frame *, struct window *, struct buffer *); +extern void disable_text_conversion (void); +extern void resume_text_conversion (void); extern void syms_of_textconv (void); #endif diff --git a/src/lread.c b/src/lread.c index 6abcea556bf..150d8a01e10 100644 --- a/src/lread.c +++ b/src/lread.c @@ -672,7 +672,11 @@ static void substitute_in_interval (INTERVAL, void *); if the character warrants that. If SECONDS is a number, wait that many seconds for input, and - return Qnil if no input arrives within that time. */ + return Qnil if no input arrives within that time. + + If text conversion is enabled and ASCII_REQUIRED && ERROR_NONASCII, + temporarily disable any input method which wants to perform + edits. */ static Lisp_Object read_filtered_event (bool no_switch_frame, bool ascii_required, @@ -680,12 +684,28 @@ read_filtered_event (bool no_switch_frame, bool ascii_required, { Lisp_Object val, delayed_switch_frame; struct timespec end_time; +#ifdef HAVE_TEXT_CONVERSION + specpdl_ref count; +#endif #ifdef HAVE_WINDOW_SYSTEM if (display_hourglass_p) cancel_hourglass (); #endif +#ifdef HAVE_TEXT_CONVERSION + count = SPECPDL_INDEX (); + + /* Don't use text conversion when trying to just read a + character. */ + + if (ascii_required && error_nonascii) + { + disable_text_conversion (); + record_unwind_protect_void (resume_text_conversion); + } +#endif + delayed_switch_frame = Qnil; /* Compute timeout. */ @@ -761,7 +781,11 @@ read_filtered_event (bool no_switch_frame, bool ascii_required, #endif +#ifdef HAVE_TEXT_CONVERSION + return unbind_to (count, val); +#else return val; +#endif } DEFUN ("read-char", Fread_char, Sread_char, 0, 3, 0, diff --git a/src/sfnt.c b/src/sfnt.c index 38f6984b93c..b6f4a48ea8b 100644 --- a/src/sfnt.c +++ b/src/sfnt.c @@ -2708,6 +2708,281 @@ sfnt_lerp_half (struct sfnt_point *control1, struct sfnt_point *control2, result->y = control1->y + ((control2->y - control1->y) >> 1); } +/* Decompose contour data inside X, Y and FLAGS, between the indices + HERE and LAST. Call LINE_TO, CURVE_TO and MOVE_TO as appropriate, + with DCONTEXT as an argument. Apply SCALE to each point; SCALE + should be the factor necessary to turn points into 16.16 fixed + point. + + Value is 1 upon failure, else 0. */ + +static int +sfnt_decompose_glyph_1 (size_t here, size_t last, + sfnt_move_to_proc move_to, + sfnt_line_to_proc line_to, + sfnt_curve_to_proc curve_to, + void *dcontext, + sfnt_fword *x, + sfnt_fword *y, unsigned char *flags, + int scale) +{ + struct sfnt_point control1, control2, start, mid; + size_t i; + + /* The contour is empty. */ + + if (here == last) + return 1; + + /* Move the pen to the start of the contour. Apparently some fonts + have off the curve points as the start of a contour, so when that + happens lerp between the first and last points. */ + + if (flags[here] & 01) /* On Curve */ + { + control1.x = x[here] * scale; + control1.y = y[here] * scale; + start = control1; + } + else if (flags[last] & 01) + { + /* Start at the last point if it is on the curve. Here, the + start really becomes the middle of a spline. */ + control1.x = x[last] * scale; + control1.y = y[last] * scale; + start = control1; + + /* Curve back one point early. */ + last -= 1; + here -= 1; + } + else + { + /* Lerp between the start and the end. */ + control1.x = x[here] * scale; + control1.y = y[here] * scale; + control2.x = x[last] * scale; + control2.y = y[last] * scale; + sfnt_lerp_half (&control1, &control2, &start); + + /* In either of these cases, start iterating from just here as + opposed to here + 1, since logically the contour now starts + from the last curve. */ + here -= 1; + } + + /* Move to the start. */ + move_to (start, dcontext); + + /* Now handle each point between here + 1 and last. */ + + i = here; + while (++i <= last) + { + /* If the point is on the curve, then draw a line here from the + last control point. */ + + if (flags[i] & 01) + { + control1.x = x[i] * scale; + control1.y = y[i] * scale; + + line_to (control1, dcontext); + + /* Move to the next point. */ + continue; + } + + /* Off the curve points are more interesting. They are handled + one by one, with points in between being interpolated, until + either the last point is reached or an on-curve point is + processed. First, load the initial control points. */ + + control1.x = x[i] * scale; + control1.y = y[i] * scale; + + while (++i <= last) + { + /* Load this point. */ + control2.x = x[i] * scale; + control2.y = y[i] * scale; + + /* If this point is on the curve, curve directly to this + point. */ + + if (flags[i] & 01) + { + curve_to (control1, control2, dcontext); + goto continue_loop; + } + + /* Calculate the point between here and the previous + point. */ + sfnt_lerp_half (&control1, &control2, &mid); + + /* Curve over there. */ + curve_to (control1, mid, dcontext); + + /* Reload the control point. */ + control1 = control2; + } + + /* Close the contour by curving back to start. */ + curve_to (control1, start, dcontext); + + /* Don't close the contour twice. */ + goto exit; + + continue_loop: + continue; + } + + /* Close the contour with a line back to start. */ + line_to (start, dcontext); + + exit: + return 0; +} + +/* Decompose contour data inside X, Y and FLAGS, between the indices + HERE and LAST. Call LINE_TO, CURVE_TO and MOVE_TO as appropriate, + with DCONTEXT as an argument. Apply SCALE to each point; SCALE + should be the factor necessary to turn points into 16.16 fixed + point. + + This is the version of sfnt_decompose_glyph_1 which takes + sfnt_fixed (or sfnt_f26dot6) as opposed to sfnt_fword. + + Value is 1 upon failure, else 0. */ + +static int +sfnt_decompose_glyph_2 (size_t here, size_t last, + sfnt_move_to_proc move_to, + sfnt_line_to_proc line_to, + sfnt_curve_to_proc curve_to, + void *dcontext, + sfnt_fixed *x, + sfnt_fixed *y, unsigned char *flags, + int scale) +{ + struct sfnt_point control1, control2, start, mid; + size_t i; + + /* The contour is empty. */ + + if (here == last) + return 1; + + /* Move the pen to the start of the contour. Apparently some fonts + have off the curve points as the start of a contour, so when that + happens lerp between the first and last points. */ + + if (flags[here] & 01) /* On Curve */ + { + control1.x = x[here] * scale; + control1.y = y[here] * scale; + start = control1; + } + else if (flags[last] & 01) + { + /* Start at the last point if it is on the curve. Here, the + start really becomes the middle of a spline. */ + control1.x = x[last] * scale; + control1.y = y[last] * scale; + start = control1; + + /* Curve back one point early. */ + last -= 1; + here -= 1; + } + else + { + /* Lerp between the start and the end. */ + control1.x = x[here] * scale; + control1.y = y[here] * scale; + control2.x = x[last] * scale; + control2.y = y[last] * scale; + sfnt_lerp_half (&control1, &control2, &start); + + /* In either of these cases, start iterating from just here as + opposed to here + 1, since logically the contour now starts + from the last curve. */ + here -= 1; + } + + /* Move to the start. */ + move_to (start, dcontext); + + /* Now handle each point between here + 1 and last. */ + + i = here; + while (++i <= last) + { + /* If the point is on the curve, then draw a line here from the + last control point. */ + + if (flags[i] & 01) + { + control1.x = x[i] * scale; + control1.y = y[i] * scale; + + line_to (control1, dcontext); + + /* Move to the next point. */ + continue; + } + + /* Off the curve points are more interesting. They are handled + one by one, with points in between being interpolated, until + either the last point is reached or an on-curve point is + processed. First, load the initial control points. */ + + control1.x = x[i] * scale; + control1.y = y[i] * scale; + + while (++i <= last) + { + /* Load this point. */ + control2.x = x[i] * scale; + control2.y = y[i] * scale; + + /* If this point is on the curve, curve directly to this + point. */ + + if (flags[i] & 01) + { + curve_to (control1, control2, dcontext); + goto continue_loop; + } + + /* Calculate the point between here and the previous + point. */ + sfnt_lerp_half (&control1, &control2, &mid); + + /* Curve over there. */ + curve_to (control1, mid, dcontext); + + /* Reload the control point. */ + control1 = control2; + } + + /* Close the contour by curving back to start. */ + curve_to (control1, start, dcontext); + + /* Don't close the contour twice. */ + goto exit; + + continue_loop: + continue; + } + + /* Close the contour with a line back to start. */ + line_to (start, dcontext); + + exit: + return 0; +} + /* Decompose GLYPH into its individual components. Call MOVE_TO to move to a specific location. For each line encountered, call LINE_TO to draw a line to that location. For each spline @@ -2736,10 +3011,8 @@ sfnt_decompose_glyph (struct sfnt_glyph *glyph, sfnt_free_glyph_proc free_glyph, void *dcontext) { - size_t here, start, last; - struct sfnt_point pen, control1, control2; + size_t here, last, n; struct sfnt_compound_glyph_context context; - size_t n; if (glyph->simple) { @@ -2756,113 +3029,23 @@ sfnt_decompose_glyph (struct sfnt_glyph *glyph, of the last point in the contour. */ last = glyph->simple->end_pts_of_contours[n]; - /* Move to the start. */ - pen.x = glyph->simple->x_coordinates[here] * 65536; - pen.y = glyph->simple->y_coordinates[here] * 65536; - move_to (pen, dcontext); - - /* Record start so the contour can be closed. */ - start = here; + /* Make sure here and last make sense. */ - /* If there is only one point in a contour, draw a one pixel - wide line. */ - if (last == here) - { - line_to (pen, dcontext); - here++; - - continue; - } - - if (here > last) - /* Indices moved backwards. */ + if (here > last || last >= glyph->simple->number_of_points) return 1; - /* Now start reading points. If the next point is on the - curve, then it is actually a line. */ - for (++here; here <= last; ++here) - { - /* Make sure here is within bounds. */ - if (here >= glyph->simple->number_of_points) - return 1; - - if (glyph->simple->flags[here] & 01) /* On Curve */ - { - pen.x = glyph->simple->x_coordinates[here] * 65536; - pen.y = glyph->simple->y_coordinates[here] * 65536; - - /* See if the last point was on the curve. If it - wasn't, then curve from there to here. */ - if (!(glyph->simple->flags[here - 1] & 01)) - { - control1.x - = glyph->simple->x_coordinates[here - 1] * 65536; - control1.y - = glyph->simple->y_coordinates[here - 1] * 65536; - curve_to (control1, pen, dcontext); - } - else - /* Otherwise, this is an ordinary line from there - to here. */ - line_to (pen, dcontext); - - continue; - } - - /* If the last point was on the curve, then there's - nothing extraordinary to do yet. */ - if (glyph->simple->flags[here - 1] & 01) - ; - else - { - /* Otherwise, interpolate the point halfway between - the last and current points and make that point - the pen. */ - control1.x = glyph->simple->x_coordinates[here - 1] * 65536; - control1.y = glyph->simple->y_coordinates[here - 1] * 65536; - control2.x = glyph->simple->x_coordinates[here] * 65536; - control2.y = glyph->simple->y_coordinates[here] * 65536; - sfnt_lerp_half (&control1, &control2, &pen); - curve_to (control1, pen, dcontext); - } - } - - /* Now close the contour if there is more than one point - inside it. */ - if (start != here - 1) - { - /* Restore here after the for loop increased it. */ - here --; - - /* Previously, this would check whether or not start is - an ``on curve'' point, but that is not necessary. - - If a contour is not closed and the edge building - process skips the second to last vertex, then the - outline can end up with missing edges. */ - - pen.x = glyph->simple->x_coordinates[start] * 65536; - pen.y = glyph->simple->y_coordinates[start] * 65536; - - /* See if the last point (in this case, `here') was - on the curve. If it wasn't, then curve from - there to here. */ - if (!(glyph->simple->flags[here] & 01)) - { - control1.x - = glyph->simple->x_coordinates[here] * 65536; - control1.y - = glyph->simple->y_coordinates[here] * 65536; - curve_to (control1, pen, dcontext); - } - else - /* Otherwise, this is an ordinary line from there - to here. */ - line_to (pen, dcontext); + /* Now perform the decomposition. */ + if (sfnt_decompose_glyph_1 (here, last, move_to, + line_to, curve_to, + dcontext, + glyph->simple->x_coordinates, + glyph->simple->y_coordinates, + glyph->simple->flags, + 65536)) + return 1; - /* Restore here to where it was earlier. */ - here++; - } + /* Move forward to the start of the next contour. */ + here = last + 1; } return 0; @@ -2898,108 +3081,22 @@ sfnt_decompose_glyph (struct sfnt_glyph *glyph, of the last point in the contour. */ last = context.contour_end_points[n]; - /* Move to the start. */ - pen.x = context.x_coordinates[here]; - pen.y = context.y_coordinates[here]; - move_to (pen, dcontext); - - /* Record start so the contour can be closed. */ - start = here; - - /* If there is only one point in a contour, draw a one pixel - wide line. */ - if (last == here) - { - line_to (pen, dcontext); - here++; - - continue; - } + /* Make sure here and last make sense. */ - if (here > last) - /* Indices moved backwards. */ + if (here > last || last >= context.num_points) goto fail; - /* Now start reading points. If the next point is on the - curve, then it is actually a line. */ - for (++here; here <= last; ++here) - { - /* Make sure here is within bounds. */ - if (here >= context.num_points) - return 1; - - if (context.flags[here] & 01) /* On Curve */ - { - pen.x = context.x_coordinates[here]; - pen.y = context.y_coordinates[here]; - - /* See if the last point was on the curve. If it - wasn't, then curve from there to here. */ - if (!(context.flags[here - 1] & 01)) - { - control1.x = context.x_coordinates[here - 1]; - control1.y = context.y_coordinates[here - 1]; - curve_to (control1, pen, dcontext); - } - else - /* Otherwise, this is an ordinary line from there - to here. */ - line_to (pen, dcontext); - - continue; - } - - /* If the last point was on the curve, then there's - nothing extraordinary to do yet. */ - if (context.flags[here - 1] & 01) - ; - else - { - /* Otherwise, interpolate the point halfway between - the last and current points and make that point - the pen. */ - control1.x = context.x_coordinates[here - 1]; - control1.y = context.y_coordinates[here - 1]; - control2.x = context.x_coordinates[here]; - control2.y = context.y_coordinates[here]; - sfnt_lerp_half (&control1, &control2, &pen); - curve_to (control1, pen, dcontext); - } - } - - /* Now close the contour if there is more than one point - inside it. */ - if (start != here - 1) - { - /* Restore here after the for loop increased it. */ - here --; - - /* Previously, this would check whether or not start is an - ``on curve'' point, but that is not necessary. - - If a contour is not closed and the edge building process - skips the second to last vertex, then the outline can end - up with missing edges. */ - - pen.x = context.x_coordinates[start]; - pen.y = context.y_coordinates[start]; - - /* See if the last point (in this case, `here') was on the - curve. If it wasn't, then curve from there to here. */ - if (!(context.flags[here] & 01)) - { - control1.x = context.x_coordinates[here]; - control1.y = context.y_coordinates[here]; - curve_to (control1, pen, dcontext); - } - else - /* Otherwise, this is an ordinary line from there - to here. */ - line_to (pen, dcontext); + /* Now perform the decomposition. */ + if (sfnt_decompose_glyph_2 (here, last, move_to, + line_to, curve_to, + dcontext, + context.x_coordinates, + context.y_coordinates, + context.flags, 1)) + goto fail; - /* Restore here to where it was earlier. */ - here++; - } + /* Move forward. */ + here = last + 1; } early: @@ -10499,9 +10596,7 @@ sfnt_decompose_instructed_outline (struct sfnt_instructed_outline *outline, sfnt_curve_to_proc curve_to, void *dcontext) { - size_t here, start, last; - struct sfnt_point pen, control1, control2; - size_t n; + size_t here, last, n; if (!outline->num_contours) return 0; @@ -10515,109 +10610,20 @@ sfnt_decompose_instructed_outline (struct sfnt_instructed_outline *outline, of the last point in the contour. */ last = outline->contour_end_points[n]; - /* Move to the start. */ - pen.x = outline->x_points[here] * 1024; - pen.y = outline->y_points[here] * 1024; - move_to (pen, dcontext); - - /* Record start so the contour can be closed. */ - start = here; - - /* If there is only one point in a contour, draw a one pixel - wide line. */ - if (last == here) - { - line_to (pen, dcontext); - here++; - - continue; - } + /* Make sure here and last make sense. */ - if (here > last) - /* Indices moved backwards. */ + if (here > last || last >= outline->num_points) goto fail; - /* Now start reading points. If the next point is on the - curve, then it is actually a line. */ - for (++here; here <= last; ++here) - { - /* Make sure here is within bounds. */ - if (here >= outline->num_points) - return 1; - - if (outline->flags[here] & 01) /* On Curve */ - { - pen.x = outline->x_points[here] * 1024; - pen.y = outline->y_points[here] * 1024; - - /* See if the last point was on the curve. If it - wasn't, then curve from there to here. */ - if (!(outline->flags[here - 1] & 01)) - { - control1.x = outline->x_points[here - 1] * 1024; - control1.y = outline->y_points[here - 1] * 1024; - curve_to (control1, pen, dcontext); - } - else - /* Otherwise, this is an ordinary line from there - to here. */ - line_to (pen, dcontext); - - continue; - } - - /* If the last point was on the curve, then there's - nothing extraordinary to do yet. */ - if (outline->flags[here - 1] & 01) - ; - else - { - /* Otherwise, interpolate the point halfway between - the last and current points and make that point - the pen. */ - control1.x = outline->x_points[here - 1] * 1024; - control1.y = outline->y_points[here - 1] * 1024; - control2.x = outline->x_points[here] * 1024; - control2.y = outline->y_points[here] * 1024; - sfnt_lerp_half (&control1, &control2, &pen); - curve_to (control1, pen, dcontext); - } - } - - /* Now close the contour if there is more than one point - inside it. */ - if (start != here - 1) - { - /* Restore here after the for loop increased it. */ - here --; - - /* Previously, this would check whether or not start is an - ``on curve'' point, but that is not necessary. - - If a contour is not closed and the edge building process - skips the second to last vertex, then the outline can end - up with missing edges. */ - - pen.x = outline->x_points[start] * 1024; - pen.y = outline->y_points[start] * 1024; - - /* See if the last point (in this case, `here') was - on the curve. If it wasn't, then curve from - there to here. */ - if (!(outline->flags[here] & 01)) - { - control1.x = outline->x_points[here] * 1024; - control1.y = outline->y_points[here] * 1024; - curve_to (control1, pen, dcontext); - } - else - /* Otherwise, this is an ordinary line from there - to here. */ - line_to (pen, dcontext); + if (sfnt_decompose_glyph_2 (here, last, move_to, + line_to, curve_to, dcontext, + outline->x_points, + outline->y_points, + outline->flags, 1024)) + goto fail; - /* Restore here to where it was earlier. */ - here++; - } + /* Move forward to the start of the next contour. */ + here = last + 1; /* here may be a phantom point when outlining a compound glyph, as they can have phantom points mixed in with contours. @@ -15530,6 +15536,9 @@ main (int argc, char **argv) return 1; } + fprintf (stderr, "number of subtables: %"PRIu16"\n", + table->num_subtables); + for (i = 0; i < table->num_subtables; ++i) { fprintf (stderr, "Found cmap table %"PRIu32": %p\n", @@ -15540,8 +15549,8 @@ main (int argc, char **argv) data[i]->format); } -#define FANCY_PPEM 12 -#define EASY_PPEM 12 +#define FANCY_PPEM 40 +#define EASY_PPEM 40 interpreter = NULL; head = sfnt_read_head_table (fd, font); diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c index feea92827d9..c28a911bfba 100644 --- a/src/sfntfont-android.c +++ b/src/sfntfont-android.c @@ -49,9 +49,11 @@ struct sfntfont_android_scanline_buffer }; /* Array of directories to search for system fonts. */ -const char *system_font_directories[] = +static char *system_font_directories[] = { "/system/fonts", + /* This should be filled in by init_sfntfont_android. */ + (char[PATH_MAX]) { }, }; /* The font cache. */ @@ -691,6 +693,10 @@ loaded before character sets are made available. */) { dir = opendir (system_font_directories[i]); + __android_log_print (ANDROID_LOG_VERBOSE, __func__, + "Loading fonts from: %s", + system_font_directories[i]); + if (!dir) continue; @@ -752,6 +758,11 @@ init_sfntfont_android (void) build_string ("Droid Sans Mono")), Fcons (build_string ("Sans Serif"), build_string ("Droid Sans"))); + + /* Set up the user fonts directory. This directory is ``fonts'' in + the Emacs files directory. */ + snprintf (system_font_directories[1], PATH_MAX, "%s/fonts", + android_get_home_directory ()); } void diff --git a/src/sfntfont.c b/src/sfntfont.c index a5ed54394a2..f9344067f1a 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -87,6 +87,10 @@ struct sfnt_font_desc /* The offset of the table directory within PATH. */ off_t offset; + + /* The number of glyphs in this font. Used to catch invalid cmap + tables. This is actually the number of glyphs - 1. */ + int num_glyphs; }; /* List of fonts. */ @@ -517,10 +521,11 @@ sfnt_enum_font_1 (int fd, const char *file, struct sfnt_offset_subtable *subtables, off_t offset) { - struct sfnt_font_desc *desc; + struct sfnt_font_desc *desc, **next, *prev; struct sfnt_head_table *head; struct sfnt_name_table *name; struct sfnt_meta_table *meta; + struct sfnt_maxp_table *maxp; Lisp_Object family, style; /* Create the font desc and copy in the file name. */ @@ -543,12 +548,16 @@ sfnt_enum_font_1 (int fd, const char *file, if (!name) goto bail2; + maxp = sfnt_read_maxp_table (fd, subtables); + if (!maxp) + goto bail3; + /* meta is not required, nor present on many non-Apple fonts. */ meta = sfnt_read_meta_table (fd, subtables); /* Decode the family and style from the name table. */ if (sfnt_decode_family_style (name, &family, &style)) - goto bail3; + goto bail4; /* Set the family. */ desc->family = family; @@ -556,6 +565,9 @@ sfnt_enum_font_1 (int fd, const char *file, desc->char_cache = Qnil; desc->subtable.platform_id = 500; + /* Set the largest glyph identifier. */ + desc->num_glyphs = maxp->num_glyphs; + /* Parse the style. */ sfnt_parse_style (style, desc); @@ -584,13 +596,32 @@ sfnt_enum_font_1 (int fd, const char *file, desc->next = system_fonts; system_fonts = desc; + /* Remove any fonts which have the same style as this one. */ + + next = &system_fonts->next; + prev = *next; + for (; *next; prev = *next) + { + if (!NILP (Fstring_equal (prev->style, desc->style)) + && !NILP (Fstring_equal (prev->family, desc->family))) + { + *next = prev->next; + xfree (prev); + } + else + next = &prev->next; + } + xfree (meta); + xfree (maxp); xfree (name); xfree (head); return 0; - bail3: + bail4: xfree (meta); + xfree (maxp); + bail3: xfree (name); bail2: xfree (head); @@ -602,6 +633,8 @@ sfnt_enum_font_1 (int fd, const char *file, /* Enumerate the font FILE into the list of system fonts. Return 1 if it could not be enumerated, 0 otherwise. + Remove any font whose family and style is a duplicate of this one. + FILE can either be a TrueType collection file containing TrueType fonts, or a TrueType font itself. */ @@ -960,6 +993,25 @@ sfntfont_read_cmap (struct sfnt_font_desc *desc, emacs_close (fd); } +/* Return whether or not CHARACTER has an associated mapping in CMAP, + and the mapping points to a valid glyph. DESC is the font + descriptor associated with the font. */ + +static bool +sfntfont_glyph_valid (struct sfnt_font_desc *desc, + sfnt_char font_character, + struct sfnt_cmap_encoding_subtable_data *cmap) +{ + sfnt_glyph glyph; + + glyph = sfnt_lookup_glyph (font_character, cmap); + + if (!glyph) + return false; + + return glyph <= desc->num_glyphs; +} + /* Look up a character CHARACTER in the font description DESC. Cache the results. Return true if the character exists, false otherwise. @@ -1013,8 +1065,10 @@ sfntfont_lookup_char (struct sfnt_font_desc *desc, Lisp_Object character, if (font_character == CHARSET_INVALID_CODE (charset)) return false; - /* Now return whether or not the glyph is present. */ - present = sfnt_lookup_glyph (font_character, *cmap) != 0; + /* Now return whether or not the glyph is present. Noto Sans + Georgian comes with a corrupt format 4 cmap table that somehow + tries to express glyphs greater than 65565. */ + present = sfntfont_glyph_valid (desc, font_character, *cmap); /* Cache the result. Store Qlambda when not present, Qt otherwise. */ @@ -1133,22 +1187,23 @@ sfntfont_list_1 (struct sfnt_font_desc *desc, Lisp_Object spec) { tem = XCDR (tem); - /* tem is a list of each characters, one of which must be + /* tem is a list of each characters, all of which must be present in the font. */ FOR_EACH_TAIL_SAFE (tem) { - if (FIXNUMP (XCAR (tem))) - { - if (!sfntfont_lookup_char (desc, XCAR (tem), &cmap, - &subtable)) - goto fail; - - /* One character is enough to pass a font. Don't - look at too many. */ - break; - } + if (FIXNUMP (XCAR (tem)) + && !sfntfont_lookup_char (desc, XCAR (tem), &cmap, + &subtable)) + goto fail; } + + /* One or more characters are missing. */ + if (!NILP (tem)) + goto fail; } + /* Fail if there are no matching fonts at all. */ + else if (NILP (tem)) + goto fail; } /* Now check that the language is supported. */ diff --git a/src/textconv.c b/src/textconv.c index 835d03f3037..5090b0a33b6 100644 --- a/src/textconv.c +++ b/src/textconv.c @@ -35,6 +35,7 @@ along with GNU Emacs. If not, see . */ #include "textconv.h" #include "buffer.h" #include "syntax.h" +#include "blockinput.h" @@ -47,6 +48,10 @@ along with GNU Emacs. If not, see . */ static struct textconv_interface *text_interface; +/* How many times text conversion has been disabled. */ + +static int suppress_conversion_count; + /* Flags used to determine what must be sent after a batch edit ends. */ @@ -391,7 +396,8 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query, static void sync_overlay (struct frame *f) { - if (MARKERP (f->conversion.compose_region_start)) + if (MARKERP (f->conversion.compose_region_start) + && !NILP (Vtext_conversion_face)) { if (NILP (f->conversion.compose_region_overlay)) { @@ -400,7 +406,7 @@ sync_overlay (struct frame *f) f->conversion.compose_region_end, Qnil, Qt, Qnil); Foverlay_put (f->conversion.compose_region_overlay, - Qface, Qunderline); + Qface, Vtext_conversion_face); } Fmove_overlay (f->conversion.compose_region_overlay, @@ -514,6 +520,7 @@ really_commit_text (struct frame *f, EMACS_INT position, { specpdl_ref count; ptrdiff_t wanted, start, end; + struct window *w; /* If F's old selected window is no longer live, fail. */ @@ -624,6 +631,10 @@ really_commit_text (struct frame *f, EMACS_INT position, /* This should deactivate the mark. */ call0 (Qdeactivate_mark); + + /* Update the ephemeral last point. */ + w = XWINDOW (selected_window); + w->ephemeral_last_point = PT; unbind_to (count, Qnil); } @@ -760,6 +771,10 @@ really_set_composing_text (struct frame *f, ptrdiff_t position, text_interface->compose_region_changed (f); } + /* Update the ephemeral last point. */ + w = XWINDOW (selected_window); + w->ephemeral_last_point = PT; + unbind_to (count, Qnil); } @@ -771,6 +786,7 @@ really_set_composing_region (struct frame *f, ptrdiff_t start, ptrdiff_t end) { specpdl_ref count; + struct window *w; /* If F's old selected window is no longer live, fail. */ @@ -810,6 +826,10 @@ really_set_composing_region (struct frame *f, ptrdiff_t start, make_fixnum (end), Qnil); sync_overlay (f); + /* Update the ephemeral last point. */ + w = XWINDOW (selected_window); + w->ephemeral_last_point = PT; + unbind_to (count, Qnil); } @@ -823,6 +843,7 @@ really_delete_surrounding_text (struct frame *f, ptrdiff_t left, { specpdl_ref count; ptrdiff_t start, end, a, b, a1, b1, lstart, rstart; + struct window *w; /* If F's old selected window is no longer live, fail. */ @@ -889,6 +910,10 @@ really_delete_surrounding_text (struct frame *f, ptrdiff_t left, if (get_mark () == PT) call0 (Qdeactivate_mark); + /* Update the ephemeral last point. */ + w = XWINDOW (selected_window); + w->ephemeral_last_point = PT; + unbind_to (count, Qnil); } @@ -904,6 +929,7 @@ really_set_point_and_mark (struct frame *f, ptrdiff_t point, ptrdiff_t mark) { specpdl_ref count; + struct window *w; /* If F's old selected window is no longer live, fail. */ @@ -922,7 +948,7 @@ really_set_point_and_mark (struct frame *f, ptrdiff_t point, { if (f->conversion.batch_edit_count > 0) f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE; - else + else if (text_interface && text_interface->point_changed) text_interface->point_changed (f, XWINDOW (f->old_selected_window), current_buffer); @@ -936,6 +962,10 @@ really_set_point_and_mark (struct frame *f, ptrdiff_t point, else call1 (Qpush_mark, make_fixnum (mark)); + /* Update the ephemeral last point. */ + w = XWINDOW (selected_window); + w->ephemeral_last_point = PT; + unbind_to (count, Qnil); } @@ -949,9 +979,11 @@ complete_edit (void *token) } /* Process and free the text conversion ACTION. F must be the frame - on which ACTION will be performed. */ + on which ACTION will be performed. -static void + Value is the window which was used, or NULL. */ + +static struct window * handle_pending_conversion_events_1 (struct frame *f, struct text_conversion_action *action) { @@ -969,9 +1001,23 @@ handle_pending_conversion_events_1 (struct frame *f, token = action->counter; xfree (action); + /* Text conversion events can still arrive immediately after + `conversion_disabled_p' becomes true. In that case, process all + events, but don't perform any associated actions. */ + + if (conversion_disabled_p ()) + return NULL; + /* Make sure completion is signalled. */ count = SPECPDL_INDEX (); record_unwind_protect_ptr (complete_edit, &token); + w = NULL; + + if (WINDOW_LIVE_P (f->old_selected_window)) + { + w = XWINDOW (f->old_selected_window); + buffer = XBUFFER (WINDOW_BUFFER (w)); + } switch (operation) { @@ -987,12 +1033,7 @@ handle_pending_conversion_events_1 (struct frame *f, break; if (f->conversion.batch_edit_flags & PENDING_POINT_CHANGE) - { - w = XWINDOW (f->old_selected_window); - buffer = XBUFFER (WINDOW_BUFFER (w)); - - text_interface->point_changed (f, w, buffer); - } + text_interface->point_changed (f, w, buffer); if (f->conversion.batch_edit_flags & PENDING_COMPOSE_CHANGE) text_interface->compose_region_changed (f); @@ -1030,6 +1071,8 @@ handle_pending_conversion_events_1 (struct frame *f, } unbind_to (count, Qnil); + + return w; } /* Decrement the variable pointed to by *PTR. */ @@ -1055,6 +1098,8 @@ handle_pending_conversion_events (void) bool handled; static int inside; specpdl_ref count; + ptrdiff_t last_point; + struct window *w; handled = false; @@ -1065,6 +1110,8 @@ handle_pending_conversion_events (void) Vtext_conversion_edits = Qnil; inside++; + last_point = -1; + w = NULL; count = SPECPDL_INDEX (); record_unwind_protect_ptr (decrement_inside, &inside); @@ -1077,16 +1124,26 @@ handle_pending_conversion_events (void) process them in bottom to up order. */ while (true) { - /* Redisplay in between if there is more than one - action. - - This can read input. This function must be reentrant - here. */ - - if (handled) - redisplay (); + /* Update the input method if handled && + w->ephemeral_last_point != last_point. */ + if (w && (last_point != w->ephemeral_last_point)) + { + if (handled + && last_point != -1 + && text_interface + && text_interface->point_changed) + { + if (f->conversion.batch_edit_count > 0) + f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE; + else + text_interface->point_changed (f, NULL, NULL); + } + + last_point = w->ephemeral_last_point; + } - /* Reload action. */ + /* Reload action. This needs to be reentrant as buffer + modification functions can call `read-char'. */ action = f->conversion.actions; /* If there are no more actions, break. */ @@ -1099,7 +1156,7 @@ handle_pending_conversion_events (void) f->conversion.actions = next; /* Handle and free the action. */ - handle_pending_conversion_events_1 (f, action); + w = handle_pending_conversion_events_1 (f, action); handled = true; } } @@ -1399,6 +1456,16 @@ get_extracted_text (struct frame *f, ptrdiff_t n, return buffer; } +/* Return whether or not text conversion is temporarily disabled. + `reset' should always call this to determine whether or not to + disable the input method. */ + +bool +conversion_disabled_p (void) +{ + return suppress_conversion_count > 0; +} + /* Window system interface. These are called from the rest of @@ -1440,6 +1507,60 @@ report_point_change (struct frame *f, struct window *window, text_interface->point_changed (f, window, buffer); } +/* Temporarily disable text conversion. Must be paired with a + corresponding call to resume_text_conversion. */ + +void +disable_text_conversion (void) +{ + Lisp_Object tail, frame; + struct frame *f; + + suppress_conversion_count++; + + if (!text_interface || suppress_conversion_count > 1) + return; + + /* Loop through and reset the input method on each window system + frame. It should call conversion_disabled_p and then DTRT. */ + + FOR_EACH_FRAME (tail, frame) + { + f = XFRAME (frame); + reset_frame_state (f); + + if (FRAME_WINDOW_P (f) && FRAME_VISIBLE_P (f)) + text_interface->reset (f); + } +} + +/* Undo the effect of the last call to `disable_text_conversion'. */ + +void +resume_text_conversion (void) +{ + Lisp_Object tail, frame; + struct frame *f; + + suppress_conversion_count--; + eassert (suppress_conversion_count >= 0); + + if (!text_interface || suppress_conversion_count) + return; + + /* Loop through and reset the input method on each window system + frame. It should call conversion_disabled_p and then DTRT. */ + + FOR_EACH_FRAME (tail, frame) + { + f = XFRAME (frame); + reset_frame_state (f); + + if (FRAME_WINDOW_P (f) && FRAME_VISIBLE_P (f)) + text_interface->reset (f); + } +} + /* Register INTERFACE as the text conversion interface. */ void @@ -1450,6 +1571,59 @@ register_textconv_interface (struct textconv_interface *interface) +/* Lisp interface. */ + +DEFUN ("set-text-conversion-style", Fset_text_conversion_style, + Sset_text_conversion_style, 1, 1, 0, + doc: /* Set the text conversion style in the current buffer. + +Set `text-conversion-mode' to VALUE, then force any input method +editing frame displaying this buffer to stop itself. + +This can lead to a significant amount of time being taken by the input +method resetting itself, so you should not use this function lightly; +instead, set `text-conversion-mode' before your buffer is displayed, +and let redisplay manage the input method appropriately. */) + (Lisp_Object value) +{ + Lisp_Object tail, frame; + struct frame *f; + Lisp_Object buffer; + + bset_text_conversion_style (current_buffer, value); + + if (!text_interface) + return Qnil; + + /* If there are any seleted windows displaying this buffer, reset + text conversion on their associated frames. */ + + if (buffer_window_count (current_buffer)) + { + buffer = Fcurrent_buffer (); + + FOR_EACH_FRAME (tail, frame) + { + f = XFRAME (frame); + + if (WINDOW_LIVE_P (f->old_selected_window) + && FRAME_WINDOW_P (f) + && EQ (XWINDOW (f->old_selected_window)->contents, + buffer)) + { + block_input (); + reset_frame_state (f); + text_interface->reset (f); + unblock_input (); + } + } + } + + return Qnil; +} + + + void syms_of_textconv (void) { @@ -1457,6 +1631,7 @@ syms_of_textconv (void) DEFSYM (Qtext_conversion, "text-conversion"); DEFSYM (Qpush_mark, "push-mark"); DEFSYM (Qunderline, "underline"); + DEFSYM (Qoverriding_text_conversion_style, "overriding-text-conversion-style"); DEFVAR_LISP ("text-conversion-edits", Vtext_conversion_edits, doc: /* List of buffers that were last edited as a result of text conversion. @@ -1480,4 +1655,21 @@ inserted. If a deletion occured, then BEG and END are the same, and EPHEMERAL is nil. */); Vtext_conversion_edits = Qnil; + + DEFVAR_LISP ("overriding-text-conversion-style", + Voverriding_text_conversion_style, + doc: /* Non-buffer local version of `text-conversion-style'. + +If this variable is the symbol `lambda', it means to consult the +buffer local variable `text-conversion-style' to determine whether or +not to activate the input method. Otherwise, its value is used in +preference to any buffer local value of `text-conversion-style'. */); + Voverriding_text_conversion_style = Qlambda; + + DEFVAR_LISP ("text-conversion-face", Vtext_conversion_face, + doc: /* Face in which to display temporary edits by an input method. +nil means to display no indication of a temporary edit. */); + Vtext_conversion_face = Qunderline; + + defsubr (&Sset_text_conversion_style); } diff --git a/src/textconv.h b/src/textconv.h index 034c663521a..16d13deb092 100644 --- a/src/textconv.h +++ b/src/textconv.h @@ -140,6 +140,7 @@ extern void delete_surrounding_text (struct frame *, ptrdiff_t, ptrdiff_t, unsigned long); extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *); +extern bool conversion_disabled_p (void); extern void register_textconv_interface (struct textconv_interface *); diff --git a/src/window.h b/src/window.h index 463c7f89b9b..36b1bfb7283 100644 --- a/src/window.h +++ b/src/window.h @@ -286,6 +286,21 @@ struct window it should be positive. */ ptrdiff_t last_point; +#ifdef HAVE_TEXT_CONVERSION + /* ``ephemeral'' last point position. This is used while + processing text conversion events. + + `last_point' is normally used during redisplay to indicate the + position of point as seem by the input method. However, it is + not updated if consequtive conversions are processed at the + same time. + + This `ephemeral_last_point' field is either the last point as + set in redisplay or the last point after a text editing + operation. */ + ptrdiff_t ephemeral_last_point; +#endif + /* Value of mark in the selected window at the time of the last redisplay. */ ptrdiff_t last_mark; diff --git a/src/xdisp.c b/src/xdisp.c index 525eaa64b3a..a17fa97ee20 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -17315,6 +17315,9 @@ mark_window_display_accurate_1 (struct window *w, bool accurate_p) w->last_mark = -1; #ifdef HAVE_TEXT_CONVERSION + /* See the description of this field in struct window. */ + w->ephemeral_last_point = w->last_point; + /* Point motion is only propagated to the input method for use in text conversion during a redisplay. While this can lead to inconsistencies when point has moved but the change has -- cgit v1.3 From 0998ab3ade7caf2b163cd99a44b8a3f17be763d5 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 19 Feb 2023 19:56:51 +0800 Subject: Report both sides of the region to the input method upon setup * java/org/gnu/emacs/EmacsNative.java (getSelection): Return array of ints. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Adjust accordingly. * src/androidterm.c (struct android_get_selection_context): New field `mark'. (android_get_selection): Set the mark field as appropriate. (getSelection): Adjust accordingly. --- java/org/gnu/emacs/EmacsNative.java | 2 +- java/org/gnu/emacs/EmacsView.java | 9 +++++---- src/androidterm.c | 38 +++++++++++++++++++++++++++++++------ 3 files changed, 38 insertions(+), 11 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index aaac9446510..ef1a0e75a5c 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -205,7 +205,7 @@ public class EmacsNative /* Return the current value of the selection, or -1 upon failure. */ - public static native int getSelection (short window); + public static native int[] getSelection (short window); static { diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 5ea8b0dcc0e..89f526853b2 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -555,7 +555,8 @@ public class EmacsView extends ViewGroup public InputConnection onCreateInputConnection (EditorInfo info) { - int selection, mode; + int mode; + int[] selection; /* Figure out what kind of IME behavior Emacs wants. */ mode = getICMode (); @@ -575,7 +576,7 @@ public class EmacsView extends ViewGroup /* If this fails or ANDROID_IC_MODE_NULL was requested, then don't initialize the input connection. */ - if (selection == -1 || mode == EmacsService.IC_MODE_NULL) + if (mode == EmacsService.IC_MODE_NULL || selection == null) { info.inputType = InputType.TYPE_NULL; return null; @@ -585,8 +586,8 @@ public class EmacsView extends ViewGroup info.imeOptions |= EditorInfo.IME_ACTION_DONE; /* Set the initial selection fields. */ - info.initialSelStart = selection; - info.initialSelEnd = selection; + info.initialSelStart = selection[0]; + info.initialSelEnd = selection[1]; /* Create the input connection if necessary. */ diff --git a/src/androidterm.c b/src/androidterm.c index 62a8d5d12b3..0701ef44436 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -4831,8 +4831,8 @@ struct android_get_selection_context android_window window; /* The position of the window's point when it was last - redisplayed. */ - ptrdiff_t point; + redisplayed, and its last mark if active. */ + ptrdiff_t point, mark; }; /* Function run on the main thread by `getSelection'. @@ -4844,6 +4844,7 @@ android_get_selection (void *data) struct android_get_selection_context *context; struct frame *f; struct window *w; + struct buffer *b; context = data; @@ -4860,25 +4861,50 @@ android_get_selection (void *data) rather important to keep the input method consistent with the contents of the display. */ context->point = w->ephemeral_last_point; + + /* Default context->mark to w->last_point too. */ + context->mark = context->point; + + /* If the mark is active, then set it properly. */ + b = XBUFFER (w->contents); + if (!NILP (BVAR (b, mark_active)) && w->last_mark != -1) + context->mark = w->last_mark; } } -JNIEXPORT jint JNICALL +JNIEXPORT jintArray JNICALL NATIVE_NAME (getSelection) (JNIEnv *env, jobject object, jshort window) { struct android_get_selection_context context; + jintArray array; + jint contents[2]; context.window = window; android_sync_edit (); if (android_run_in_emacs_thread (android_get_selection, &context)) - return -1; + return NULL; if (context.point == -1) - return -1; + return NULL; + + /* Wraparound actually makes more sense than truncation; at least + editing will sort of work. */ + contents[0] = (unsigned int) min (context.point, + context.mark); + contents[1] = (unsigned int) max (context.point, + context.mark); + + /* Now create the array. */ + array = (*env)->NewIntArray (env, 2); + + if (!array) + return NULL; - return min (context.point, TYPE_MAXIMUM (jint)); + /* Set its contents. */ + (*env)->SetIntArrayRegion (env, array, 0, 2, contents); + return array; } JNIEXPORT void JNICALL -- 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/EmacsView.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 2634765bc382c27e2d11dc14174ca80d9cf41e15 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 4 Mar 2023 15:55:09 +0800 Subject: Improve context menus on old versions of Android * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field `lastClosedMenu'. (onContextMenuClosed): Don't send event if a menu is closed twice in a row. Also, clear wasSubmenuSelected immediately. * java/org/gnu/emacs/EmacsContextMenu.java: Display submenus manually in Android 6.0 and earlier. * java/org/gnu/emacs/EmacsView.java (onCreateContextMenu) (popupMenu): Adjust accordingly. --- java/org/gnu/emacs/EmacsActivity.java | 17 ++++++- java/org/gnu/emacs/EmacsContextMenu.java | 81 ++++++++++++++++++++++---------- java/org/gnu/emacs/EmacsView.java | 9 ++-- 3 files changed, 76 insertions(+), 31 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 62bef33fab3..13002d5d0e5 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -65,6 +65,9 @@ public class EmacsActivity extends Activity /* Whether or not this activity is fullscreen. */ private boolean isFullscreen; + /* The last context menu to be closed. */ + private Menu lastClosedMenu; + static { focusedActivities = new ArrayList (); @@ -308,9 +311,19 @@ public class EmacsActivity extends Activity Log.d (TAG, "onContextMenuClosed: " + menu); /* See the comment inside onMenuItemClick. */ + if (EmacsContextMenu.wasSubmenuSelected - && menu.toString ().contains ("ContextMenuBuilder")) - return; + || menu == lastClosedMenu) + { + EmacsContextMenu.wasSubmenuSelected = false; + lastClosedMenu = menu; + return; + } + + /* lastClosedMenu is set because Android apparently calls this + function twice. */ + + lastClosedMenu = null; /* Send a context menu event given that no menu item has already been selected. */ diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index a1bca98daa0..d1a624e68d9 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -58,6 +58,7 @@ public final class EmacsContextMenu public String itemName, tooltip; public EmacsContextMenu subMenu; public boolean isEnabled, isCheckable, isChecked; + public EmacsView inflatedView; @Override public boolean @@ -67,6 +68,34 @@ public final class EmacsContextMenu if (subMenu != null) { + /* Android 6.0 and earlier don't support nested submenus + properly, so display the submenu popup by hand. */ + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) + { + Log.d (TAG, "onMenuItemClick: displaying submenu " + subMenu); + + /* Still set wasSubmenuSelected -- if not set, the + dismissal of this context menu will result in a + context menu event being sent. */ + wasSubmenuSelected = true; + + /* Running a popup menu from inside a click handler + doesn't work, so make sure it is displayed + outside. */ + + inflatedView.post (new Runnable () { + @Override + public void + run () + { + inflatedView.popupMenu (subMenu, 0, 0, true); + } + }); + + return true; + } + /* After opening a submenu within a submenu, Android will send onContextMenuClosed for a ContextMenuBuilder. This will normally confuse Emacs into thinking that the @@ -164,10 +193,11 @@ public final class EmacsContextMenu return item.subMenu; } - /* Add the contents of this menu to MENU. */ + /* Add the contents of this menu to MENU. Assume MENU will be + displayed in INFLATEDVIEW. */ private void - inflateMenuItems (Menu menu) + inflateMenuItems (Menu menu, EmacsView inflatedView) { Intent intent; MenuItem menuItem; @@ -177,26 +207,26 @@ public final class EmacsContextMenu { if (item.subMenu != null) { - 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. + + Note that Android 4.0 and later technically supports + having multiple layers of nested submenus, but if they + are used, onContextMenuClosed becomes unreliable. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - /* 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); - item.subMenu.inflateMenuItems (submenu); - } - 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; + item.subMenu.inflateMenuItems (submenu, inflatedView); + + /* This is still needed to set wasSubmenuSelected. */ + menuItem = submenu.getItem (); } + else + menuItem = menu.add (item.itemName); - /* This is still needed to set wasSubmenuSelected. */ - menuItem = submenu.getItem (); + item.inflatedView = inflatedView; menuItem.setOnMenuItemClickListener (item); } else @@ -227,16 +257,14 @@ public final class EmacsContextMenu } } - /* 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. */ + /* Enter the items in this context menu to MENU. + Assume that MENU will be displayed in VIEW; this may lead to + popupMenu being called on VIEW if a submenu is selected. */ public void - expandTo (Menu menu) + expandTo (Menu menu, EmacsView view) { - inflateMenuItems (menu); + inflateMenuItems (menu, view); } /* Return the parent or NULL. */ @@ -260,7 +288,8 @@ public final class EmacsContextMenu /* No submenu has been selected yet. */ wasSubmenuSelected = false; - return window.view.popupMenu (this, xPosition, yPosition); + return window.view.popupMenu (this, xPosition, yPosition, + false); } /* Display this context menu on WINDOW, at xPosition and diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index d2330494bc7..617836d8811 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -464,19 +464,22 @@ public final class EmacsView extends ViewGroup if (contextMenu == null) return; - contextMenu.expandTo (menu); + contextMenu.expandTo (menu, this); } public boolean popupMenu (EmacsContextMenu menu, int xPosition, - int yPosition) + int yPosition, boolean force) { - if (popupActive) + if (popupActive && !force) return false; contextMenu = menu; popupActive = true; + Log.d (TAG, "popupMenu: " + menu + " @" + xPosition + + ", " + yPosition + " " + force); + /* Use showContextMenu (float, float) on N to get actual popup behavior. */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) -- 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/EmacsView.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/EmacsView.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 c0a6f14f4a5069c28b7c90247546f1c5889a6d21 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 6 Mar 2023 15:30:29 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function requestSelectionUpdate. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Call it instead of losing if getting the current selection fails. * src/android-asset.h (AAsset_seek): Define stub. * src/android.c (android_open): Take mode_t. (android_open_asset, android_close_asset, android_asset_read_quit) (android_asset_read, android_asset_lseek, android_asset_fstat): New functions. * src/android.h (struct android_fd_or_asset): Update prototypes. * src/androidgui.h (enum android_ime_operation): Add new operation to update the selection position. * src/androidterm.c (android_handle_ime_event): Handle new operation. (requestSelectionUpdate): New function. * src/fileio.c (close_file_unwind_emacs_fd): New function. (Fcopy_file, union read_non_regular, read_non_regular) (Finsert_file_contents): Use optimized codepath to insert Android asset files. * src/frame.h (enum text_conversion_operation): New operation. * src/textconv.c (really_request_point_update) (handle_pending_conversion_events_1, request_point_update): New functions. * src/textconv.h: Update prototypes. --- java/org/gnu/emacs/EmacsNative.java | 1 + java/org/gnu/emacs/EmacsView.java | 29 ++++-- src/android-asset.h | 8 ++ src/android.c | 150 ++++++++++++++++++++++++++++++- src/android.h | 26 +++++- src/androidgui.h | 1 + src/androidterm.c | 26 ++++++ src/fileio.c | 174 ++++++++++++++++++++++++------------ src/frame.h | 1 + src/textconv.c | 43 +++++++++ src/textconv.h | 1 + 11 files changed, 392 insertions(+), 68 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 38370d60727..8e626b9534b 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -203,6 +203,7 @@ public final class EmacsNative public static native ExtractedText getExtractedText (short window, ExtractedTextRequest req, int flags); + public static native void requestSelectionUpdate (short window); /* Return the current value of the selection, or -1 upon diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index f751eaaa3e4..90a2c912a5a 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -573,20 +573,35 @@ public final class EmacsView extends ViewGroup /* Set a reasonable inputType. */ info.inputType = InputType.TYPE_CLASS_TEXT; - /* Obtain the current position of point and set it as the - selection. */ - selection = EmacsNative.getSelection (window.handle); - - Log.d (TAG, "onCreateInputConnection: current selection is: " + selection); - /* If this fails or ANDROID_IC_MODE_NULL was requested, then don't initialize the input connection. */ - if (mode == EmacsService.IC_MODE_NULL || selection == null) + + if (mode == EmacsService.IC_MODE_NULL) { info.inputType = InputType.TYPE_NULL; return null; } + /* Obtain the current position of point and set it as the + selection. */ + selection = EmacsNative.getSelection (window.handle); + + if (selection != null) + Log.d (TAG, "onCreateInputConnection: current selection is: " + + selection[0] + ", by " + selection[1]); + else + { + Log.d (TAG, "onCreateInputConnection: current selection could" + + " not be retrieved."); + + /* If the selection could not be obtained, return 0 by 0. + However, ask for the selection position to be updated as + soon as possible. */ + + selection = new int[] { 0, 0, }; + EmacsNative.requestSelectionUpdate (window.handle); + } + if (mode == EmacsService.IC_MODE_ACTION) info.imeOptions |= EditorInfo.IME_ACTION_DONE; diff --git a/src/android-asset.h b/src/android-asset.h index 3b2f8105865..4fb309f1645 100644 --- a/src/android-asset.h +++ b/src/android-asset.h @@ -412,3 +412,11 @@ AAsset_read (AAsset *asset, void *buffer, size_t size) return android_asset_read_internal (asset, MIN (size, INT_MAX), buffer); } + +static off_t +AAsset_seek (AAsset *asset, off_t offset, int whence) +{ + /* Java InputStreams don't support seeking at all. */ + errno = ESPIPE; + return -1; +} diff --git a/src/android.c b/src/android.c index 9fc4143602c..f49249de09f 100644 --- a/src/android.c +++ b/src/android.c @@ -1568,7 +1568,7 @@ android_close_on_exec (int fd) contain all assets in the application package. */ int -android_open (const char *filename, int oflag, int mode) +android_open (const char *filename, int oflag, mode_t mode) { const char *name; AAsset *asset; @@ -5971,6 +5971,154 @@ android_set_fullscreen (android_window window, bool fullscreen) +/* External asset management interface. By using functions here + to read and write from files, Emacs can avoid opening a + shared memory file descriptor for each ``asset'' file. */ + +/* Like android_open. However, return a structure that can + either directly hold an AAsset or a file descriptor. + + Value is the structure upon success. Upon failure, value + consists of an uninitialized file descriptor, but its asset + field is set to -1, and errno is set accordingly. */ + +struct android_fd_or_asset +android_open_asset (const char *filename, int oflag, mode_t mode) +{ + const char *name; + struct android_fd_or_asset fd; + AAsset *asset; + + /* Initialize FD by setting its asset to an invalid + pointer. */ + fd.asset = (void *) -1; + + /* See if this is an asset. */ + + if (asset_manager && (name = android_get_asset_name (filename))) + { + /* Return failure for unsupported flags. */ + + if (oflag & O_WRONLY || oflag & O_RDWR) + { + errno = EROFS; + return fd; + } + + if (oflag & O_DIRECTORY) + { + errno = ENOTSUP; + return fd; + } + + /* Now try to open the asset. */ + asset = AAssetManager_open (asset_manager, name, + AASSET_MODE_STREAMING); + + if (!asset) + { + errno = ENOENT; + return fd; + } + + /* Return the asset. */ + fd.asset = asset; + return fd; + } + + /* If the file is not an asset, fall back to android_open and + get a regular file descriptor. */ + + fd.fd = android_open (filename, oflag, mode); + if (fd.fd < 1) + return fd; + + /* Set fd.asset to NULL, signifying that it is a file + descriptor. */ + fd.asset = NULL; + return fd; +} + +/* Like android_close. However, it takes a ``file descriptor'' + opened using android_open_asset. */ + +int +android_close_asset (struct android_fd_or_asset asset) +{ + if (!asset.asset) + return android_close (asset.fd); + + AAsset_close (asset.asset); + return 0; +} + +/* Like `emacs_read_quit'. However, it handles file descriptors + opened using `android_open_asset' as well. */ + +ssize_t +android_asset_read_quit (struct android_fd_or_asset asset, + void *buffer, size_t size) +{ + if (!asset.asset) + return emacs_read_quit (asset.fd, buffer, size); + + /* It doesn't seem possible to quit from inside AAsset_read, + sadly. */ + return AAsset_read (asset.asset, buffer, size); +} + +/* Like `read'. However, it handles file descriptors opened + using `android_open_asset' as well. */ + +ssize_t +android_asset_read (struct android_fd_or_asset asset, + void *buffer, size_t size) +{ + if (!asset.asset) + return read (asset.fd, buffer, size); + + /* It doesn't seem possible to quit from inside AAsset_read, + sadly. */ + return AAsset_read (asset.asset, buffer, size); +} + +/* Like `lseek', but it handles ``file descriptors'' opened with + android_open_asset. */ + +off_t +android_asset_lseek (struct android_fd_or_asset asset, off_t off, + int whence) +{ + if (!asset.asset) + return lseek (asset.fd, off, whence); + + return AAsset_seek (asset.asset, off, whence); +} + +/* Like `fstat'. */ + +int +android_asset_fstat (struct android_fd_or_asset asset, + struct stat *statb) +{ + if (!asset.asset) + return fstat (asset.fd, statb); + + /* Clear statb. */ + memset (statb, 0, sizeof *statb); + + /* Set the mode. */ + statb->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + + /* Owned by root. */ + statb->st_uid = 0; + statb->st_gid = 0; + + /* Size of the file. */ + statb->st_size = AAsset_getLength (asset.asset); + return 0; +} + #else /* ANDROID_STUBIFY */ /* X emulation functions for Android. */ diff --git a/src/android.h b/src/android.h index 95206b77979..ed0089ad94e 100644 --- a/src/android.h +++ b/src/android.h @@ -46,7 +46,7 @@ extern int android_emacs_init (int, char **, char *); extern int android_select (int, fd_set *, fd_set *, fd_set *, struct timespec *); -extern int android_open (const char *, int, int); +extern int android_open (const char *, int, mode_t); extern char *android_user_full_name (struct passwd *); extern int android_fstat (int, struct stat *); extern int android_fstatat (int, const char *restrict, @@ -107,6 +107,30 @@ extern void android_closedir (struct android_dir *); +/* External asset manager interface. */ + +struct android_fd_or_asset +{ + /* The file descriptor. */ + int fd; + + /* The asset. If set, FD is not a real file descriptor. */ + void *asset; +}; + +extern struct android_fd_or_asset android_open_asset (const char *, + int, mode_t); +extern int android_close_asset (struct android_fd_or_asset); +extern ssize_t android_asset_read_quit (struct android_fd_or_asset, + void *, size_t); +extern ssize_t android_asset_read (struct android_fd_or_asset, + void *, size_t); +extern off_t android_asset_lseek (struct android_fd_or_asset, off_t, int); +extern int android_asset_fstat (struct android_fd_or_asset, + struct stat *); + + + /* Very miscellaneous functions. */ struct android_battery_state diff --git a/src/androidgui.h b/src/androidgui.h index 6514c78d289..e1c80a71a59 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -430,6 +430,7 @@ enum android_ime_operation ANDROID_IME_SET_POINT, ANDROID_IME_START_BATCH_EDIT, ANDROID_IME_END_BATCH_EDIT, + ANDROID_IME_REQUEST_SELECTION_UPDATE, }; struct android_ime_event diff --git a/src/androidterm.c b/src/androidterm.c index a6709ac8169..0cc2b35099c 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -630,6 +630,10 @@ android_handle_ime_event (union android_event *event, struct frame *f) case ANDROID_IME_END_BATCH_EDIT: end_batch_edit (f, event->ime.counter); break; + + case ANDROID_IME_REQUEST_SELECTION_UPDATE: + request_point_update (f, event->ime.counter); + break; } } @@ -5169,6 +5173,28 @@ NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object, return string; } +JNIEXPORT void JNICALL +NATIVE_NAME (requestSelectionUpdate) (JNIEnv *env, jobject object, + jshort window) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_REQUEST_SELECTION_UPDATE; + event.ime.start = 0; + event.ime.end = 0; + event.ime.length = 0; + event.ime.position = 0; + event.ime.text = NULL; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + #ifdef __clang__ #pragma clang diagnostic pop #else diff --git a/src/fileio.c b/src/fileio.c index 7bc9800aaf8..ae244b8f85a 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -113,6 +113,37 @@ along with GNU Emacs. If not, see . */ #include "commands.h" +#if !defined HAVE_ANDROID || defined ANDROID_STUBIFY + +/* Type describing a file descriptor used by functions such as + `insert-file-contents'. */ + +typedef int emacs_fd; + +/* Function used to read and open from such a file descriptor. */ + +#define emacs_fd_open emacs_open +#define emacs_fd_close emacs_close +#define emacs_fd_read emacs_read_quit +#define emacs_fd_lseek lseek +#define emacs_fd_fstat sys_fstat +#define emacs_fd_valid_p(fd) ((fd) >= 0) +#define emacs_fd_to_int(fds) (fds) + +#else /* HAVE_ANDROID && !defined ANDROID_STUBIFY */ + +typedef struct android_fd_or_asset emacs_fd; + +#define emacs_fd_open android_open_asset +#define emacs_fd_close android_close_asset +#define emacs_fd_read android_asset_read_quit +#define emacs_fd_lseek android_asset_lseek +#define emacs_fd_fstat android_asset_fstat +#define emacs_fd_valid_p(fd) ((fd).asset != ((void *) -1)) +#define emacs_fd_to_int(fds) ((fds).asset ? -1 : (fds).fd) + +#endif /* !defined HAVE_ANDROID || defined ANDROID_STUBIFY */ + /* True during writing of auto-save files. */ static bool auto_saving; @@ -299,6 +330,15 @@ close_file_unwind (int fd) emacs_close (fd); } +static void +close_file_unwind_emacs_fd (void *ptr) +{ + emacs_fd *fd; + + fd = ptr; + emacs_fd_close (*fd); +} + void fclose_unwind (void *arg) { @@ -2221,7 +2261,8 @@ permissions. */) #else bool already_exists = false; mode_t new_mask; - int ifd, ofd; + emacs_fd ifd; + int ofd; struct stat st; #endif @@ -2265,22 +2306,24 @@ permissions. */) report_file_error ("Copying permissions to", newname); } #else /* not WINDOWSNT */ - ifd = emacs_open (SSDATA (encoded_file), O_RDONLY | O_NONBLOCK, 0); + ifd = emacs_fd_open (SSDATA (encoded_file), O_RDONLY | O_NONBLOCK, 0); - if (ifd < 0) + if (!emacs_fd_valid_p (ifd)) report_file_error ("Opening input file", file); - record_unwind_protect_int (close_file_unwind, ifd); + record_unwind_protect_ptr (close_file_unwind_emacs_fd, &ifd); - if (sys_fstat (ifd, &st) != 0) + if (emacs_fd_fstat (ifd, &st) != 0) report_file_error ("Input file status", file); if (!NILP (preserve_permissions)) { #if HAVE_LIBSELINUX - if (is_selinux_enabled ()) + if (is_selinux_enabled () + && emacs_fd_to_int (ifd) != -1) { - conlength = fgetfilecon (ifd, &con); + conlength = fgetfilecon (emacs_fd_to_int (ifd), + &con); if (conlength == -1) report_file_error ("Doing fgetfilecon", file); } @@ -2329,7 +2372,8 @@ permissions. */) maybe_quit (); - if (clone_file (ofd, ifd)) + if (emacs_fd_to_int (ifd) != -1 + && clone_file (ofd, emacs_fd_to_int (ifd))) newsize = st.st_size; else { @@ -2337,30 +2381,38 @@ permissions. */) ssize_t copied; #ifndef MSDOS - for (newsize = 0; newsize < insize; newsize += copied) + newsize = 0; + + if (emacs_fd_to_int (ifd) != -1) { - /* Copy at most COPY_MAX bytes at a time; this is min - (PTRDIFF_MAX, SIZE_MAX) truncated to a value that is - surely aligned well. */ - ssize_t ssize_max = TYPE_MAXIMUM (ssize_t); - ptrdiff_t copy_max = min (ssize_max, SIZE_MAX) >> 30 << 30; - off_t intail = insize - newsize; - ptrdiff_t len = min (intail, copy_max); - copied = copy_file_range (ifd, NULL, ofd, NULL, len, 0); - if (copied <= 0) - break; - maybe_quit (); + for (; newsize < insize; newsize += copied) + { + /* Copy at most COPY_MAX bytes at a time; this is min + (PTRDIFF_MAX, SIZE_MAX) truncated to a value that is + surely aligned well. */ + ssize_t ssize_max = TYPE_MAXIMUM (ssize_t); + ptrdiff_t copy_max = min (ssize_max, SIZE_MAX) >> 30 << 30; + off_t intail = insize - newsize; + ptrdiff_t len = min (intail, copy_max); + copied = copy_file_range (emacs_fd_to_int (ifd), NULL, + ofd, NULL, len, 0); + if (copied <= 0) + break; + maybe_quit (); + } } #endif /* MSDOS */ /* Fall back on read+write if copy_file_range failed, or if the - input is empty and so could be a /proc file. read+write will - either succeed, or report an error more precisely than - copy_file_range would. */ + input is empty and so could be a /proc file, or if ifd is an + invention of android.c. read+write will either succeed, or + report an error more precisely than copy_file_range + would. */ if (newsize != insize || insize == 0) { char buf[MAX_ALLOCA]; - for (; (copied = emacs_read_quit (ifd, buf, sizeof buf)); + + for (; (copied = emacs_fd_read (ifd, buf, sizeof buf)); newsize += copied) { if (copied < 0) @@ -2408,8 +2460,10 @@ permissions. */) } } - switch (!NILP (preserve_permissions) - ? qcopy_acl (SSDATA (encoded_file), ifd, + switch ((!NILP (preserve_permissions) + && emacs_fd_to_int (ifd) != -1) + ? qcopy_acl (SSDATA (encoded_file), + emacs_fd_to_int (ifd), SSDATA (encoded_newname), ofd, preserved_permissions) : (already_exists @@ -2449,7 +2503,9 @@ permissions. */) if (emacs_close (ofd) < 0) report_file_error ("Write error", newname); - emacs_close (ifd); + /* Note that ifd is not closed twice because unwind_protects are + discarded at the end of this function. */ + emacs_fd_close (ifd); #ifdef MSDOS /* In DJGPP v2.0 and later, fstat usually returns true file mode bits, @@ -3826,7 +3882,7 @@ union read_non_regular { struct { - int fd; + emacs_fd fd; ptrdiff_t inserted, trytry; } s; GCALIGNED_UNION_MEMBER @@ -3837,10 +3893,10 @@ static Lisp_Object read_non_regular (Lisp_Object state) { union read_non_regular *data = XFIXNUMPTR (state); - int nbytes = emacs_read_quit (data->s.fd, - ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE - + data->s.inserted), - data->s.trytry); + int nbytes = emacs_fd_read (data->s.fd, + ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE + + data->s.inserted), + data->s.trytry); return make_fixnum (nbytes); } @@ -3989,7 +4045,7 @@ by calling `format-decode', which see. */) { struct stat st; struct timespec mtime; - int fd; + emacs_fd fd; ptrdiff_t inserted = 0; int unprocessed; specpdl_ref count = SPECPDL_INDEX (); @@ -4067,8 +4123,8 @@ by calling `format-decode', which see. */) orig_filename = filename; filename = ENCODE_FILE (filename); - fd = emacs_open (SSDATA (filename), O_RDONLY, 0); - if (fd < 0) + fd = emacs_fd_open (SSDATA (filename), O_RDONLY, 0); + if (!emacs_fd_valid_p (fd)) { save_errno = errno; if (NILP (visit)) @@ -4086,7 +4142,7 @@ by calling `format-decode', which see. */) } specpdl_ref fd_index = SPECPDL_INDEX (); - record_unwind_protect_int (close_file_unwind, fd); + record_unwind_protect_ptr (close_file_unwind_emacs_fd, &fd); /* Replacement should preserve point as it preserves markers. */ if (!NILP (replace)) @@ -4096,7 +4152,7 @@ by calling `format-decode', which see. */) XCAR (XCAR (window_markers))); } - if (sys_fstat (fd, &st) != 0) + if (emacs_fd_fstat (fd, &st) != 0) report_file_error ("Input file status", orig_filename); mtime = get_stat_mtime (&st); @@ -4117,7 +4173,7 @@ by calling `format-decode', which see. */) xsignal2 (Qfile_error, build_string ("not a regular file"), orig_filename); - seekable = lseek (fd, 0, SEEK_CUR) < 0; + seekable = emacs_fd_lseek (fd, 0, SEEK_CUR) < 0; if (!NILP (beg) && !seekable) xsignal2 (Qfile_error, build_string ("cannot use a start position in a non-seekable file/device"), @@ -4196,17 +4252,17 @@ by calling `format-decode', which see. */) int nread; if (st.st_size <= (1024 * 4)) - nread = emacs_read_quit (fd, read_buf, 1024 * 4); + nread = emacs_fd_read (fd, read_buf, 1024 * 4); else { - nread = emacs_read_quit (fd, read_buf, 1024); + nread = emacs_fd_read (fd, read_buf, 1024); if (nread == 1024) { int ntail; - if (lseek (fd, st.st_size - 1024 * 3, SEEK_CUR) < 0) + if (emacs_fd_lseek (fd, st.st_size - 1024 * 3, SEEK_CUR) < 0) report_file_error ("Setting file position", orig_filename); - ntail = emacs_read_quit (fd, read_buf + nread, 1024 * 3); + ntail = emacs_fd_read (fd, read_buf + nread, 1024 * 3); nread = ntail < 0 ? ntail : nread + ntail; } } @@ -4247,7 +4303,7 @@ by calling `format-decode', which see. */) specpdl_ptr--; /* Rewind the file for the actual read done later. */ - if (lseek (fd, 0, SEEK_SET) < 0) + if (emacs_fd_lseek (fd, 0, SEEK_SET) < 0) report_file_error ("Setting file position", orig_filename); } } @@ -4306,7 +4362,7 @@ by calling `format-decode', which see. */) if (beg_offset != 0) { - if (lseek (fd, beg_offset, SEEK_SET) < 0) + if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0) report_file_error ("Setting file position", orig_filename); } @@ -4314,7 +4370,7 @@ by calling `format-decode', which see. */) match the text at the beginning of the buffer. */ while (true) { - int nread = emacs_read_quit (fd, read_buf, sizeof read_buf); + int nread = emacs_fd_read (fd, read_buf, sizeof read_buf); if (nread < 0) report_file_error ("Read error", orig_filename); else if (nread == 0) @@ -4349,7 +4405,7 @@ by calling `format-decode', which see. */) there's no need to replace anything. */ if (same_at_start - BEGV_BYTE == end_offset - beg_offset) { - emacs_close (fd); + emacs_fd_close (fd); clear_unwind_protect (fd_index); /* Truncate the buffer to the size of the file. */ @@ -4372,14 +4428,14 @@ by calling `format-decode', which see. */) break; /* How much can we scan in the next step? */ trial = min (curpos, sizeof read_buf); - if (lseek (fd, curpos - trial, SEEK_SET) < 0) + if (emacs_fd_lseek (fd, curpos - trial, SEEK_SET) < 0) report_file_error ("Setting file position", orig_filename); total_read = nread = 0; while (total_read < trial) { - nread = emacs_read_quit (fd, read_buf + total_read, - trial - total_read); + nread = emacs_fd_read (fd, read_buf + total_read, + trial - total_read); if (nread < 0) report_file_error ("Read error", orig_filename); else if (nread == 0) @@ -4499,7 +4555,7 @@ by calling `format-decode', which see. */) /* First read the whole file, performing code conversion into CONVERSION_BUFFER. */ - if (lseek (fd, beg_offset, SEEK_SET) < 0) + if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0) report_file_error ("Setting file position", orig_filename); inserted = 0; /* Bytes put into CONVERSION_BUFFER so far. */ @@ -4510,8 +4566,8 @@ by calling `format-decode', which see. */) /* Read at most READ_BUF_SIZE bytes at a time, to allow quitting while reading a huge file. */ - this = emacs_read_quit (fd, read_buf + unprocessed, - READ_BUF_SIZE - unprocessed); + this = emacs_fd_read (fd, read_buf + unprocessed, + READ_BUF_SIZE - unprocessed); if (this <= 0) break; @@ -4526,7 +4582,7 @@ by calling `format-decode', which see. */) if (this < 0) report_file_error ("Read error", orig_filename); - emacs_close (fd); + emacs_fd_close (fd); clear_unwind_protect (fd_index); if (unprocessed > 0) @@ -4673,7 +4729,7 @@ by calling `format-decode', which see. */) if (beg_offset != 0 || !NILP (replace)) { - if (lseek (fd, beg_offset, SEEK_SET) < 0) + if (emacs_fd_lseek (fd, beg_offset, SEEK_SET) < 0) report_file_error ("Setting file position", orig_filename); } @@ -4726,10 +4782,10 @@ by calling `format-decode', which see. */) /* Allow quitting out of the actual I/O. We don't make text part of the buffer until all the reading is done, so a C-g here doesn't do any harm. */ - this = emacs_read_quit (fd, - ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE - + inserted), - trytry); + this = emacs_fd_read (fd, + ((char *) BEG_ADDR + PT_BYTE - BEG_BYTE + + inserted), + trytry); } if (this <= 0) @@ -4756,7 +4812,7 @@ by calling `format-decode', which see. */) else Fset (Qdeactivate_mark, Qt); - emacs_close (fd); + emacs_fd_close (fd); clear_unwind_protect (fd_index); if (read_quit < 0) diff --git a/src/frame.h b/src/frame.h index ca4cca17d74..e2900d1c15b 100644 --- a/src/frame.h +++ b/src/frame.h @@ -88,6 +88,7 @@ enum text_conversion_operation TEXTCONV_SET_COMPOSING_REGION, TEXTCONV_SET_POINT_AND_MARK, TEXTCONV_DELETE_SURROUNDING_TEXT, + TEXTCONV_REQUEST_POINT_UPDATE, }; /* Structure describing a single edit being performed by the input diff --git a/src/textconv.c b/src/textconv.c index 91d386d4c61..c1ce83b1d7d 100644 --- a/src/textconv.c +++ b/src/textconv.c @@ -957,6 +957,26 @@ really_delete_surrounding_text (struct frame *f, ptrdiff_t left, unbind_to (count, Qnil); } +/* Update the interface with F's new point and mark. If a batch edit + is in progress, schedule the update for when it finishes + instead. */ + +static void +really_request_point_update (struct frame *f) +{ + /* If F's old selected window is no longer live, fail. */ + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return; + + if (f->conversion.batch_edit_count > 0) + f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE; + else if (text_interface && text_interface->point_changed) + text_interface->point_changed (f, + XWINDOW (f->old_selected_window), + current_buffer); +} + /* Set point in F to POSITION. If MARK is not POSITION, activate the mark and set MARK to that as well. @@ -1163,6 +1183,10 @@ handle_pending_conversion_events_1 (struct frame *f, really_delete_surrounding_text (f, XFIXNUM (XCAR (data)), XFIXNUM (XCDR (data))); break; + + case TEXTCONV_REQUEST_POINT_UPDATE: + really_request_point_update (f); + break; } /* Signal success. */ @@ -1457,6 +1481,25 @@ delete_surrounding_text (struct frame *f, ptrdiff_t left, input_pending = true; } +/* Request an immediate call to INTERFACE->point_changed with the new + details of F's region unless a batch edit is in progress. */ + +void +request_point_update (struct frame *f, unsigned long counter) +{ + struct text_conversion_action *action, **last; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_REQUEST_POINT_UPDATE; + action->data = Qnil; + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + /* Return N characters of text around point in F's old selected window. diff --git a/src/textconv.h b/src/textconv.h index 16d13deb092..9bc9e7d9bd1 100644 --- a/src/textconv.h +++ b/src/textconv.h @@ -138,6 +138,7 @@ extern void textconv_set_point_and_mark (struct frame *, ptrdiff_t, ptrdiff_t, unsigned long); extern void delete_surrounding_text (struct frame *, ptrdiff_t, ptrdiff_t, unsigned long); +extern void request_point_update (struct frame *, unsigned long); extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *); extern bool conversion_disabled_p (void); -- cgit v1.3 From bb55528c7b58c5f50336ed3f2ff9759559d78680 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 8 Mar 2023 15:04:49 +0800 Subject: Update Android port * doc/emacs/android.texi (Android File System): Document what `temp~unlinked' means in the temporary files directory. * java/org/gnu/emacs/EmacsService.java (updateExtractedText): New function. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Ask the input method nicely to not display the extracted text UI. * src/android.c (struct android_emacs_service): New method `updateExtractedText'. (android_hack_asset_fd_fallback): Improve naming convention. Fix typo. (android_init_emacs_service): Add new method. (android_update_extracted_text): New function. (android_open_asset): Fix typo. * src/androidgui.h: Update prototypes. * src/androidterm.c (struct android_get_extracted_text_context): New field `flags'. (android_get_extracted_text): Set flags on the frame's output data. (android_build_extracted_text): New function. (getExtractedText): Move out class structures. (android_update_selection): Send updates to extracted text if the input method asked for them. (android_reset_conversion): Clear extracted text flags. * src/androidterm.h (struct android_output): New fields for storing extracted text data. --- doc/emacs/android.texi | 7 +++ java/org/gnu/emacs/EmacsService.java | 12 ++++ java/org/gnu/emacs/EmacsView.java | 1 + src/android.c | 44 ++++++++++++-- src/androidgui.h | 2 + src/androidterm.c | 111 ++++++++++++++++++++++++++++++++--- src/androidterm.h | 10 ++++ 7 files changed, 175 insertions(+), 12 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index d49e0754b0a..8e98b92314a 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -205,6 +205,13 @@ other directories are not found at any fixed location, although the app data directory is typically symlinked to @file{/data/data/org.gnu.emacs}. +@cindex temp~unlinked.NNNN files, Android + On Android devices running very old (2.6.29) versions of the Linux +kernel, Emacs needs to create files named starting with +@file{temp~unlinked} in the the temporary file directory in order to +read from asset files. Do not create files with such names yourself, +or they may be overwritten or removed. + @cindex file system limitations, Android 11 On Android 11 and later, the Android system restricts applications from accessing files in the @file{/sdcard} directory using diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index f99d7a40067..848ad4de789 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.InputDevice; import android.view.KeyEvent; +import android.view.inputmethod.ExtractedText; import android.app.Notification; import android.app.NotificationManager; @@ -811,4 +812,15 @@ public final class EmacsService extends Service } }); } + + public void + updateExtractedText (EmacsWindow window, ExtractedText text, + int token) + { + if (DEBUG_IC) + Log.d (TAG, "updateExtractedText: @" + token + ", " + text); + + window.view.imManager.updateExtractedText (window.view, + token, text); + } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 90a2c912a5a..6ace609f386 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -569,6 +569,7 @@ public final class EmacsView extends ViewGroup /* Make sure the input method never displays a full screen input box that obscures Emacs. */ info.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; + info.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI; /* Set a reasonable inputType. */ info.inputType = InputType.TYPE_CLASS_TEXT; diff --git a/src/android.c b/src/android.c index 11b0fa5e0f3..e620a041348 100644 --- a/src/android.c +++ b/src/android.c @@ -112,6 +112,7 @@ struct android_emacs_service jmethodID check_content_uri; jmethodID query_battery; jmethodID display_toast; + jmethodID update_extracted_text; }; struct android_emacs_pixmap @@ -1236,13 +1237,12 @@ android_hack_asset_fd_fallback (AAsset *asset) Creating an ashmem file descriptor and reading from it doesn't work on these old Android versions. */ - snprintf (filename, PATH_MAX, "%s/%s.%d", - android_cache_dir, "temp-unlinked", - getpid ()); + snprintf (filename, PATH_MAX, "%s/temp~unlinked.%d", + android_cache_dir, getpid ()); fd = open (filename, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd < 1) + if (fd < 0) return -1; if (unlink (filename)) @@ -2135,6 +2135,9 @@ android_init_emacs_service (void) FIND_METHOD (query_battery, "queryBattery", "()[J"); FIND_METHOD (display_toast, "displayToast", "(Ljava/lang/String;)V"); + FIND_METHOD (update_extracted_text, "updateExtractedText", + "(Lorg/gnu/emacs/EmacsWindow;" + "Landroid/view/inputmethod/ExtractedText;I)V"); #undef FIND_METHOD } @@ -5991,6 +5994,37 @@ android_reset_ic (android_window window, enum android_ic_mode mode) android_exception_check (); } +/* Make updates to extracted text known to the input method on + WINDOW. TEXT should be a local reference to the new + extracted text. TOKEN should be the token specified by the + input method. */ + +void +android_update_extracted_text (android_window window, void *text, + int token) +{ + jobject object; + jmethodID method; + + object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW); + method = service_class.update_extracted_text; + + (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, + emacs_service, + service_class.class, + method, object, + /* N.B. that + text is not + jobject, + because that + type is not + available in + androidgui.h. */ + (jobject) text, + (jint) token); + android_exception_check_1 (text); +} + /* Window decoration management functions. */ @@ -6083,7 +6117,7 @@ android_open_asset (const char *filename, int oflag, mode_t mode) get a regular file descriptor. */ fd.fd = android_open (filename, oflag, mode); - if (fd.fd < 1) + if (fd.fd < 0) return fd; /* Set fd.asset to NULL, signifying that it is a file diff --git a/src/androidgui.h b/src/androidgui.h index e1c80a71a59..afcaed98cae 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -613,6 +613,8 @@ 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 void android_update_extracted_text (android_window, void *, + int); extern int android_set_fullscreen (android_window, bool); #endif diff --git a/src/androidterm.c b/src/androidterm.c index 0cc2b35099c..f4a535292f2 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -4968,6 +4968,10 @@ NATIVE_NAME (performEditorAction) (JNIEnv *env, jobject object, android_write_event (&event); } + + +/* Text extraction. */ + struct android_get_extracted_text_context { /* The parameters of the request. */ @@ -4976,6 +4980,9 @@ struct android_get_extracted_text_context /* Token for the request. */ int token; + /* Flags associated with the request. */ + int flags; + /* The returned text, or NULL. */ char *text; @@ -5011,6 +5018,14 @@ android_get_extracted_text (void *data) = get_extracted_text (f, min (request->hint_max_chars, 600), &request->start, &request->offset, &request->length, &request->bytes); + + /* See if request->flags & GET_EXTRACTED_TEXT_MONITOR. If so, then + the input method has asked to monitor changes to the extracted + text until the next IM context reset. */ + + FRAME_ANDROID_OUTPUT (f)->extracted_text_flags = request->flags; + FRAME_ANDROID_OUTPUT (f)->extracted_text_token = request->token; + FRAME_ANDROID_OUTPUT (f)->extracted_text_hint = request->hint_max_chars; } /* Structure describing the `ExtractedTextRequest' class. @@ -5038,6 +5053,51 @@ struct android_extracted_text_class jfieldID text; }; +/* Fields and methods associated with the `ExtractedTextRequest' + class. */ +struct android_extracted_text_request_class request_class; + +/* Fields and methods associated with the `ExtractedText' class. */ +struct android_extracted_text_class text_class; + +/* Return an ExtractedText object corresponding to the extracted text + TEXT. START is a character position describing the offset of the + first character in TEXT. OFFSET is the offset of point relative to + START. + + Assume that request_class and text_class have already been + initialized. + + Value is NULL if an error occurs; the exception is not cleared, + else a local reference to the ExtractedText object. */ + +static jobject +android_build_extracted_text (jstring text, ptrdiff_t start, + ptrdiff_t offset) +{ + JNIEnv *env; + jobject object; + + env = android_java_env; + + /* Create an ExtractedText object containing this information. */ + object = (*env)->NewObject (env, text_class.class, + text_class.constructor); + if (!object) + return NULL; + + (*env)->SetIntField (env, object, text_class.partial_start_offset, -1); + (*env)->SetIntField (env, object, text_class.partial_end_offset, -1); + (*env)->SetIntField (env, object, text_class.selection_start, + min (offset, TYPE_MAXIMUM (jint))); + (*env)->SetIntField (env, object, text_class.selection_end, + min (offset, TYPE_MAXIMUM (jint))); + (*env)->SetIntField (env, object, text_class.start_offset, + min (start, TYPE_MAXIMUM (jint))); + (*env)->SetObjectField (env, object, text_class.text, text); + return object; +} + JNIEXPORT jobject JNICALL NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object, jshort window, jobject request, @@ -5046,14 +5106,10 @@ NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object, JNI_STACK_ALIGNMENT_PROLOGUE; struct android_get_extracted_text_context context; - static struct android_extracted_text_request_class request_class; - static struct android_extracted_text_class text_class; jstring string; jclass class; jobject object; - /* TODO: report changes to extracted text. */ - /* Initialize both classes if necessary. */ if (!request_class.initialized) @@ -5106,6 +5162,7 @@ NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object, = (*env)->GetIntField (env, request, request_class.hint_max_chars); context.token = (*env)->GetIntField (env, request, request_class.token); + context.flags = flags; context.text = NULL; context.window = window; @@ -5126,8 +5183,8 @@ NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object, return NULL; /* Create an ExtractedText object containing this information. */ - object = (*android_java_env)->NewObject (env, text_class.class, - text_class.constructor); + object = (*env)->NewObject (env, text_class.class, + text_class.constructor); if (!object) return NULL; @@ -5143,6 +5200,8 @@ NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object, return object; } + + JNIEXPORT jstring JNICALL NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object, jshort window) @@ -5210,8 +5269,12 @@ NATIVE_NAME (requestSelectionUpdate) (JNIEnv *env, jobject object, static void android_update_selection (struct frame *f, struct window *w) { - ptrdiff_t start, end, point, mark; + ptrdiff_t start, end, point, mark, offset, length, bytes; struct buffer *b; + int hint, token; + char *text; + jobject extracted; + jstring string; if (MARKERP (f->conversion.compose_region_start)) { @@ -5246,6 +5309,36 @@ android_update_selection (struct frame *f, struct window *w) the selection is less than or equal to the end. */ android_update_ic (FRAME_ANDROID_WINDOW (f), min (point, mark), max (point, mark), start, end); + + /* Update the extracted text as well, if the input method has asked + for updates. 1 is + InputConnection.GET_EXTRACTED_TEXT_MONITOR. */ + + if (FRAME_ANDROID_OUTPUT (f)->extracted_text_flags & 1) + { + hint = FRAME_ANDROID_OUTPUT (f)->extracted_text_hint; + token = FRAME_ANDROID_OUTPUT (f)->extracted_text_token; + text = get_extracted_text (f, min (hint, 600), &start, + &offset, &length, &bytes); + + /* Make a string out of the extracted text. */ + string = android_text_to_string (android_java_env, + text, length, bytes); + xfree (text); + android_exception_check (); + + /* Make extracted text out of that string. */ + extracted = android_build_extracted_text (string, start, + offset); + android_exception_check_1 (string); + ANDROID_DELETE_LOCAL_REF (string); + + /* extracted is now an associated ExtractedText object. Perform + the update. */ + android_update_extracted_text (FRAME_ANDROID_WINDOW (f), + extracted, token); + ANDROID_DELETE_LOCAL_REF (extracted); + } } /* Notice that the input method connection to F should be reset as a @@ -5283,6 +5376,10 @@ android_reset_conversion (struct frame *f) android_reset_ic (FRAME_ANDROID_WINDOW (f), mode); + /* Clear extracted text flags. Since the IM has been reinitialised, + it should no longer be displaying extracted text. */ + FRAME_ANDROID_OUTPUT (f)->extracted_text_flags = 0; + /* Move its selection to the specified position. */ android_update_selection (f, NULL); } diff --git a/src/androidterm.h b/src/androidterm.h index ac845187a66..9bd11bb7853 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -241,6 +241,16 @@ struct android_output /* List of all tools (either styluses or fingers) pressed onto the frame. */ struct android_touch_point *touch_points; + + /* Flags associated with the last request to obtain ``extracted + text''. */ + int extracted_text_flags; + + /* Token asssociated with that request. */ + int extracted_text_token; + + /* The number of characters of extracted text wanted by the IM. */ + int extracted_text_hint; }; enum -- 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/EmacsView.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 da660a1ffa3218f8e6ec4dfd5422ca6c1ded38ae Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 17 Mar 2023 10:38:09 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsView.java (cancelPopupMenu): Dismiss context menu correctly. (isOpaque): New function. * java/org/gnu/emacs/EmacsWindowAttachmentManager.java: Make consumer list public. --- java/org/gnu/emacs/EmacsView.java | 21 +++++++++++++++++++++ .../org/gnu/emacs/EmacsWindowAttachmentManager.java | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 878ef2f3fbf..681da98fa16 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -514,6 +514,17 @@ public final class EmacsView extends ViewGroup contextMenu = null; popupActive = false; + + /* It is not possible to know with 100% certainty which activity + is currently displaying the context menu. Loop through each + activity and call `closeContextMenu' instead. */ + + for (EmacsWindowAttachmentManager.WindowConsumer consumer + : EmacsWindowAttachmentManager.MANAGER.consumers) + { + if (consumer instanceof EmacsActivity) + ((EmacsActivity) consumer).closeContextMenu (); + } } @Override @@ -646,6 +657,16 @@ public final class EmacsView extends ViewGroup return isCurrentlyTextEditor; } + @Override + public boolean + isOpaque () + { + /* Returning true here allows the system to not draw the contents + of windows underneath this view, thereby improving + performance. */ + return true; + } + public synchronized void setICMode (int icMode) { diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java index 30f29250970..c0197ab802c 100644 --- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java +++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java @@ -67,7 +67,7 @@ public final class EmacsWindowAttachmentManager public void destroy (); }; - private List consumers; + public List consumers; public List windows; public -- 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/EmacsView.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 f3dd887d1848759baf0d91264882509159bb04fc Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 18 Mar 2023 20:05:38 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsView.java (onAttachedToWindow): Send measured width and height in exposures again. --- java/org/gnu/emacs/EmacsView.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 88d17a255a2..10c1af9e19a 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -574,7 +574,8 @@ public final class EmacsView extends ViewGroup bitmapDirty = true; /* Now expose the view contents again. */ - EmacsNative.sendExpose (this.window.handle, 0, 0, 0, 0); + EmacsNative.sendExpose (this.window.handle, 0, 0, + measuredWidth, measuredHeight); super.onAttachedToWindow (); } -- cgit v1.3 From 181453285c67783ebf8eb269dc19fdb0e563af62 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 20 May 2023 10:26:28 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsView.java (swapBuffers): Restore missing damage rect code. (onDetachedFromWindow): Remove redundant synchronization. --- java/org/gnu/emacs/EmacsView.java | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 10c1af9e19a..124ea5301bb 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -356,16 +356,23 @@ public final class EmacsView extends ViewGroup damageRect = null; + /* Now see if there is a damage region. */ + synchronized (damageRegion) { if (damageRegion.isEmpty ()) return; + /* And extract and clear the damage region. */ + + damageRect = damageRegion.getBounds (); + damageRegion.setEmpty (); + bitmap = getBitmap (); /* Transfer the bitmap to the surface view, then invalidate it. */ - surfaceView.setBitmap (bitmap, damageRect); + surfaceView.setBitmap (bitmap, damageRect); } } @@ -545,20 +552,17 @@ public final class EmacsView extends ViewGroup { isAttachedToWindow = false; - synchronized (this) - { - /* Recycle the bitmap and call GC. */ + /* Recycle the bitmap and call GC. */ - if (bitmap != null) - bitmap.recycle (); + if (bitmap != null) + bitmap.recycle (); - bitmap = null; - canvas = null; - surfaceView.setBitmap (null, null); + bitmap = null; + canvas = null; + surfaceView.setBitmap (null, null); - /* Collect the bitmap storage; it could be large. */ - Runtime.getRuntime ().gc (); - } + /* Collect the bitmap storage; it could be large. */ + Runtime.getRuntime ().gc (); super.onDetachedFromWindow (); } -- cgit v1.3 From d33bf0a0afddb76598aa020f68402d0e19cfb65c Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 27 May 2023 15:19:02 +0800 Subject: Remove synchronization around `damageRegion' * java/org/gnu/emacs/EmacsView.java (EmacsView, swapBuffers): Remove unnecessary documentation. `damageRegion' is only changed from the Emacs thread. --- java/org/gnu/emacs/EmacsView.java | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 124ea5301bb..eb1d88ae242 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -338,10 +338,7 @@ public final class EmacsView extends ViewGroup public void damageRect (Rect damageRect) { - synchronized (damageRegion) - { - damageRegion.union (damageRect); - } + damageRegion.union (damageRect); } /* This method is called from both the UI thread and the Emacs @@ -358,22 +355,19 @@ public final class EmacsView extends ViewGroup /* Now see if there is a damage region. */ - synchronized (damageRegion) - { - if (damageRegion.isEmpty ()) - return; + if (damageRegion.isEmpty ()) + return; - /* And extract and clear the damage region. */ + /* And extract and clear the damage region. */ - damageRect = damageRegion.getBounds (); - damageRegion.setEmpty (); + damageRect = damageRegion.getBounds (); + damageRegion.setEmpty (); - bitmap = getBitmap (); + bitmap = getBitmap (); - /* Transfer the bitmap to the surface view, then invalidate - it. */ - surfaceView.setBitmap (bitmap, damageRect); - } + /* Transfer the bitmap to the surface view, then invalidate + it. */ + surfaceView.setBitmap (bitmap, damageRect); } @Override -- cgit v1.3 From 327d2d013130ec745880b44573dcda3a6620faba Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 27 May 2023 16:30:12 +0800 Subject: Add extra thread-related checking * java/org/gnu/emacs/EmacsService.java (EmacsService) (checkEmacsThread): New function. (fillPolygon, drawRectangle, drawLine, drawPoint, copyArea) (clearArea): * java/org/gnu/emacs/EmacsThread.java (EmacsThread): * java/org/gnu/emacs/EmacsView.java (EmacsView, swapBuffers): Call where appropriate. --- java/org/gnu/emacs/EmacsService.java | 30 ++++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsThread.java | 2 +- java/org/gnu/emacs/EmacsView.java | 5 +++++ 3 files changed, 36 insertions(+), 1 deletion(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index bb17d27bcf8..d2e52ed5e62 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -100,6 +100,10 @@ public final class EmacsService extends Service information. */ public static final boolean DEBUG_IC = false; + /* Flag that says whether or not to perform extra checks on threads + performing drawing calls. */ + private static final boolean DEBUG_THREADS = false; + /* Return the directory leading to the directory in which native library files are stored on behalf of CONTEXT. */ @@ -309,10 +313,29 @@ public final class EmacsService extends Service syncRunnable (runnable); } + + + public static void + checkEmacsThread () + { + if (DEBUG_THREADS) + { + if (Thread.currentThread () instanceof EmacsThread) + return; + + throw new RuntimeException ("Emacs thread function" + + " called from other thread!"); + } + } + + /* These drawing functions must only be called from the Emacs + thread. */ + public void fillRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { + checkEmacsThread (); EmacsFillRectangle.perform (drawable, gc, x, y, width, height); } @@ -321,6 +344,7 @@ public final class EmacsService extends Service fillPolygon (EmacsDrawable drawable, EmacsGC gc, Point points[]) { + checkEmacsThread (); EmacsFillPolygon.perform (drawable, gc, points); } @@ -328,6 +352,7 @@ public final class EmacsService extends Service drawRectangle (EmacsDrawable drawable, EmacsGC gc, int x, int y, int width, int height) { + checkEmacsThread (); EmacsDrawRectangle.perform (drawable, gc, x, y, width, height); } @@ -336,6 +361,7 @@ public final class EmacsService extends Service drawLine (EmacsDrawable drawable, EmacsGC gc, int x, int y, int x2, int y2) { + checkEmacsThread (); EmacsDrawLine.perform (drawable, gc, x, y, x2, y2); } @@ -344,6 +370,7 @@ public final class EmacsService extends Service drawPoint (EmacsDrawable drawable, EmacsGC gc, int x, int y) { + checkEmacsThread (); EmacsDrawPoint.perform (drawable, gc, x, y); } @@ -353,6 +380,7 @@ public final class EmacsService extends Service int srcX, int srcY, int width, int height, int destX, int destY) { + checkEmacsThread (); EmacsCopyArea.perform (srcDrawable, gc, dstDrawable, srcX, srcY, width, height, destX, destY); @@ -361,6 +389,7 @@ public final class EmacsService extends Service public void clearWindow (EmacsWindow window) { + checkEmacsThread (); window.clearWindow (); } @@ -368,6 +397,7 @@ public final class EmacsService extends Service clearArea (EmacsWindow window, int x, int y, int width, int height) { + checkEmacsThread (); window.clearArea (x, y, width, height); } diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java index 468c6530af0..1343b70bf5a 100644 --- a/java/org/gnu/emacs/EmacsThread.java +++ b/java/org/gnu/emacs/EmacsThread.java @@ -25,7 +25,7 @@ import java.util.Arrays; import android.os.Build; import android.util.Log; -public class EmacsThread extends Thread +public final class EmacsThread extends Thread { private static final String TAG = "EmacsThread"; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index eb1d88ae242..09bc9d719d3 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -338,6 +338,7 @@ public final class EmacsView extends ViewGroup public void damageRect (Rect damageRect) { + EmacsService.checkEmacsThread (); damageRegion.union (damageRect); } @@ -351,6 +352,10 @@ public final class EmacsView extends ViewGroup Rect damageRect; Bitmap bitmap; + /* Make sure this function is called only from the Emacs + thread. */ + EmacsService.checkEmacsThread (); + damageRect = null; /* Now see if there is a damage region. */ -- cgit v1.3 From 57903519eb61632c4a85fbaf420109892955079a Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 31 May 2023 10:13:04 +0800 Subject: Update Android port * java/debug.sh (is_root): Go back to using unix sockets; allow adb to forward them correctly. * java/org/gnu/emacs/EmacsInputConnection.java (getExtractedText): Don't print text if NULL. * java/org/gnu/emacs/EmacsService.java (EmacsService): New field `imSyncInProgress'. (updateIC): If an IM sync might be in progress, avoid deadlocks. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Set `imSyncInProgress' across synchronization point. * src/android.c (android_check_query): Use __atomic_store_n. (android_answer_query): New function. (android_begin_query): Set `android_servicing_query' to 2. Check once, and don't spin waiting for query to complete. (android_end_query): Use __atomic_store_n. (android_run_in_emacs_thread): Compare-and-exchange flag. If originally 1, fail. * src/textconv.c (really_set_composing_text): Clear conversion region if text is empty. --- java/debug.sh | 17 +++--- java/org/gnu/emacs/EmacsInputConnection.java | 8 +++ java/org/gnu/emacs/EmacsService.java | 28 ++++++++++ java/org/gnu/emacs/EmacsView.java | 8 ++- src/android.c | 81 ++++++++++++++++++++++------ src/textconv.c | 6 +++ 6 files changed, 126 insertions(+), 22 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/debug.sh b/java/debug.sh index 339b3604810..0458003fe72 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -19,6 +19,7 @@ ## along with GNU Emacs. If not, see . set -m +set -x oldpwd=`pwd` cd `dirname $0` @@ -310,22 +311,26 @@ rm -f /tmp/file-descriptor-stamp if [ -z "$gdbserver" ]; then if [ "$is_root" = "yes" ]; then - adb -s $device shell $gdbserver_bin --once \ + adb -s $device shell $gdbserver_bin --multi \ "+/data/local/tmp/debug.$package.socket" --attach $pid >&5 & gdb_socket="localfilesystem:/data/local/tmp/debug.$package.socket" - else - adb -s $device shell run-as $package $gdbserver_bin --once \ + else + adb -s $device shell run-as $package $gdbserver_bin --multi \ "+debug.$package.socket" --attach $pid >&5 & gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket" fi else # Normally the program cannot access $gdbserver_bin when it is # placed in /data/local/tmp. - adb -s $device shell run-as $package $gdbserver_cmd --once \ - "0.0.0.0:7654" --attach $pid >&5 & - gdb_socket="tcp:7654" + adb -s $device shell run-as $package $gdbserver_cmd --multi \ + "+debug.$package.socket" --attach $pid >&5 & + gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket" fi +# In order to allow adb to forward to the gdbserver socket, make the +# app data directory a+x. +adb -s $device shell run-as $package chmod a+x $app_data_dir + # Wait until gdbserver successfully runs. line= while read -u 5 line; do diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 21bbaca5d07..420da58c0f8 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -286,6 +286,14 @@ public final class EmacsInputConnection extends BaseInputConnection text = EmacsNative.getExtractedText (windowHandle, request, flags); + if (text == null) + { + if (EmacsService.DEBUG_IC) + Log.d (TAG, "getExtractedText: text is NULL"); + + return null; + } + if (EmacsService.DEBUG_IC) Log.d (TAG, "getExtractedText: " + text.text + " @" + text.startOffset + ":" + text.selectionStart diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 546d22627c5..2f35933a7d1 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -104,6 +104,9 @@ public final class EmacsService extends Service performing drawing calls. */ private static final boolean DEBUG_THREADS = false; + /* Whether or not onCreateInputMethod is calling getSelection. */ + public static volatile boolean imSyncInProgress; + /* Return the directory leading to the directory in which native library files are stored on behalf of CONTEXT. */ @@ -636,16 +639,41 @@ public final class EmacsService extends Service int newSelectionEnd, int composingRegionStart, int composingRegionEnd) { + boolean wasSynchronous; + if (DEBUG_IC) Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart + " " + newSelectionEnd + " " + composingRegionStart + " " + composingRegionEnd)); + + /* `updateSelection' holds an internal lock that is also taken + before `onCreateInputConnection' (in EmacsView.java) is called; + when that then asks the UI thread for the current selection, a + dead lock results. To remedy this, reply to any synchronous + queries now -- and prohibit more queries for the duration of + `updateSelection' -- if EmacsView may have been asking for the + value of the region. */ + + wasSynchronous = false; + if (EmacsService.imSyncInProgress) + { + /* `beginSynchronous' will answer any outstanding queries and + signal that one is now in progress, thereby preventing + `getSelection' from blocking. */ + + EmacsNative.beginSynchronous (); + wasSynchronous = true; + } + window.view.imManager.updateSelection (window.view, newSelectionStart, newSelectionEnd, composingRegionStart, composingRegionEnd); + + if (wasSynchronous) + EmacsNative.endSynchronous (); } public void diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 09bc9d719d3..bb450bb8e6b 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -628,8 +628,14 @@ public final class EmacsView extends ViewGroup } /* Obtain the current position of point and set it as the - selection. */ + selection. Don't do this under one specific situation: if + `android_update_ic' is being called in the main thread, trying + to synchronize with it can cause a dead lock in the IM + manager. */ + + EmacsService.imSyncInProgress = true; selection = EmacsNative.getSelection (window.handle); + EmacsService.imSyncInProgress = false; if (selection != null) Log.d (TAG, "onCreateInputConnection: current selection is: " diff --git a/src/android.c b/src/android.c index 8cc18787358..9d1399f3fc2 100644 --- a/src/android.c +++ b/src/android.c @@ -6959,8 +6959,11 @@ android_display_toast (const char *text) -/* Whether or not a query is currently being made. */ -static bool android_servicing_query; +/* The thread from which a query against a thread is currently being + made, if any. Value is 0 if no query is in progress, 1 if a query + is being made from the UI thread to the main thread, and 2 if a + query is being made the other way around. */ +static char android_servicing_query; /* Function that is waiting to be run in the Emacs thread. */ static void (*android_query_function) (void *); @@ -7010,7 +7013,37 @@ android_check_query (void) /* Finish the query. */ __atomic_store_n (&android_query_context, NULL, __ATOMIC_SEQ_CST); __atomic_store_n (&android_query_function, NULL, __ATOMIC_SEQ_CST); - __atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST); + __atomic_store_n (&android_servicing_query, 0, __ATOMIC_SEQ_CST); + + /* Signal completion. */ + sem_post (&android_query_sem); +} + +/* Run the function that the UI thread has asked to run, and then + signal its completion. Do not change `android_servicing_query' + after it completes. */ + +static void +android_answer_query (void) +{ + void (*proc) (void *); + void *closure; + + eassert (__atomic_load_n (&android_servicing_query, __ATOMIC_SEQ_CST) + == 1); + + /* First, load the procedure and closure. */ + __atomic_load (&android_query_context, &closure, __ATOMIC_SEQ_CST); + __atomic_load (&android_query_function, &proc, __ATOMIC_SEQ_CST); + + if (!proc) + return; + + proc (closure); + + /* Finish the query. */ + __atomic_store_n (&android_query_context, NULL, __ATOMIC_SEQ_CST); + __atomic_store_n (&android_query_function, NULL, __ATOMIC_SEQ_CST); /* Signal completion. */ sem_post (&android_query_sem); @@ -7025,18 +7058,23 @@ android_check_query (void) static void android_begin_query (void) { - if (__atomic_test_and_set (&android_servicing_query, - __ATOMIC_SEQ_CST)) + char old; + + /* Load the previous value of `android_servicing_query' and upgrade + it to 2. */ + + old = __atomic_exchange_n (&android_servicing_query, + 2, __ATOMIC_SEQ_CST); + + /* See if a query was previously in progress. */ + if (old == 1) { /* Answer the query that is currently being made. */ assert (android_query_function != NULL); - android_check_query (); - - /* Wait for that query to complete. */ - while (__atomic_load_n (&android_servicing_query, - __ATOMIC_SEQ_CST)) - ;; + android_answer_query (); } + + /* `android_servicing_query' is now 2. */ } /* Notice that a query has stopped. This function may be called from @@ -7045,7 +7083,7 @@ android_begin_query (void) static void android_end_query (void) { - __atomic_clear (&android_servicing_query, __ATOMIC_SEQ_CST); + __atomic_store_n (&android_servicing_query, 0, __ATOMIC_SEQ_CST); } /* Synchronously ask the Emacs thread to run the specified PROC with @@ -7063,6 +7101,7 @@ int android_run_in_emacs_thread (void (*proc) (void *), void *closure) { union android_event event; + char old; event.xaction.type = ANDROID_WINDOW_ACTION; event.xaction.serial = ++event_serial; @@ -7074,10 +7113,13 @@ android_run_in_emacs_thread (void (*proc) (void *), void *closure) __atomic_store_n (&android_query_function, proc, __ATOMIC_SEQ_CST); /* Don't allow deadlocks to happen; make sure the Emacs thread is - not waiting for something to be done. */ + not waiting for something to be done (in that case, + `android_query_context' is 2.) */ - if (__atomic_test_and_set (&android_servicing_query, - __ATOMIC_SEQ_CST)) + old = 0; + if (!__atomic_compare_exchange_n (&android_servicing_query, &old, + 1, false, __ATOMIC_SEQ_CST, + __ATOMIC_SEQ_CST)) { __atomic_store_n (&android_query_context, NULL, __ATOMIC_SEQ_CST); @@ -7098,6 +7140,15 @@ android_run_in_emacs_thread (void (*proc) (void *), void *closure) while (sem_wait (&android_query_sem) < 0) ;; + /* At this point, `android_servicing_query' should either be zero if + the query was answered or two if the main thread has started a + query. */ + + eassert (!__atomic_load_n (&android_servicing_query, + __ATOMIC_SEQ_CST) + || (__atomic_load_n (&android_servicing_query, + __ATOMIC_SEQ_CST) == 2)); + return 0; } diff --git a/src/textconv.c b/src/textconv.c index 26f351dc729..1530cc0ce32 100644 --- a/src/textconv.c +++ b/src/textconv.c @@ -792,6 +792,12 @@ really_set_composing_text (struct frame *f, ptrdiff_t position, /* Move the composition overlay. */ sync_overlay (f); + /* If TEXT is empty, remove the composing region. This goes against + the documentation, but is ultimately what programs expect. */ + + if (!SCHARS (text)) + really_finish_composing_text (f); + /* If PT hasn't changed, the conversion region definitely has. Otherwise, redisplay will update the input method instead. */ -- cgit v1.3 From 740af4668c8d9bc8e4ee1e60ebeb366690fee93e Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sun, 4 Jun 2023 12:04:15 +0800 Subject: Fix input method synchronization problems * java/debug.sh (gdbserver_cmd, is_root): Prefer TCP again. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function `queryAndSpin'. * java/org/gnu/emacs/EmacsService.java (EmacsService) (icBeginSynchronous, icEndSynchronous, viewGetSelection): New synchronization functions. (resetIC, updateCursorAnchorInfo): Call those instead. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Call viewGetSelection. * src/android.c (JNICALL, android_answer_query_spin): New functions. --- java/debug.sh | 13 ++--- java/org/gnu/emacs/EmacsNative.java | 6 ++ java/org/gnu/emacs/EmacsService.java | 103 ++++++++++++++++++++++++++--------- java/org/gnu/emacs/EmacsView.java | 13 +++-- src/android.c | 26 +++++++++ 5 files changed, 123 insertions(+), 38 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/debug.sh b/java/debug.sh index 0458003fe72..d6e439bec90 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -19,7 +19,6 @@ ## along with GNU Emacs. If not, see . set -m -set -x oldpwd=`pwd` cd `dirname $0` @@ -273,7 +272,7 @@ fi gdbserver_cmd= is_root= if [ -z "$gdbserver" ]; then - gdbserver_bin=/system/bin/gdbserver + gdbserver_bin=/system/bin/gdbserver64 else gdbserver_bin=/data/local/tmp/gdbserver gdbserver_cat="cat $gdbserver_bin | run-as $package sh -c \ @@ -312,12 +311,12 @@ rm -f /tmp/file-descriptor-stamp if [ -z "$gdbserver" ]; then if [ "$is_root" = "yes" ]; then adb -s $device shell $gdbserver_bin --multi \ - "+/data/local/tmp/debug.$package.socket" --attach $pid >&5 & - gdb_socket="localfilesystem:/data/local/tmp/debug.$package.socket" + "0.0.0.0:7564" --attach $pid >&5 & + gdb_socket="tcp:7564" else - adb -s $device shell run-as $package $gdbserver_bin --multi \ - "+debug.$package.socket" --attach $pid >&5 & - gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket" + adb -s $device shell $gdbserver_bin --multi \ + "0.0.0.0:7564" --attach $pid >&5 & + gdb_socket="tcp:7564" fi else # Normally the program cannot access $gdbserver_bin when it is diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index eb75201088b..f03736fe614 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -177,6 +177,12 @@ public final class EmacsNative main thread's looper to respond. */ public static native void endSynchronous (); + /* Prevent deadlocks while reliably allowing queries from the Emacs + thread to the main thread to complete by waiting for a query to + start from the main thread, then answer it; assume that a query + is certain to start shortly. */ + public static native void answerQuerySpin (); + /* Return whether or not KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP and KEYCODE_VOLUME_MUTE should be forwarded to Emacs. */ public static native boolean shouldForwardMultimediaButtons (); diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index dde60e1c5af..6d70536faf0 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -25,6 +25,8 @@ import java.io.UnsupportedEncodingException; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + import android.graphics.Matrix; import android.graphics.Point; @@ -106,8 +108,17 @@ public final class EmacsService extends Service performing drawing calls. */ private static final boolean DEBUG_THREADS = false; - /* Whether or not onCreateInputMethod is calling getSelection. */ - public static volatile boolean imSyncInProgress; + /* Atomic integer used for synchronization between + icBeginSynchronous/icEndSynchronous and viewGetSelection. + + Value is 0 if no query is in progress, 1 if viewGetSelection is + being called, and 2 if icBeginSynchronous was called. */ + public static final AtomicInteger servicingQuery; + + static + { + servicingQuery = new AtomicInteger (); + }; /* Return the directory leading to the directory in which native library files are stored on behalf of CONTEXT. */ @@ -658,46 +669,79 @@ public final class EmacsService extends Service EmacsNative.endSynchronous (); } + + + /* IMM functions such as `updateSelection' holds an internal lock + that is also taken before `onCreateInputConnection' (in + EmacsView.java) is called; when that then asks the UI thread for + the current selection, a dead lock results. To remedy this, + reply to any synchronous queries now -- and prohibit more queries + for the duration of `updateSelection' -- if EmacsView may have + been asking for the value of the region. */ + + public static void + icBeginSynchronous () + { + /* Set servicingQuery to 2, so viewGetSelection knows it shouldn't + proceed. */ + + if (servicingQuery.getAndSet (2) == 1) + /* But if viewGetSelection is already in progress, answer it + first. */ + EmacsNative.answerQuerySpin (); + } + + public static void + icEndSynchronous () + { + if (servicingQuery.getAndSet (0) != 2) + throw new RuntimeException ("incorrect value of `servicingQuery': " + + "likely 1"); + } + + public static int[] + viewGetSelection (short window) + { + int[] selection; + + /* See if a query is already in progress from the other + direction. */ + if (!servicingQuery.compareAndSet (0, 1)) + return null; + + /* Now call the regular getSelection. Note that this can't race + with answerQuerySpin, as `android_servicing_query' can never be + 2 when icBeginSynchronous is called, so a query will always be + started. */ + selection = EmacsNative.getSelection (window); + + /* Finally, clear servicingQuery if its value is still 1. If a + query has started from the other side, it ought to be 2. */ + + servicingQuery.compareAndSet (1, 0); + return selection; + } + + + public void updateIC (EmacsWindow window, int newSelectionStart, int newSelectionEnd, int composingRegionStart, int composingRegionEnd) { - boolean wasSynchronous; - if (DEBUG_IC) Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart + " " + newSelectionEnd + " " + composingRegionStart + " " + composingRegionEnd)); - /* `updateSelection' holds an internal lock that is also taken - before `onCreateInputConnection' (in EmacsView.java) is called; - when that then asks the UI thread for the current selection, a - dead lock results. To remedy this, reply to any synchronous - queries now -- and prohibit more queries for the duration of - `updateSelection' -- if EmacsView may have been asking for the - value of the region. */ - - wasSynchronous = false; - if (EmacsService.imSyncInProgress) - { - /* `beginSynchronous' will answer any outstanding queries and - signal that one is now in progress, thereby preventing - `getSelection' from blocking. */ - - EmacsNative.beginSynchronous (); - wasSynchronous = true; - } - + icBeginSynchronous (); window.view.imManager.updateSelection (window.view, newSelectionStart, newSelectionEnd, composingRegionStart, composingRegionEnd); - - if (wasSynchronous) - EmacsNative.endSynchronous (); + icEndSynchronous (); } public void @@ -707,7 +751,10 @@ public final class EmacsService extends Service Log.d (TAG, "resetIC: " + window); window.view.setICMode (icMode); + + icBeginSynchronous (); window.view.imManager.restartInput (window.view); + icEndSynchronous (); } public void @@ -733,11 +780,15 @@ public final class EmacsService extends Service 0); info = builder.build (); + + if (DEBUG_IC) Log.d (TAG, ("updateCursorAnchorInfo: " + x + " " + y + " " + yBaseline + "-" + yBottom)); + icBeginSynchronous (); window.view.imManager.updateCursorAnchorInfo (window.view, info); + icEndSynchronous (); } /* Open a content URI described by the bytes BYTES, a non-terminated diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index bb450bb8e6b..c223dfa7911 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -630,12 +630,11 @@ public final class EmacsView extends ViewGroup /* Obtain the current position of point and set it as the selection. Don't do this under one specific situation: if `android_update_ic' is being called in the main thread, trying - to synchronize with it can cause a dead lock in the IM - manager. */ + to synchronize with it can cause a dead lock in the IM manager. + See icBeginSynchronous in EmacsService.java for more + details. */ - EmacsService.imSyncInProgress = true; - selection = EmacsNative.getSelection (window.handle); - EmacsService.imSyncInProgress = false; + selection = EmacsService.viewGetSelection (window.handle); if (selection != null) Log.d (TAG, "onCreateInputConnection: current selection is: " @@ -664,6 +663,10 @@ public final class EmacsView extends ViewGroup if (inputConnection == null) inputConnection = new EmacsInputConnection (this); + else + /* Reset the composing region, in case there is still composing + text. */ + inputConnection.finishComposingText (); /* Return the input connection. */ return inputConnection; diff --git a/src/android.c b/src/android.c index e74d40a0cdb..f59c0d9e5d2 100644 --- a/src/android.c +++ b/src/android.c @@ -2997,6 +2997,7 @@ NATIVE_NAME (blitRect) (JNIEnv *env, jobject object, static void android_begin_query (void); static void android_end_query (void); +static void android_answer_query_spin (void); JNIEXPORT void JNICALL NATIVE_NAME (beginSynchronous) (JNIEnv *env, jobject object) @@ -3014,6 +3015,14 @@ NATIVE_NAME (endSynchronous) (JNIEnv *env, jobject object) android_end_query (); } +JNIEXPORT void JNICALL +NATIVE_NAME (answerQuerySpin) (JNIEnv *env, jobject object) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + android_answer_query_spin (); +} + #ifdef __clang__ #pragma clang diagnostic pop #else @@ -7041,6 +7050,23 @@ android_answer_query (void) sem_post (&android_query_sem); } +/* Like `android_answer_query'. However, the query may not have + begun; spin until it has. */ + +static void +android_answer_query_spin (void) +{ + int n; + + while (!(n = __atomic_load_n (&android_servicing_query, + __ATOMIC_SEQ_CST))) + eassert (!n); + + /* Note that this function is supposed to be called before + `android_begin_query' starts, so clear the service flag. */ + android_check_query (); +} + /* Notice that the Emacs thread will start blocking waiting for a response from the UI thread. Process any pending queries from the UI thread. -- cgit v1.3 From 66783af554176c68cb58726aeff4ae6a23224234 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 5 Jun 2023 10:38:25 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsNative.java (EmacsNative): New function clearInputFlags. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Stop reporting changes after a new input method connection is established. * src/androidterm.c (android_handle_ime_event): Implement that change. (JNICALL): New function. --- java/org/gnu/emacs/EmacsNative.java | 1 + java/org/gnu/emacs/EmacsView.java | 8 +++---- src/androidterm.c | 48 ++++++++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 5 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index f03736fe614..cb1c6caa79a 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -221,6 +221,7 @@ public final class EmacsNative int flags); public static native void requestSelectionUpdate (short window); public static native void requestCursorUpdates (short window, int mode); + public static native void clearInputFlags (short window); /* Return the current value of the selection, or -1 upon diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index c223dfa7911..a78dec08839 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -627,6 +627,10 @@ public final class EmacsView extends ViewGroup return null; } + /* Reset flags set by the previous input method. */ + + EmacsNative.clearInputFlags (window.handle); + /* Obtain the current position of point and set it as the selection. Don't do this under one specific situation: if `android_update_ic' is being called in the main thread, trying @@ -663,10 +667,6 @@ public final class EmacsView extends ViewGroup if (inputConnection == null) inputConnection = new EmacsInputConnection (this); - else - /* Reset the composing region, in case there is still composing - text. */ - inputConnection.finishComposingText (); /* Return the input connection. */ return inputConnection; diff --git a/src/androidterm.c b/src/androidterm.c index d8a8b4c2d71..afa10a1b94b 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -675,6 +675,7 @@ static void android_handle_ime_event (union android_event *event, struct frame *f) { Lisp_Object text UNINIT; + struct android_output *output; /* First, decode the text if necessary. */ @@ -707,8 +708,23 @@ android_handle_ime_event (union android_event *event, struct frame *f) break; case ANDROID_IME_FINISH_COMPOSING_TEXT: + + if (event->ime.length == 2) + { + output = FRAME_ANDROID_OUTPUT (f); + + /* A new input method has connected to Emacs. Stop + reporting changes that the previous input method has + asked to monitor. */ + + output->extracted_text_flags = 0; + output->extracted_text_token = 0; + output->extracted_text_hint = 0; + output->need_cursor_updates = false; + } + finish_composing_text (f, event->ime.counter, - event->ime.length); + event->ime.length == 1); break; case ANDROID_IME_SET_COMPOSING_TEXT: @@ -5580,6 +5596,36 @@ NATIVE_NAME (requestCursorUpdates) (JNIEnv *env, jobject object, android_write_event (&event); } +/* Notice that a new input method connection has been initialized and + clear cursor update requests, extracted text requests, and the + composing region. */ + +JNIEXPORT void JNICALL +NATIVE_NAME (clearInputFlags) (JNIEnv *env, jobject object, + jshort window) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT; + event.ime.start = 0; + event.ime.end = 0; + + /* This value of `length' means that updates to the cursor position + and extracted text should not be reported anymore. */ + + event.ime.length = 2; + event.ime.position = 0; + event.ime.text = NULL; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + #ifdef __clang__ #pragma clang diagnostic pop #else -- cgit v1.3 From 63339a9577f085074d16fa8c56ddd56864c94dda Mon Sep 17 00:00:00 2001 From: Po Lu Date: Wed, 7 Jun 2023 11:03:56 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsInputConnection.java (beginBatchEdit) (endBatchEdit, commitCompletion, commitText, deleteSurroundingText) (finishComposingText, getSelectedText, getTextAfterCursor) (getTextBeforeCursor, setComposingText, setComposingRegion) (performEditorAction, performContextMenuAction, getExtractedText) (setSelection, sendKeyEvent, deleteSurroundingTextInCodePoints) (requestCursorUpdates): Ensure that the input connection is up to date. (getSurroundingText): New function. * java/org/gnu/emacs/EmacsNative.java (getSurroundingText): Export new C function. * java/org/gnu/emacs/EmacsService.java (resetIC): Invalidate previously created input connections. * java/org/gnu/emacs/EmacsView.java (EmacsView) (onCreateInputConnection): Signify that input connections are now up to date. * src/androidterm.c (struct android_get_surrounding_text_context): New structure. (android_get_surrounding_text, NATIVE_NAME): * src/textconv.c (get_surrounding_text): * src/textconv.h: New functions. --- java/org/gnu/emacs/EmacsInputConnection.java | 105 ++++++++++++++++++++ java/org/gnu/emacs/EmacsNative.java | 4 + java/org/gnu/emacs/EmacsService.java | 1 + java/org/gnu/emacs/EmacsView.java | 13 +++ src/androidterm.c | 143 +++++++++++++++++++++++++++ src/textconv.c | 100 +++++++++++++++++++ src/textconv.h | 4 + 7 files changed, 370 insertions(+) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 9ced7cb7aaf..73c93c67ac7 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -23,7 +23,9 @@ import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextSnapshot; + import android.view.KeyEvent; import android.os.Build; @@ -88,6 +90,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean beginBatchEdit () { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "beginBatchEdit"); @@ -99,6 +105,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean endBatchEdit () { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "endBatchEdit"); @@ -110,6 +120,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean commitCompletion (CompletionInfo info) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "commitCompletion: " + info); @@ -125,6 +139,10 @@ public final class EmacsInputConnection extends BaseInputConnection { int[] selection; + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "commitText: " + text + " " + newCursorPosition); @@ -156,6 +174,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean deleteSurroundingText (int leftLength, int rightLength) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, ("deleteSurroundingText: " + leftLength + " " + rightLength)); @@ -169,6 +191,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean finishComposingText () { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "finishComposingText"); @@ -180,6 +206,10 @@ public final class EmacsInputConnection extends BaseInputConnection public String getSelectedText (int flags) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return null; + if (EmacsService.DEBUG_IC) Log.d (TAG, "getSelectedText: " + flags); @@ -192,6 +222,10 @@ public final class EmacsInputConnection extends BaseInputConnection { String string; + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return null; + if (EmacsService.DEBUG_IC) Log.d (TAG, "getTextAfterCursor: " + length + " " + flags); @@ -210,6 +244,10 @@ public final class EmacsInputConnection extends BaseInputConnection { String string; + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return null; + if (EmacsService.DEBUG_IC) Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags); @@ -226,6 +264,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean setComposingText (CharSequence text, int newCursorPosition) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, ("setComposingText: " + text + " ## " + newCursorPosition)); @@ -239,6 +281,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean setComposingRegion (int start, int end) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "setComposingRegion: " + start + " " + end); @@ -250,6 +296,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean performEditorAction (int editorAction) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "performEditorAction: " + editorAction); @@ -263,6 +313,10 @@ public final class EmacsInputConnection extends BaseInputConnection { int action; + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "performContextMenuAction: " + contextMenuAction); @@ -310,6 +364,10 @@ public final class EmacsInputConnection extends BaseInputConnection ExtractedText text; int[] selection; + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return null; + if (EmacsService.DEBUG_IC) Log.d (TAG, "getExtractedText: " + request.hintMaxChars + ", " + request.hintMaxLines + " " + flags); @@ -360,6 +418,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean setSelection (int start, int end) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "setSelection: " + start + " " + end); @@ -371,6 +433,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean sendKeyEvent (KeyEvent key) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "sendKeyEvent: " + key); @@ -381,6 +447,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean deleteSurroundingTextInCodePoints (int beforeLength, int afterLength) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + /* This can be implemented the same way as deleteSurroundingText. */ return this.deleteSurroundingText (beforeLength, afterLength); @@ -390,6 +460,10 @@ public final class EmacsInputConnection extends BaseInputConnection public boolean requestCursorUpdates (int cursorUpdateMode) { + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return false; + if (EmacsService.DEBUG_IC) Log.d (TAG, "requestCursorUpdates: " + cursorUpdateMode); @@ -397,6 +471,37 @@ public final class EmacsInputConnection extends BaseInputConnection return true; } + @Override + public SurroundingText + getSurroundingText (int beforeLength, int afterLength, + int flags) + { + SurroundingText text; + + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return null; + + if (EmacsService.DEBUG_IC) + Log.d (TAG, ("getSurroundingText: " + beforeLength + ", " + + afterLength)); + + text = EmacsNative.getSurroundingText (windowHandle, beforeLength, + afterLength, flags); + + if (text != null) + Log.d (TAG, ("getSurroundingText: " + + text.getSelectionStart () + + "," + + text.getSelectionEnd () + + "+" + + text.getOffset () + + ": " + + text.getText ())); + + return text; + } + /* Override functions which are not implemented. */ diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index cb1c6caa79a..cb89cf6808a 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -25,6 +25,7 @@ import android.graphics.Bitmap; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.SurroundingText; public final class EmacsNative { @@ -222,6 +223,9 @@ public final class EmacsNative public static native void requestSelectionUpdate (short window); public static native void requestCursorUpdates (short window, int mode); public static native void clearInputFlags (short window); + public static native SurroundingText getSurroundingText (short window, + int left, int right, + int flags); /* Return the current value of the selection, or -1 upon diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 2074a5b7c2b..48e39f8b355 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -748,6 +748,7 @@ public final class EmacsService extends Service window.view.setICMode (icMode); icBeginSynchronous (); + window.view.icGeneration++; window.view.imManager.restartInput (window.view); icEndSynchronous (); } diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index a78dec08839..d432162132d 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -111,6 +111,13 @@ public final class EmacsView extends ViewGroup details. */ private int icMode; + /* The number of calls to `resetIC' to have taken place the last + time an InputConnection was created. */ + public long icSerial; + + /* The number of calls to `recetIC' that have taken place. */ + public volatile long icGeneration; + public EmacsView (EmacsWindow window) { @@ -627,6 +634,12 @@ public final class EmacsView extends ViewGroup return null; } + /* Set icSerial. If icSerial < icGeneration, the input connection + has been reset, and future input should be ignored until a new + connection is created. */ + + icSerial = icGeneration; + /* Reset flags set by the previous input method. */ EmacsNative.clearInputFlags (window.handle); diff --git a/src/androidterm.c b/src/androidterm.c index 2a054715d6a..77f2bd1c7a0 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -5636,6 +5636,149 @@ NATIVE_NAME (clearInputFlags) (JNIEnv *env, jobject object, android_write_event (&event); } + + +/* Context for a call to `getSurroundingText'. */ + +struct android_get_surrounding_text_context +{ + /* Number of characters before the region to return. */ + int before_length; + + /* Number of characters after the region to return. */ + int after_length; + + /* The returned text, or NULL. */ + char *text; + + /* The size of that text in characters and bytes. */ + ptrdiff_t length, bytes; + + /* Offsets into that text. */ + ptrdiff_t offset, start, end; + + /* The window. */ + android_window window; +}; + +/* Return the surrounding text in the surrounding text context + specified by DATA. */ + +static void +android_get_surrounding_text (void *data) +{ + struct android_get_surrounding_text_context *request; + struct frame *f; + ptrdiff_t temp; + + request = data; + + /* Find the frame associated with the window. */ + f = android_window_to_frame (NULL, request->window); + + if (!f) + return; + + /* Now get the surrounding text. */ + request->text + = get_surrounding_text (f, request->before_length, + request->after_length, &request->length, + &request->bytes, &request->offset, + &request->start, &request->end); + + /* Sort request->start and request->end for compatibility with some + bad input methods. */ + + if (request->end < request->start) + { + temp = request->start; + request->start = request->end; + request->end = temp; + } +} + +JNIEXPORT jobject JNICALL +NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject ignored_object, + jshort window, jint before_length, + jint after_length, jint flags) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + static jclass class; + static jmethodID constructor; + + struct android_get_surrounding_text_context context; + jstring string; + jobject object; + + /* Initialize CLASS if it has not yet been initialized. */ + + if (!class) + { + class + = (*env)->FindClass (env, ("android/view/inputmethod" + "/SurroundingText")); + +#if __ANDROID_API__ < 31 + /* If CLASS cannot be found, the version of Android currently + running is too old. */ + + if (!class) + { + (*env)->ExceptionClear (env); + return NULL; + } +#else /* __ANDROID_API__ >= 31 */ + assert (class); +#endif /* __ANDROID_API__ < 31 */ + + class = (*env)->NewGlobalRef (env, class); + if (!class) + return NULL; + + /* Now look for its constructor. */ + constructor = (*env)->GetMethodID (env, class, "", + "(Ljava/lang/CharSequence;III)V"); + assert (constructor); + } + + context.before_length = before_length; + context.after_length = after_length; + context.window = window; + context.text = NULL; + + android_sync_edit (); + if (android_run_in_emacs_thread (android_get_surrounding_text, + &context)) + return NULL; + + if (!context.text) + return NULL; + + /* Encode the returned text. */ + string = android_text_to_string (env, context.text, context.length, + context.bytes); + free (context.text); + + if (!string) + return NULL; + + /* Create an SurroundingText object containing this information. */ + object = (*env)->NewObject (env, class, constructor, string, + (jint) min (context.start, + TYPE_MAXIMUM (jint)), + (jint) min (context.end, + TYPE_MAXIMUM (jint)), + /* Adjust point offsets to fit into + Android's 0-based indexing. */ + (jint) min (context.offset - 1, + TYPE_MAXIMUM (jint))); + if (!object) + return NULL; + + return object; +} + #ifdef __clang__ #pragma clang diagnostic pop #else diff --git a/src/textconv.c b/src/textconv.c index 0dcf5bdcea8..1161b781b6a 100644 --- a/src/textconv.c +++ b/src/textconv.c @@ -1732,6 +1732,106 @@ get_extracted_text (struct frame *f, ptrdiff_t n, return buffer; } +/* Return the text between the positions PT - LEFT and PT + RIGHT. If + the mark is active, return the range of text relative to the bounds + of the region instead. + + Set *LENGTH to the number of characters returned, *BYTES to the + number of bytes returned, *OFFSET to the character position of the + returned text, and *START_RETURN and *END_RETURN to the mark and + point relative to that position. */ + +char * +get_surrounding_text (struct frame *f, ptrdiff_t left, + ptrdiff_t right, ptrdiff_t *length, + ptrdiff_t *bytes, ptrdiff_t *offset, + ptrdiff_t *start_return, + ptrdiff_t *end_return) +{ + specpdl_ref count; + ptrdiff_t start, end, start_byte, end_byte, mark, temp; + char *buffer; + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return NULL; + + /* Save the excursion, as there will be extensive changes to the + selected window. */ + count = SPECPDL_INDEX (); + record_unwind_protect_excursion (); + + /* Inhibit quitting. */ + specbind (Qinhibit_quit, Qt); + + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + select_window (f->old_selected_window, Qt); + buffer = NULL; + + /* Figure out the bounds of the text to return. */ + + /* First, obtain start and end. */ + end = get_mark (); + start = PT; + + /* If the mark is not active, make it start and end. */ + + if (end == -1) + end = start; + + /* Now sort start and end. */ + + if (end < start) + { + temp = start; + start = end; + end = temp; + } + + /* And subtract left and right. */ + + if (INT_SUBTRACT_WRAPV (start, left, &start) + || INT_ADD_WRAPV (end, right, &end)) + goto finish; + + start = max (start, BEGV); + end = min (end, ZV); + + /* Detect overflow. */ + + if (!(start <= PT && PT <= end)) + goto finish; + + /* Convert the character positions to byte positions. */ + start_byte = CHAR_TO_BYTE (start); + end_byte = CHAR_TO_BYTE (end); + + /* Extract the text from the buffer. */ + buffer = xmalloc (end_byte - start_byte); + copy_buffer (start, start_byte, end, end_byte, + buffer); + + /* Get the mark. If it's not active, use PT. */ + + mark = get_mark (); + + if (mark == -1) + mark = PT; + + /* Return the offsets. Unlike `get_extracted_text', this need not + sort mark and point. */ + + *offset = start; + *start_return = mark - start; + *end_return = PT - start; + *length = end - start; + *bytes = end_byte - start_byte; + + finish: + unbind_to (count, Qnil); + return buffer; +} + /* Return whether or not text conversion is temporarily disabled. `reset' should always call this to determine whether or not to disable the input method. */ diff --git a/src/textconv.h b/src/textconv.h index 339cefdba92..7550388a723 100644 --- a/src/textconv.h +++ b/src/textconv.h @@ -143,6 +143,10 @@ extern void textconv_barrier (struct frame *, unsigned long); extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *, bool *); +extern char *get_surrounding_text (struct frame *, ptrdiff_t, + ptrdiff_t, ptrdiff_t *, + ptrdiff_t *, ptrdiff_t *, + ptrdiff_t *, ptrdiff_t *); extern bool conversion_disabled_p (void); extern void register_textconv_interface (struct textconv_interface *); -- 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/EmacsView.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/EmacsView.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 363e293cc919ab02c40bd9a8fa4875c2e5644b2d Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 15 Jun 2023 12:36:50 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsInputConnection.java (EmacsInputConnection, beginBatchEdit, reset, endBatchEdit): Keep track of the number of batch edits and return an appropriate value. (takeSnapshot): Implement function. * java/org/gnu/emacs/EmacsNative.java (takeSnapshot): New function. * java/org/gnu/emacs/EmacsService.java (resetIC): Improve debugging output. * java/org/gnu/emacs/EmacsView.java (onCreateInputConnection): Call `reset' to clear the UI side batch edit count. * src/androidterm.c (struct android_get_surrounding_text_context): New fields `conversion_start' and `conversion_end'. (android_get_surrounding_text): Return the conversion region. (android_get_surrounding_text_internal, NATIVE_NAME): Factor out `getSurroundingText'. (takeSnapshot): New function. --- java/org/gnu/emacs/EmacsInputConnection.java | 66 +++++++++++--- java/org/gnu/emacs/EmacsNative.java | 2 + java/org/gnu/emacs/EmacsService.java | 25 +++++- java/org/gnu/emacs/EmacsView.java | 3 + src/androidterm.c | 129 +++++++++++++++++++++++++-- 5 files changed, 201 insertions(+), 24 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index 1bcc9a62a81..f8dce5dfa79 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -50,6 +50,11 @@ public final class EmacsInputConnection implements InputConnection /* The handle ID associated with that view's window. */ private short windowHandle; + /* Number of batch edits currently underway. Used to avoid + synchronizing with the Emacs thread after each + `endBatchEdit'. */ + private int batchEditCount; + /* Whether or not to synchronize and call `updateIC' with the selection position after committing text. @@ -110,6 +115,10 @@ public final class EmacsInputConnection implements InputConnection Log.d (TAG, "beginBatchEdit"); EmacsNative.beginBatchEdit (windowHandle); + + /* Keep a record of the number of outstanding batch edits here as + well. */ + batchEditCount++; return true; } @@ -125,7 +134,14 @@ public final class EmacsInputConnection implements InputConnection Log.d (TAG, "endBatchEdit"); EmacsNative.endBatchEdit (windowHandle); - return true; + + /* Subtract one from the UI thread record of the number of batch + edits currently under way. */ + + if (batchEditCount > 0) + batchEditCount -= 1; + + return batchEditCount > 0; } public boolean @@ -584,21 +600,50 @@ public final class EmacsInputConnection implements InputConnection return text; } - - /* Override functions which are not implemented. */ - @Override - public Handler - getHandler () + public TextSnapshot + takeSnapshot () { - return null; + TextSnapshot snapshot; + + /* Return if the input connection is out of date. */ + if (view.icSerial < view.icGeneration) + return null; + + snapshot = EmacsNative.takeSnapshot (windowHandle); + + if (EmacsService.DEBUG_IC) + Log.d (TAG, ("takeSnapshot: " + + snapshot.getSurroundingText ().getText () + + " @ " + snapshot.getCompositionEnd () + + ", " + snapshot.getCompositionStart ())); + + return snapshot; } @Override public void closeConnection () { + batchEditCount = 0; + } + + + public void + reset () + { + batchEditCount = 0; + } + + + /* Override functions which are not implemented. */ + + @Override + public Handler + getHandler () + { + return null; } @Override @@ -616,13 +661,6 @@ public final class EmacsInputConnection implements InputConnection return false; } - @Override - public TextSnapshot - takeSnapshot () - { - return null; - } - @Override public boolean clearMetaKeyStates (int states) diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 2fcbf8b94ef..9e87c419f95 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -26,6 +26,7 @@ import android.graphics.Bitmap; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextSnapshot; public final class EmacsNative { @@ -230,6 +231,7 @@ public final class EmacsNative public static native SurroundingText getSurroundingText (short window, int left, int right, int flags); + public static native TextSnapshot takeSnapshot (short window); /* Return the current value of the selection, or -1 upon diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 96216e51cf4..2fe4e8c4146 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -767,8 +767,31 @@ public final class EmacsService extends Service public void resetIC (EmacsWindow window, int icMode) { + int oldMode; + if (DEBUG_IC) - Log.d (TAG, "resetIC: " + window); + Log.d (TAG, "resetIC: " + window + ", " + icMode); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && (oldMode = window.view.getICMode ()) == icMode + /* Don't do this if there is currently no input + connection. */ + && oldMode != IC_MODE_NULL) + { + if (DEBUG_IC) + Log.d (TAG, "resetIC: calling invalidateInput"); + + /* Android 33 and later allow the IM reset to be optimized out + and replaced by a call to `invalidateInput', which is much + faster, as it does not involve resetting the input + connection. */ + + icBeginSynchronous (); + window.view.imManager.invalidateInput (window.view); + icEndSynchronous (); + + return; + } window.view.setICMode (icMode); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 278c6025902..aba1184b0c2 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -681,6 +681,9 @@ public final class EmacsView extends ViewGroup if (inputConnection == null) inputConnection = new EmacsInputConnection (this); + else + /* Clear several pieces of state in the input connection. */ + inputConnection.reset (); /* Return the input connection. */ return inputConnection; diff --git a/src/androidterm.c b/src/androidterm.c index 191ff65199b..29076981a47 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -5668,6 +5668,10 @@ struct android_get_surrounding_text_context /* Offsets into that text. */ ptrdiff_t offset, start, end; + /* The start and end indices of the conversion region. + -1 if it does not exist. */ + ptrdiff_t conversion_start, conversion_end; + /* The window. */ android_window window; }; @@ -5706,22 +5710,47 @@ android_get_surrounding_text (void *data) request->start = request->end; request->end = temp; } + + /* Retrieve the conversion region. */ + + request->conversion_start = -1; + request->conversion_end = -1; + + if (MARKERP (f->conversion.compose_region_start)) + { + request->conversion_start + = marker_position (f->conversion.compose_region_start) - 1; + request->conversion_end + = marker_position (f->conversion.compose_region_end) - 1; + } } -JNIEXPORT jobject JNICALL -NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject ignored_object, - jshort window, jint before_length, - jint after_length, jint flags) -{ - JNI_STACK_ALIGNMENT_PROLOGUE; +/* Return a local reference to a `SurroundingText' object describing + WINDOW's surrounding text. ENV should be a valid JNI environment + for the current thread. - static jclass class; - static jmethodID constructor; + BEFORE_LENGTH and AFTER_LENGTH specify the number of characters + around point and mark to return. + Return the conversion region (or -1) in *CONVERSION_START and + *CONVERSION_END if non-NULL. + + Value is the object upon success, else NULL. */ + +static jobject +android_get_surrounding_text_internal (JNIEnv *env, jshort window, + jint before_length, + jint after_length, + ptrdiff_t *conversion_start, + ptrdiff_t *conversion_end) +{ struct android_get_surrounding_text_context context; jstring string; jobject object; + static jclass class; + static jmethodID constructor; + /* Initialize CLASS if it has not yet been initialized. */ if (!class) @@ -5745,7 +5774,9 @@ NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject ignored_object, class = (*env)->NewGlobalRef (env, class); if (!class) - return NULL; + /* Clear class to prevent a local reference from remaining in + `class'. */ + return (class = NULL); /* Now look for its constructor. */ constructor = (*env)->GetMethodID (env, class, "", @@ -5787,6 +5818,86 @@ NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject ignored_object, if (!object) return NULL; + /* Now return the conversion region if that was requested. */ + + if (conversion_start) + { + *conversion_start = context.conversion_start; + *conversion_end = context.conversion_start; + } + + return object; +} + +JNIEXPORT jobject JNICALL +NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject object, + jshort window, jint before_length, + jint after_length, jint flags) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + return android_get_surrounding_text_internal (env, window, before_length, + after_length, NULL, NULL); +} + +JNIEXPORT jobject JNICALL +NATIVE_NAME (takeSnapshot) (JNIEnv *env, jobject object, jshort window) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + jobject text; + ptrdiff_t start, end; + + static jclass class; + static jmethodID constructor; + + /* First, obtain the surrounding text and conversion region. */ + text = android_get_surrounding_text_internal (env, window, 600, 600, + &start, &end); + + /* If that fails, return NULL. */ + + if (!text) + return NULL; + + /* Next, initialize the TextSnapshot class. */ + + if (!class) + { + class + = (*env)->FindClass (env, ("android/view/inputmethod" + "/TextSnapshot")); +#if __ANDROID_API__ < 33 + /* If CLASS cannot be found, the version of Android currently + running is too old. */ + + if (!class) + { + (*env)->ExceptionClear (env); + return NULL; + } +#else /* __ANDROID_API__ >= 33 */ + assert (class); +#endif /* __ANDROID_API__ < 33 */ + + class = (*env)->NewGlobalRef (env, class); + if (!class) + /* Clear class to prevent a local reference from remaining in + `class'. */ + return (class = NULL); + + constructor = (*env)->GetMethodID (env, class, "", + "(Landroid/view/inputmethod" + "/SurroundingText;III)V"); + assert (constructor); + } + + /* Try to create a TextSnapshot object. */ + eassert (start <= end); + object = (*env)->NewObject (env, class, constructor, text, + (jint) min (start, TYPE_MAXIMUM (jint)), + (jint) min (end, TYPE_MAXIMUM (jint)), + (jint) 0); return object; } -- 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/EmacsView.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 405d14402f21df3404ce9c5aa3c7f942e6deb3e3 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Mon, 19 Jun 2023 15:26:07 +0800 Subject: Update Android port * java/org/gnu/emacs/EmacsView.java (EmacsView, dimensionsLock): New field. (): Create new lock object. (handleDirtyBitmap, onLayout, onAttachedToWindow): Make sure measuredWidth and measuredHeight are extracted and set atomically. Send Expose upon layout changes if the view has grown. --- java/org/gnu/emacs/EmacsView.java | 68 ++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) (limited to 'java/org/gnu/emacs/EmacsView.java') diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 0cabefdf385..bb4dace655a 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -88,6 +88,9 @@ public final class EmacsView extends ViewGroup /* The last measured width and height. */ private int measuredWidth, measuredHeight; + /* Object acting as a lock for those values. */ + private Object dimensionsLock; + /* The serial of the last clip rectangle change. */ private long lastClipSerial; @@ -144,12 +147,23 @@ public final class EmacsView extends ViewGroup /* Add this view as its own global layout listener. */ getViewTreeObserver ().addOnGlobalLayoutListener (this); + + /* Create an object used as a lock. */ + this.dimensionsLock = new Object (); } private void handleDirtyBitmap () { Bitmap oldBitmap; + int measuredWidth, measuredHeight; + + synchronized (dimensionsLock) + { + /* Load measuredWidth and measuredHeight. */ + measuredWidth = this.measuredWidth; + measuredHeight = this.measuredHeight; + } if (measuredWidth == 0 || measuredHeight == 0) return; @@ -171,7 +185,7 @@ public final class EmacsView extends ViewGroup /* Save the old bitmap. */ oldBitmap = bitmap; - /* Recreate the front and back buffer bitmaps. */ + /* Recreate the back buffer bitmap. */ bitmap = Bitmap.createBitmap (measuredWidth, measuredHeight, @@ -249,8 +263,11 @@ public final class EmacsView extends ViewGroup public void prepareForLayout (int wantedWidth, int wantedHeight) { - measuredWidth = wantedWidth; - measuredHeight = wantedWidth; + synchronized (dimensionsLock) + { + measuredWidth = wantedWidth; + measuredHeight = wantedWidth; + } } @Override @@ -294,19 +311,39 @@ public final class EmacsView extends ViewGroup onLayout (boolean changed, int left, int top, int right, int bottom) { - int count, i; + int count, i, oldMeasuredWidth, oldMeasuredHeight; View child; Rect windowRect; + boolean needExpose; count = getChildCount (); + needExpose = false; - measuredWidth = right - left; - measuredHeight = bottom - top; + synchronized (dimensionsLock) + { + /* Load measuredWidth and measuredHeight. */ + oldMeasuredWidth = measuredWidth; + oldMeasuredHeight = measuredHeight; - /* Dirty the back buffer. */ + /* Set measuredWidth and measuredHeight. */ + measuredWidth = right - left; + measuredHeight = bottom - top; + } - if (changed) - explicitlyDirtyBitmap (); + /* Dirty the back buffer if the layout change resulted in the view + being resized. */ + + if (changed && (right - left != oldMeasuredWidth + || bottom - top != oldMeasuredHeight)) + { + explicitlyDirtyBitmap (); + + /* Expose the window upon a change in the view's size. */ + + if (right - left > oldMeasuredWidth + || bottom - top > oldMeasuredHeight) + needExpose = true; + } for (i = 0; i < count; ++i) { @@ -336,6 +373,10 @@ public final class EmacsView extends ViewGroup mustReportLayout = false; window.viewLayout (left, top, right, bottom); } + + if (needExpose) + EmacsNative.sendExpose (this.window.handle, 0, 0, + right - left, bottom - top); } public void @@ -579,9 +620,12 @@ public final class EmacsView extends ViewGroup was called. */ bitmapDirty = true; - /* Now expose the view contents again. */ - EmacsNative.sendExpose (this.window.handle, 0, 0, - measuredWidth, measuredHeight); + synchronized (dimensionsLock) + { + /* Now expose the view contents again. */ + EmacsNative.sendExpose (this.window.handle, 0, 0, + measuredWidth, measuredHeight); + } super.onAttachedToWindow (); } -- 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/EmacsView.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