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/AndroidManifest.xml | 54 ++++ java/Makefile.in | 150 +++++++++ java/README | 6 + java/debug.sh | 242 +++++++++++++++ java/emacs.keystore | Bin 0 -> 2776 bytes java/org/gnu/emacs/EmacsActivity.java | 146 +++++++++ java/org/gnu/emacs/EmacsApplication.java | 27 ++ java/org/gnu/emacs/EmacsCopyArea.java | 131 ++++++++ java/org/gnu/emacs/EmacsDrawLine.java | 137 +++++++++ java/org/gnu/emacs/EmacsDrawPoint.java | 30 ++ java/org/gnu/emacs/EmacsDrawRectangle.java | 127 ++++++++ java/org/gnu/emacs/EmacsDrawable.java | 33 ++ java/org/gnu/emacs/EmacsFillPolygon.java | 150 +++++++++ java/org/gnu/emacs/EmacsFillRectangle.java | 129 ++++++++ java/org/gnu/emacs/EmacsFontDriver.java | 150 +++++++++ java/org/gnu/emacs/EmacsGC.java | 116 +++++++ java/org/gnu/emacs/EmacsHandleObject.java | 62 ++++ java/org/gnu/emacs/EmacsNative.java | 72 +++++ java/org/gnu/emacs/EmacsPaintQueue.java | 138 +++++++++ java/org/gnu/emacs/EmacsPaintReq.java | 33 ++ java/org/gnu/emacs/EmacsPixmap.java | 102 ++++++ java/org/gnu/emacs/EmacsSdk7FontDriver.java | 460 ++++++++++++++++++++++++++++ java/org/gnu/emacs/EmacsService.java | 384 +++++++++++++++++++++++ java/org/gnu/emacs/EmacsSurfaceView.java | 83 +++++ java/org/gnu/emacs/EmacsThread.java | 44 +++ java/org/gnu/emacs/EmacsView.java | 211 +++++++++++++ java/org/gnu/emacs/EmacsWindow.java | 336 ++++++++++++++++++++ 27 files changed, 3553 insertions(+) create mode 100644 java/AndroidManifest.xml create mode 100644 java/Makefile.in create mode 100644 java/README create mode 100755 java/debug.sh create mode 100644 java/emacs.keystore create mode 100644 java/org/gnu/emacs/EmacsActivity.java create mode 100644 java/org/gnu/emacs/EmacsApplication.java create mode 100644 java/org/gnu/emacs/EmacsCopyArea.java create mode 100644 java/org/gnu/emacs/EmacsDrawLine.java create mode 100644 java/org/gnu/emacs/EmacsDrawPoint.java create mode 100644 java/org/gnu/emacs/EmacsDrawRectangle.java create mode 100644 java/org/gnu/emacs/EmacsDrawable.java create mode 100644 java/org/gnu/emacs/EmacsFillPolygon.java create mode 100644 java/org/gnu/emacs/EmacsFillRectangle.java create mode 100644 java/org/gnu/emacs/EmacsFontDriver.java create mode 100644 java/org/gnu/emacs/EmacsGC.java create mode 100644 java/org/gnu/emacs/EmacsHandleObject.java create mode 100644 java/org/gnu/emacs/EmacsNative.java create mode 100644 java/org/gnu/emacs/EmacsPaintQueue.java create mode 100644 java/org/gnu/emacs/EmacsPaintReq.java create mode 100644 java/org/gnu/emacs/EmacsPixmap.java create mode 100644 java/org/gnu/emacs/EmacsSdk7FontDriver.java create mode 100644 java/org/gnu/emacs/EmacsService.java create mode 100644 java/org/gnu/emacs/EmacsSurfaceView.java create mode 100644 java/org/gnu/emacs/EmacsThread.java create mode 100644 java/org/gnu/emacs/EmacsView.java create mode 100644 java/org/gnu/emacs/EmacsWindow.java (limited to 'java') diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml new file mode 100644 index 00000000000..75aa5bdf409 --- /dev/null +++ b/java/AndroidManifest.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/Makefile.in b/java/Makefile.in new file mode 100644 index 00000000000..e9fcc625cb2 --- /dev/null +++ b/java/Makefile.in @@ -0,0 +1,150 @@ +### @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 . + +top_builddir = @top_builddir@ + +-include ${top_builddir}/src/verbose.mk + +SHELL = @SHELL@ +JAVAC = @JAVAC@ +AAPT = @AAPT@ +DX = @DX@ +ZIPALIGN = @ZIPALIGN@ +JARSIGNER = @JARSIGNER@ +ANDROID_JAR = @ANDROID_JAR@ +ANDROID_ABI = @ANDROID_ABI@ + +WARN_JAVAFLAGS = -Xlint:deprecation +JAVAFLAGS = -classpath "$(ANDROID_JAR):." -target 1.7 -source 1.7 \ + $(WARN_JAVAFLAGS) + +SIGN_EMACS = -keystore emacs.keystore -storepass emacs1 + +JAVA_FILES = $(shell find . -type f -name *.java) +CLASS_FILES = $(foreach file,$(JAVA_FILES),$(basename $(file)).class) + +# How this stuff works. + +# emacs.apk depends on emacs.apk-in, which is simply a ZIP archive +# containing the following files: +# lib/$(ANDROID_ABI)/libemacs.so +# lib/$(ANDROID_ABI)/libandroid-emacs.so +# lib/$(ANDROID_ABI)/libctags.so +# lib/$(ANDROID_ABI)/libhexl.so +# lib/$(ANDROID_ABI)/libmovemail.so +# lib/$(ANDROID_ABI)/librcs2log.so +# lib/$(ANDROID_ABI)/libebrowse.so +# assets/info/ +# assets/etc/ +# assets/lisp/ + +.PHONY: emacs.apk-in all +all: emacs.apk + +# Binaries to cross-compile. +CROSS_BINS = ../xcompile/src/android-emacs ../xcompile/lib-src/ctags \ + ../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \ + ../xcompile/lib-src/ebrowse + +# Libraries to cross-compile. +CROSS_LIBS = ../xcompile/src/libemacs.so + +.PHONY: $(CROSS_BINS) $(CROSS_LIBS) + +../xcompile/src/android-emacs ../xcompile/src/libemacs.so: + make -C ../xcompile src/$(notdir $@) + +../xcompile/lib-src/hexl ../xcompile/lib-src/movemail \ +../xcompile/lib-src/ctags ../xcompile/lib-src/ebrowse &: + make -C ../xcompile lib-src/$(notdir $@) + +emacs.apk-in: $(CROSS_BINS) $(CROSS_LIBS) AndroidManifest.xml +# 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 +# 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 +# Remove undesirable files from those directories. + for subdir in `find install_temp -type d -print`; do \ + chmod a+rx $${subdir} ; \ + rm -rf $${subdir}/.gitignore ; \ + rm -rf $${subdir}/.DS_Store ; \ + rm -rf $${subdir}/#* ; \ + rm -rf $${subdir}/.#* ; \ + rm -rf $${subdir}/*~ ; \ + rm -rf $${subdir}/*.orig ; \ + rm -rf $${subdir}/ChangeLog* ; \ + rm -rf $${subdir}/[mM]akefile*[.-]in ; \ + rm -rf $${subdir}/Makefile; \ + done +# Install architecture dependents to lib/$(ANDROID_ABI). This +# perculiar naming scheme is required to make Android preserve these +# binaries upon installation. + 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 + for file in $(CROSS_LIBS); do \ + if [ -x $$file ]; then \ + cp -f $$file install_temp/lib/$(ANDROID_ABI); \ + fi \ + done +# Package everything. + $(AAPT) package -I "$(ANDROID_JAR)" -F $@ -f -M AndroidManifest.xml + pushd install_temp; $(AAPT) add ../$@ `find lib -type f`; popd + pushd install_temp; $(AAPT) add ../$@ `find assets -type f`; popd + rm -rf install_temp + +.SUFFIXES: .java .class +.java.class &: + $(AM_V_JAVAC) $(JAVAC) $(JAVAFLAGS) $< + +# N.B. that find must be called all over again in case javac generated +# nested classes. + +classes.dex: $(CLASS_FILES) + $(AM_V_DX) $(DX) --classpath $(ANDROID_JAR) \ + $(subst $$,\$$,$(shell find . -type f -name *.class)) + +# When emacs.keystore expires, regenerate it with: +# +# keytool -genkey -v -keystore emacs.keystore -alias "Emacs keystore" \ +# -keyalg RSA -sigalg SHA1withRSA -keysize 2048 -validity 100000 + +.PHONY: clean maintainer-clean + +emacs.apk: 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 $@ + rm -f $@.unaligned + +clean: + rm -f emacs.apk emacs.apk-in *.dex *.unaligned *.class + rm -rf install-temp + find . -name '*.class' -delete + +maintainer-clean: clean diff --git a/java/README b/java/README new file mode 100644 index 00000000000..50c2332ce95 --- /dev/null +++ b/java/README @@ -0,0 +1,6 @@ +This directory holds the Java sources of the port of GNU Emacs to +Android-like systems. + +Please keep the Java code indented with tabs and formatted according +to the rules for C code in the GNU coding standards. Always use +C-style comments. diff --git a/java/debug.sh b/java/debug.sh new file mode 100755 index 00000000000..dd710dc31af --- /dev/null +++ b/java/debug.sh @@ -0,0 +1,242 @@ +#!/bin/bash +### Run Emacs under GDB or JDB on Android. + +## 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 . + +set -m +oldpwd=`pwd` +cd `dirname $0` + +devices=`adb devices | grep device | awk -- '/device\y/ { print $1 }' -` +device= +progname=$0 +package=org.gnu.emacs +activity=org.gnu.emacs.EmacsActivity +gdb_port=5039 +jdb_port=64013 +jdb=no + +while [ $# -gt 0 ]; do + case "$1" in + ## This option specifies the serial number of a device to use. + "--device" ) + device="$2" + if [ -z device ]; then + echo "You must specify an argument to --device" + exit 1 + fi + ;; + "--help" ) + echo "Usage: $progname [options] -- [gdb options]" + echo "" + echo " --device DEVICE run Emacs on the specified device" + 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 " --help print this message" + echo "" + echo "Available devices:" + for device in $devices; do + echo " " $device + done + echo "" + exit 0 + ;; + "--jdb" ) + jdb=yes + ;; + "--port" ) + gdb_port=$1 + ;; + "--" ) + shift + gdbargs=$@ + break; + ;; + * ) + echo "$progname: Unrecognized argument $1" + exit 1 + ;; + esac + shift +done + +if [ -z $devices ]; then + echo "No devices are available." + exit 1 +fi + +if [ -z $device ]; then + device=$devices +fi + +if [ `wc -w <<< "$devices"` -gt 1 ] && [ -z device ]; then + echo "Multiple devices are available. Please pick one using" + echo "--device and try again." +fi + +echo "Looking for $package on device $device" + +# Find the application data directory +app_data_dir=`adb -s $device shell run-as $package sh -c 'pwd 2> /dev/null'` + +if [ -z $app_data_dir ]; then + echo "The data directory for the package $package was not found." + 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` + +# 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` + +# Next, remove lines matching "ps" itself. +package_pids=`awk -- '{ + if (!match ($0, /(PID|ps)/)) + print $1 +}' <<< $package_pids` + +pid=$package_pids +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 + }' + echo "" + printf "Which one do you want to attach to? " + read pid +elif [ -z $package_pids ]; then + echo "No processes were found to attach to." + 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" + +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 \ + com.sun.jdi.SocketAttach:hostname=localhost,port=$jdb_port" + +if [ $jdb = "yes" ]; then + # Just start JDB and then exit + $jdb_command + exit 1 +fi + +exec 4<> /tmp/file-descriptor-stamp + +# 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 + +# 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 & + +# Wait until gdbserver successfully runs. +line= +while read -u 5 line; do + case "$line" in + *Attached* ) + break; + ;; + *error* | *Error* | failed ) + echo $line + exit 1 + ;; + * ) + ;; + 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" + +# Forward the gdb server port here. +adb -s $device forward "tcp:$gdb_port" \ + "localfilesystem:$app_data_dir/debug.$package_uid.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" + echo "with --port?" + exit 1; +fi + +# Finally, start gdb with any extra arguments needed. +cd "$oldpwd" +gdb --eval-command "" --eval-command "target remote localhost:$gdb_port" $gdbargs diff --git a/java/emacs.keystore b/java/emacs.keystore new file mode 100644 index 00000000000..76d80b6db65 Binary files /dev/null and b/java/emacs.keystore differ diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java new file mode 100644 index 00000000000..cacd5f13e60 --- /dev/null +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -0,0 +1,146 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +package org.gnu.emacs; + +import java.lang.IllegalStateException; +import java.util.List; +import java.util.ArrayList; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; + +public class EmacsActivity extends Activity +{ + public static final String TAG = "EmacsActivity"; + + /* List of all activities that do not have an associated + EmacsWindow. */ + public static List availableActivities; + + /* The currently attached EmacsWindow, or null if none. */ + private EmacsWindow window; + + /* The frame layout associated with the activity. */ + private FrameLayout layout; + + static + { + /* Set up the list of available activities. */ + availableActivities = new ArrayList (); + }; + + public void + attachChild (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); + + /* Remove the objects from the lists of what is available. */ + EmacsService.availableChildren.remove (child); + availableActivities.remove (this); + + /* Now set child->activity. */ + child.setActivity (this); + } + + /* Make this activity available for future windows to attach + again. */ + + public void + makeAvailable () + { + window = null; + + for (EmacsWindow iterWindow + : EmacsService.availableChildren) + { + synchronized (iterWindow) + { + if (!iterWindow.isDestroyed ()) + attachChild (iterWindow); + + return; + } + } + + availableActivities.add (this); + } + + @Override + public void + onCreate (Bundle savedInstanceState) + { + FrameLayout.LayoutParams params; + + params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT); + + /* Make the frame layout. */ + layout = new FrameLayout (this); + layout.setLayoutParams (params); + + /* 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)); + + super.onCreate (savedInstanceState); + } + + @Override + public void + onStop () + { + /* The activity is no longer visible. If there is a window + attached, detach it. */ + + if (window != null) + { + layout.removeView (window.view); + + /* Notice that the window is already available too. But do + not call noticeAvailableChild; that might assign it to some + other activity, which behaves badly. */ + EmacsService.availableChildren.add (window); + window = null; + } + + /* Finally, remove this activity from the list of available + activities. */ + availableActivities.remove (this); + super.onStop (); + } +}; diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java new file mode 100644 index 00000000000..125da05cfd4 --- /dev/null +++ b/java/org/gnu/emacs/EmacsApplication.java @@ -0,0 +1,27 @@ +/* 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.app.Application; + +public class EmacsApplication extends Application +{ + /* This class currently does nothing. */ +}; diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java new file mode 100644 index 00000000000..f34d1ecde01 --- /dev/null +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -0,0 +1,131 @@ +/* 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.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 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; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + }; + + public + EmacsCopyArea (EmacsDrawable destination, EmacsDrawable source, + int src_x, int src_y, int width, int height, + int dest_x, int dest_y, EmacsGC immutableGC) + { + 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; + } + + @Override + public EmacsGC + getGC () + { + return immutableGC; + } + + @Override + public void + paintTo (Canvas canvas, Paint paint, EmacsGC immutableGC) + { + int alu; + Bitmap bitmap; + Paint maskPaint; + Canvas maskCanvas; + Bitmap maskBitmap; + Rect rect, srcRect; + + /* TODO implement stippling. */ + if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; + + alu = immutableGC.function; + rect = getRect (); + bitmap = source.getBitmap (); + + if (alu == EmacsGC.GC_COPY) + paint.setXfermode (null); + else + 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); + 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) + return; + + maskPaint.setXfermode (srcInAlu); + 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); + } + } +} diff --git a/java/org/gnu/emacs/EmacsDrawLine.java b/java/org/gnu/emacs/EmacsDrawLine.java new file mode 100644 index 00000000000..6389031bbfa --- /dev/null +++ b/java/org/gnu/emacs/EmacsDrawLine.java @@ -0,0 +1,137 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +package org.gnu.emacs; + +import java.lang.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 class EmacsDrawLine implements EmacsPaintReq +{ + private int x, y, x2, y2; + private EmacsDrawable drawable; + private EmacsGC immutableGC; + private static Xfermode xorAlu, srcInAlu; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + }; + + 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; + } + + @Override + public Rect + getRect () + { + return 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; + + /* TODO implement stippling. */ + if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + 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); + + if (immutableGC.clip_mask == null) + { + paint.setColor (immutableGC.foreground | 0xff000000); + canvas.drawLine ((float) x, (float) y, + (float) x2, (float) y2, + paint); + } + else + { + 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); + } + + paint.setXfermode (null); + } +} diff --git a/java/org/gnu/emacs/EmacsDrawPoint.java b/java/org/gnu/emacs/EmacsDrawPoint.java new file mode 100644 index 00000000000..772757ff424 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDrawPoint.java @@ -0,0 +1,30 @@ +/* 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 EmacsDrawPoint extends EmacsDrawRectangle +{ + public + EmacsDrawPoint (EmacsDrawable drawable, int x, int y, + EmacsGC immutableGC) + { + super (drawable, x, y, 1, 1, immutableGC); + } +} diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java new file mode 100644 index 00000000000..462bf7c85b5 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -0,0 +1,127 @@ +/* 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.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; + } + + @Override + public Rect + getRect () + { + return new Rect (x, y, x + width, y + height); + } + + @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; + + /* TODO implement stippling. */ + if (immutableGC.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) + return; + + alu = immutableGC.function; + rect = getRect (); + + paint.setStyle (Paint.Style.STROKE); + + if (alu == EmacsGC.GC_COPY) + paint.setXfermode (null); + else + paint.setXfermode (xorAlu); + + if (immutableGC.clip_mask == null) + { + paint.setColor (immutableGC.foreground | 0xff000000); + canvas.drawRect (rect, paint); + } + else + { + 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.drawRect (srcRect, maskPaint); + canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + } + + paint.setXfermode (null); + } +} diff --git a/java/org/gnu/emacs/EmacsDrawable.java b/java/org/gnu/emacs/EmacsDrawable.java new file mode 100644 index 00000000000..19062137213 --- /dev/null +++ b/java/org/gnu/emacs/EmacsDrawable.java @@ -0,0 +1,33 @@ +/* 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.graphics.Rect; +import android.graphics.Bitmap; +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 new file mode 100644 index 00000000000..3198c7f07c4 --- /dev/null +++ b/java/org/gnu/emacs/EmacsFillPolygon.java @@ -0,0 +1,150 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +package org.gnu.emacs; + +import java.lang.Math; + +import android.graphics.Bitmap; +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 +{ + private EmacsDrawable drawable; + private EmacsGC immutableGC; + private Path path; + + private static Xfermode xorAlu, srcInAlu; + + static + { + xorAlu = new PorterDuffXfermode (Mode.XOR); + srcInAlu = new PorterDuffXfermode (Mode.SRC_IN); + }; + + public + EmacsFillPolygon (EmacsDrawable drawable, Point points[], + EmacsGC immutableGC) + { + int i; + + this.drawable = drawable; + this.immutableGC = immutableGC; + + /* Build the path from the given array of points. */ + path = new Path (); + + if (points.length >= 1) + { + path.moveTo (points[0].x, points[0].y); + + for (i = 1; i < points.length; ++i) + path.lineTo (points[i].x, points[i].y); + + path.close (); + } + } + + @Override + public Rect + getRect () + { + RectF rect; + + rect = new RectF (0, 0, 0, 0); + path.computeBounds (rect, true); + + return new Rect ((int) Math.floor (rect.left), + (int) Math.floor (rect.top), + (int) Math.ceil (rect.right), + (int) Math.ceil (rect.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 (); + + if (alu == EmacsGC.GC_COPY) + paint.setXfermode (null); + else + paint.setXfermode (xorAlu); + + paint.setStyle (Paint.Style.FILL); + + 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); + } + } +} diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java new file mode 100644 index 00000000000..7246c13de7f --- /dev/null +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -0,0 +1,129 @@ +/* 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.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 +{ + 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 () + { + 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; + + /* 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 (immutableGC.clip_mask == null) + { + paint.setColor (immutableGC.foreground | 0xff000000); + canvas.drawRect (rect, paint); + } + else + { + 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.drawRect (srcRect, maskPaint); + canvas.drawBitmap (maskBitmap, srcRect, rect, paint); + } + + paint.setXfermode (null); + } +} diff --git a/java/org/gnu/emacs/EmacsFontDriver.java b/java/org/gnu/emacs/EmacsFontDriver.java new file mode 100644 index 00000000000..f419e71059d --- /dev/null +++ b/java/org/gnu/emacs/EmacsFontDriver.java @@ -0,0 +1,150 @@ +/* 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 java.util.List; + +public abstract class EmacsFontDriver +{ + /* Font weights. */ + public static final int THIN = 0; + public static final int ULTRA_LIGHT = 40; + public static final int LIGHT = 50; + public static final int SEMI_LIGHT = 55; + public static final int REGULAR = 80; + public static final int MEDIUM = 100; + public static final int SEMI_BOLD = 180; + public static final int BOLD = 200; + public static final int EXTRA_BOLD = 205; + public static final int BLACK = 210; + public static final int ULTRA_HEAVY = 250; + + /* Font slants. */ + public static final int REVERSE_OBLIQUE = 0; + public static final int REVERSE_ITALIC = 10; + public static final int NORMAL = 100; + public static final int ITALIC = 200; + public static final int OBLIQUE = 210; + + /* Font widths. */ + public static final int ULTRA_CONDENSED = 50; + public static final int EXTRA_CONDENSED = 63; + public static final int CONDENSED = 75; + public static final int SEMI_CONDENSED = 87; + public static final int UNSPECIFIED = 100; + public static final int SEMI_EXPANDED = 113; + public static final int EXPANDED = 125; + public static final int EXTRA_EXPANDED = 150; + public static final int ULTRA_EXPANDED = 200; + + /* Font spacings. */ + public static final int PROPORTIONAL = 0; + public static final int DUAL = 90; + public static final int MONO = 100; + public static final int CHARCELL = 110; + + public class FontSpec + { + /* The fields below mean the same as they do in enum + font_property_index in font.h. */ + + public String foundry; + public String family; + public String adstyle; + public String registry; + public Integer width; + public Integer weight; + public Integer slant; + public Integer size; + public Integer spacing; + public Integer avgwidth; + + @Override + public String + toString () + { + return ("foundry: " + foundry + + " family: " + family + + " adstyle: " + adstyle + + " registry: " + registry + + " width: " + width + + " weight: " + weight + + " slant: " + slant + + " spacing: " + spacing + + " avgwidth: " + avgwidth); + } + }; + + public class FontMetrics + { + public short lbearing; + public short rbearing; + public short width; + public short ascent; + public short descent; + } + + public class FontEntity extends FontSpec + { + /* No extra fields here. */ + }; + + public abstract class FontObject extends FontSpec + { + public int minWidth; + public int maxWidth; + public int pixelSize; + public int height; + public int spaceWidth; + public int averageWidth; + public int ascent; + public int descent; + public int underlineThickness; + public int underlinePosition; + public int baselineOffset; + public int relativeCompose; + public int defaultAscent; + public int encodingCharset; + public int repertoryCharset; + + public + FontObject () + { + encodingCharset = -1; + repertoryCharset = -1; + } + }; + + /* These mean the same as they do in struct font_driver. */ + public abstract FontEntity[] list (FontSpec fontSpec); + public abstract FontEntity match (FontSpec fontSpec); + public abstract String[] listFamilies (); + 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[]); + public abstract int encodeChar (FontObject fontObject, char charCode); + + public static EmacsFontDriver + createFontDriver () + { + return new EmacsSdk7FontDriver (); + } +}; diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java new file mode 100644 index 00000000000..0becb04519d --- /dev/null +++ b/java/org/gnu/emacs/EmacsGC.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; + +import android.graphics.Rect; + +/* X like graphics context structures. Keep the enums in synch with + androidgui.h! */ + +public class EmacsGC extends EmacsHandleObject +{ + public static final int GC_COPY = 0; + public static final int GC_XOR = 1; + + public static final int GC_FILL_SOLID = 0; + public static final int GC_FILL_OPAQUE_STIPPLED = 1; + + 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 EmacsPixmap clip_mask, stipple; + private boolean dirty; + private EmacsGC immutableGC; + + /* The following fields are only set on immutable GCs. */ + + public + EmacsGC (short handle) + { + /* For historical reasons the C code has an extra layer of + indirection above this GC handle. struct android_gc is the GC + used by Emacs code, while android_gcontext is the type of the + handle. */ + super (handle); + + fill_style = GC_FILL_SOLID; + function = GC_COPY; + foreground = 0; + background = 0xffffffff; + } + + public + EmacsGC (EmacsGC source) + { + super ((short) 0); + + 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]; + + 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); + } + } + } + + /* Mark this GC as dirty. This means immutableGC will return a new + copy of this GC the next time it is called. */ + + public void + markDirty () + { + dirty = true; + } + + public EmacsGC + immutableGC () + { + if (immutableGC == null || dirty) + { + immutableGC = new EmacsGC (this); + dirty = false; + } + + return immutableGC; + }; +}; diff --git a/java/org/gnu/emacs/EmacsHandleObject.java b/java/org/gnu/emacs/EmacsHandleObject.java new file mode 100644 index 00000000000..a57a3bbdfa9 --- /dev/null +++ b/java/org/gnu/emacs/EmacsHandleObject.java @@ -0,0 +1,62 @@ +/* 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 java.lang.Object; +import java.lang.IllegalStateException; + +/* This defines something that is a so-called ``handle''. Handles + must be created by C code, and will remain existing until + destroyHandle is called. C code then refers to the handle by a + number which maps into the Java object representing the handle. + + All handle operations must be done from the Emacs thread. */ + +public abstract class EmacsHandleObject +{ + /* Whether or not this handle has been destroyed. */ + volatile boolean destroyed; + + /* The handle associated with this object. */ + public short handle; + + public + EmacsHandleObject (short handle) + { + this.handle = handle; + } + + public void + destroyHandle () throws IllegalStateException + { + synchronized (this) + { + destroyed = true; + } + } + + public boolean + isDestroyed () + { + return destroyed; + } +}; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java new file mode 100644 index 00000000000..6550e6fa2a1 --- /dev/null +++ b/java/org/gnu/emacs/EmacsNative.java @@ -0,0 +1,72 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +package org.gnu.emacs; + +import java.lang.System; + +import android.content.res.AssetManager; + +public class EmacsNative +{ + /* Set certain parameters before initializing Emacs. This proves + that libemacs.so is being loaded from Java code. + + assetManager must be the asset manager associated with the + context that is loading Emacs. It is saved and remains for the + remainder the lifetime of the Emacs process. + + filesDir must be the package's data storage location for the + current Android user. + + libDir must be the package's data storage location for native + libraries. It is used as PATH. + + emacsService must be the emacsService singleton. */ + public static native void setEmacsParams (AssetManager assetManager, + String filesDir, + String libDir, + EmacsService emacsService); + + /* Initialize Emacs with the argument array ARGV. Each argument + must contain a NULL terminated string, or else the behavior is + undefined. */ + public static native void initEmacs (String argv[]); + + /* Abort and generate a native core dump. */ + public static native void emacsAbort (); + + /* Send an ANDROID_CONFIGURE_NOTIFY event. */ + public static native void sendConfigureNotify (short window, long time, + int x, int y, int width, + int height); + + /* Send an ANDROID_KEY_PRESS event. */ + public static native void sendKeyPress (short window, long time, int state, + int keyCode); + + /* Send an ANDROID_KEY_RELEASE event. */ + public static native void sendKeyRelease (short window, long time, int state, + int keyRelease); + + static + { + System.loadLibrary ("emacs"); + }; +}; diff --git a/java/org/gnu/emacs/EmacsPaintQueue.java b/java/org/gnu/emacs/EmacsPaintQueue.java new file mode 100644 index 00000000000..5af5868d3b9 --- /dev/null +++ b/java/org/gnu/emacs/EmacsPaintQueue.java @@ -0,0 +1,138 @@ +/* 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.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; + +public class EmacsPaintQueue +{ + /* Queue of paint operations. This is modified from the Emacs + thread, and entire paint queues are periodically flushed to the + application thread where it is executed. */ + private List paintOperations; + + /* Number of operations in this queue. */ + public int numRequests; + + public + EmacsPaintQueue () + { + paintOperations = new LinkedList (); + } + + public void + run () + { + EmacsDrawable drawable, last; + Canvas canvas; + EmacsGC gc, lastGC; + int i; + Paint paint; + Rect rect, offsetRect, copyRect; + + canvas = null; + last = null; + gc = null; + paint = new Paint (); + + 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 (); + + 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.clipRect (gc.clip_rects[0]); + req.paintTo (canvas, paint, gc); + } + else + { + /* There are multiple clip rects. Android doesn't let + programs use RegionOp.UNION on the clip rectangle, + so Emacs must iterate over each intersection and + paint it manually. This seems inefficient but + thankfully Emacs never seems to use more than one + clip rect. */ + + for (i = 0; i < gc.clip_rects.length; ++i) + { + copyRect = new Rect (gc.clip_rects[i]); + + if (copyRect.intersect (rect)) + { + canvas.save (); + canvas.clipRect (copyRect); + req.paintTo (canvas, paint, gc); + canvas.restore (); + } + } + } + + drawable.damageRect (rect); + canvas.restore (); + } + } + } + + public void + appendPaintOperation (EmacsPaintReq req) + { + paintOperations.add (req); + numRequests++; + } +}; diff --git a/java/org/gnu/emacs/EmacsPaintReq.java b/java/org/gnu/emacs/EmacsPaintReq.java new file mode 100644 index 00000000000..5b14b005093 --- /dev/null +++ b/java/org/gnu/emacs/EmacsPaintReq.java @@ -0,0 +1,33 @@ +/* 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.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; + +public interface EmacsPaintReq +{ + public EmacsDrawable getDrawable (); + public EmacsGC getGC (); + public void paintTo (Canvas canvas, Paint paint, + EmacsGC immutableGC); + public Rect getRect (); +}; diff --git a/java/org/gnu/emacs/EmacsPixmap.java b/java/org/gnu/emacs/EmacsPixmap.java new file mode 100644 index 00000000000..ccd5f1e0043 --- /dev/null +++ b/java/org/gnu/emacs/EmacsPixmap.java @@ -0,0 +1,102 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +package org.gnu.emacs; + +import java.lang.IllegalArgumentException; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; + +/* Drawable backed by bitmap. */ + +public class EmacsPixmap extends EmacsHandleObject + implements EmacsDrawable +{ + /* The depth of the bitmap. This is not actually used, just defined + in order to be consistent with X. */ + public int depth, width, height; + + /* The bitmap itself. */ + public Bitmap bitmap; + + /* The canvas used to draw to BITMAP. */ + public Canvas canvas; + + public + EmacsPixmap (short handle, int colors[], 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 (colors, width, height, + Bitmap.Config.ALPHA_8); + break; + + case 24: + bitmap = Bitmap.createBitmap (colors, width, height, + Bitmap.Config.ARGB_8888); + bitmap.setHasAlpha (false); + break; + } + + this.width = width; + this.height = height; + this.depth = depth; + } + + @Override + public Canvas + lockCanvas () + { + if (canvas == null) + canvas = new Canvas (bitmap); + + return canvas; + } + + @Override + public void + unlockCanvas () + { + + } + + @Override + public void + damageRect (Rect damageRect) + { + + } + + @Override + public Bitmap + getBitmap () + { + return bitmap; + } +}; diff --git a/java/org/gnu/emacs/EmacsSdk7FontDriver.java b/java/org/gnu/emacs/EmacsSdk7FontDriver.java new file mode 100644 index 00000000000..5a8cdbfc75b --- /dev/null +++ b/java/org/gnu/emacs/EmacsSdk7FontDriver.java @@ -0,0 +1,460 @@ +/* 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 java.io.File; +import java.io.IOException; + +import java.util.LinkedList; +import java.util.List; + +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; + +import android.util.Log; + +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 + { + /* The typeface and paint. */ + public Typeface typeface; + public Paint typefacePaint; + public String familyName; + public int slant, width, weight, spacing; + + public + Sdk7Typeface (String fileName, Typeface typeface) + { + String style, testString; + int index, measured, i; + float[] widths; + + slant = NORMAL; + weight = REGULAR; + width = UNSPECIFIED; + spacing = PROPORTIONAL; + + typefacePaint = new Paint (); + typefacePaint.setTypeface (typeface); + + /* For the calls to measureText below. */ + typefacePaint.setTextSize (10.0f); + + /* Parse the file name into some useful data. First, strip off + the extension. */ + fileName = fileName.split ("\\.", 2)[0]; + + /* Next, split the file name by dashes. Everything before the + last dash is part of the family name. */ + index = fileName.lastIndexOf ("-"); + + if (index > 0) + { + style = fileName.substring (index + 1, fileName.length ()); + familyName = fileName.substring (0, index); + + /* Look for something describing the weight. */ + if (style.contains ("Thin")) + weight = THIN; + else if (style.contains ("UltraLight")) + weight = ULTRA_LIGHT; + else if (style.contains ("SemiLight")) + weight = SEMI_LIGHT; + else if (style.contains ("Light")) + weight = LIGHT; + else if (style.contains ("Medium")) + weight = MEDIUM; + else if (style.contains ("SemiBold")) + weight = SEMI_BOLD; + else if (style.contains ("ExtraBold")) + weight = EXTRA_BOLD; + else if (style.contains ("Bold")) + weight = BOLD; + else if (style.contains ("Black")) + weight = BLACK; + else if (style.contains ("UltraHeavy")) + weight = ULTRA_HEAVY; + + /* And the slant. */ + if (style.contains ("ReverseOblique")) + slant = OBLIQUE; + else if (style.contains ("ReverseItalic")) + slant = REVERSE_ITALIC; + else if (style.contains ("Italic")) + slant = ITALIC; + else if (style.contains ("Oblique")) + slant = OBLIQUE; + + /* Finally, the width. */ + if (style.contains ("UltraCondensed")) + width = ULTRA_CONDENSED; + else if (style.contains ("ExtraCondensed")) + width = EXTRA_CONDENSED; + else if (style.contains ("SemiCondensed")) + width = SEMI_CONDENSED; + else if (style.contains ("Condensed")) + width = CONDENSED; + else if (style.contains ("SemiExpanded")) + width = SEMI_EXPANDED; + else if (style.contains ("ExtraExpanded")) + width = EXTRA_EXPANDED; + else if (style.contains ("UltraExpanded")) + width = ULTRA_EXPANDED; + else if (style.contains ("Expanded")) + width = EXPANDED; + + /* Guess the spacing information. */ + testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + widths = new float[testString.length ()]; + + measured = typefacePaint.getTextWidths (testString, + 0, testString.length (), + widths); + spacing = MONO; + for (i = 0; i < measured; ++i) + { + if (i != 0 && widths[i - 1] != widths[i]) + /* This isn't a monospace font. */ + spacing = PROPORTIONAL; + } + } + else + familyName = fileName; + + Log.d (TAG, "Initialized new typeface " + familyName); + } + + @Override + public String + toString () + { + return ("Sdk7Typeface (" + + String.valueOf (familyName) + ", " + + String.valueOf (slant) + ", " + + String.valueOf (width) + ", " + + String.valueOf (weight) + ", " + + String.valueOf (spacing) + ")"); + } + }; + + private class Sdk7FontEntity extends FontEntity + { + /* The typeface. */ + public Sdk7Typeface typeface; + + public + Sdk7FontEntity (Sdk7Typeface typeface) + { + float width; + + foundry = "Google"; + family = typeface.familyName; + adstyle = null; + weight = typeface.weight; + slant = typeface.slant; + spacing = typeface.spacing; + width = typeface.width; + + this.typeface = typeface; + } + }; + + private class Sdk7FontObject extends FontObject + { + /* The typeface. */ + public Sdk7Typeface typeface; + + /* The text size. */ + public int pixelSize; + + public + Sdk7FontObject (Sdk7Typeface typeface, int pixelSize) + { + float totalWidth; + String testWidth, testString; + + this.typeface = typeface; + this.pixelSize = pixelSize; + + family = typeface.familyName; + adstyle = null; + weight = typeface.weight; + slant = typeface.slant; + spacing = typeface.spacing; + width = typeface.width; + + /* Compute the ascent and descent. */ + typeface.typefacePaint.setTextSize (pixelSize); + ascent + = Math.round (-typeface.typefacePaint.ascent ()); + descent + = Math.round (typeface.typefacePaint.descent ()); + + /* Compute the average width. */ + testString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + totalWidth = typeface.typefacePaint.measureText (testString); + + if (totalWidth > 0) + avgwidth = Math.round (totalWidth + / testString.length ()); + + /* Android doesn't expose the font average width and height + information, so this will have to do. */ + minWidth = maxWidth = avgwidth; + + /* This is different from avgwidth in the font spec! */ + averageWidth = avgwidth; + + /* Set the space width. */ + totalWidth = typeface.typefacePaint.measureText (" "); + spaceWidth = Math.round (totalWidth); + + /* Set the height and default ascent. */ + height = ascent + descent; + defaultAscent = ascent; + } + }; + + private String[] fontFamilyList; + private Sdk7Typeface[] typefaceList; + private Sdk7Typeface fallbackTypeface; + + public + EmacsSdk7FontDriver () + { + int i; + File systemFontsDirectory, fontFile; + Typeface typeface; + + systemFontsDirectory = new File ("/system/fonts"); + + fontFamilyList = systemFontsDirectory.list (); + typefaceList = new Sdk7Typeface[fontFamilyList.length]; + + /* It would be nice to avoid opening each and every font upon + startup. But that doesn't seem to be possible on + Android. */ + + for (i = 0; i < fontFamilyList.length; ++i) + { + fontFile = new File (systemFontsDirectory, + fontFamilyList[i]); + typeface = Typeface.createFromFile (fontFile); + typefaceList[i] = new Sdk7Typeface (fontFile.getName (), + typeface); + } + + fallbackTypeface = new Sdk7Typeface ("monospace", + Typeface.MONOSPACE); + } + + private boolean + checkMatch (Sdk7Typeface typeface, FontSpec fontSpec) + { + if (fontSpec.family != null + && !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; + + if (fontSpec.spacing != null + && !fontSpec.spacing.equals (typeface.spacing)) + return false; + + if (fontSpec.weight != null + && !fontSpec.weight.equals (typeface.weight)) + return false; + + if (fontSpec.width != null + && !fontSpec.width.equals (typeface.width)) + return false; + + return true; + } + + @Override + public FontEntity[] + list (FontSpec fontSpec) + { + LinkedList list; + int i; + + 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]); + } + + @Override + public FontEntity + match (FontSpec fontSpec) + { + FontEntity[] entities; + int i; + + entities = this.list (fontSpec); + + if (entities.length == 0) + return new Sdk7FontEntity (fallbackTypeface); + + return entities[0]; + } + + @Override + public String[] + listFamilies () + { + return fontFamilyList; + } + + @Override + public FontObject + openFont (FontEntity fontEntity, int pixelSize) + { + return new Sdk7FontObject (((Sdk7FontEntity) fontEntity).typeface, + pixelSize); + } + + @Override + public int + hasChar (FontSpec font, char charCode) + { + float missingGlyphWidth, emGlyphWidth, width; + Rect rect1, rect2; + Paint paint; + Sdk7FontObject fontObject; + + if (font instanceof Sdk7FontObject) + { + fontObject = (Sdk7FontObject) font; + paint = fontObject.typeface.typefacePaint; + } + else + paint = ((Sdk7FontEntity) font).typeface.typefacePaint; + + paint.setTextSize (10); + + if (Character.isWhitespace (charCode)) + return 1; + + missingGlyphWidth = paint.measureText (TOFU_STRING); + emGlyphWidth = paint.measureText (EM_STRING); + width = paint.measureText ("" + charCode); + + if (width == 0f) + return 0; + + if (width != missingGlyphWidth) + return 1; + + rect1 = new Rect (); + rect2 = new Rect (); + + paint.getTextBounds (TOFU_STRING, 0, TOFU_STRING.length (), + rect1); + paint.getTextBounds ("" + charCode, 0, 1, rect2); + return rect1.equals (rect2) ? 1 : 0; + } + + private void + textExtents1 (Sdk7FontObject font, int code, FontMetrics metrics, + 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. + + The origin is at 0, 0, and lbearing is the distance counting + rightwards from the origin to the left most pixel in the glyph + raster. rbearing is the distance between the origin and the + rightmost pixel in the glyph raster. ascent is the distance + counting upwards between the the topmost pixel in the glyph + raster. descent is the distance (once again counting + downwards) between the origin and the bottommost pixel in the + glyph raster. + + width is the distance between the origin and the origin of any + character to the right. */ + + metrics.lbearing = (short) bounds.left; + metrics.rbearing = (short) bounds.right; + metrics.ascent = (short) -bounds.top; + metrics.descent = (short) bounds.bottom; + metrics.width = (short) Math.round (width[0]); + } + + @Override + public void + textExtents (FontObject font, int code[], FontMetrics fontMetrics[]) + { + int i; + Paint paintCache; + Rect boundsCache; + Sdk7FontObject fontObject; + + 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], + paintCache, boundsCache); + } + + @Override + public int + encodeChar (FontObject fontObject, char charCode) + { + return charCode; + } +}; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java new file mode 100644 index 00000000000..311226e6f7e --- /dev/null +++ b/java/org/gnu/emacs/EmacsService.java @@ -0,0 +1,384 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +package org.gnu.emacs; + +import java.lang.Runnable; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +import android.graphics.Canvas; +import android.graphics.Bitmap; +import android.graphics.Point; + +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.util.Log; + +class Holder +{ + T thing; +}; + +/* EmacsService is the service that starts the thread running Emacs + and handles requests by that Emacs instance. */ + +public class EmacsService extends Service +{ + public static final String TAG = "EmacsService"; + public static final int MAX_PENDING_REQUESTS = 256; + public static volatile EmacsService SERVICE; + + private EmacsThread thread; + 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 (); + }; + + @Override + public int + onStartCommand (Intent intent, int flags, int startId) + { + return START_NOT_STICKY; + } + + @Override + public IBinder + onBind (Intent intent) + { + return null; + } + + @TargetApi (Build.VERSION_CODES.GINGERBREAD) + String + getLibraryDirectory () + { + int apiLevel; + Context context; + + context = getApplicationContext (); + apiLevel = android.os.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 () + { + AssetManager manager; + Context app_context; + String filesDir, libDir; + + SERVICE = this; + handler = new Handler (Looper.getMainLooper ()); + manager = getAssets (); + app_context = getApplicationContext (); + + try + { + /* Configure Emacs with the asset manager and other necessary + parameters. */ + filesDir = app_context.getFilesDir ().getCanonicalPath (); + libDir = getLibraryDirectory (); + + Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir + + " and libDir = " + libDir); + + EmacsNative.setEmacsParams (manager, filesDir, libDir, + this); + + /* Start the thread that runs Emacs. */ + thread = new EmacsThread (this); + thread.start (); + } + catch (IOException exception) + { + EmacsNative.emacsAbort (); + return; + } + } + + + + /* Functions from here on must only be called from the Emacs + thread. */ + + void + runOnUiThread (Runnable runnable) + { + handler.post (runnable); + } + + EmacsView + getEmacsView (final EmacsWindow window) + { + Runnable runnable; + final Holder view; + + view = new Holder (); + + runnable = new Runnable () { + public void + run () + { + synchronized (this) + { + view.thing = new EmacsView (window); + notify (); + } + } + }; + + synchronized (runnable) + { + runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } + } + + 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 + 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 (); + } + + public void + fillPolygon (EmacsDrawable drawable, EmacsGC gc, + Point points[]) + { + EmacsPaintReq req; + + ensurePaintQueue (); + + req = new EmacsFillPolygon (drawable, points, + gc.immutableGC ()); + paintQueue.appendPaintOperation (req); + checkFlush (); + } + + public void + drawRectangle (EmacsDrawable drawable, EmacsGC gc, + int x, int y, int width, int height) + { + EmacsPaintReq req; + + 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 ()); + paintQueue.appendPaintOperation (req); + checkFlush (); + } + + 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 (); + } + + 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 (); + } + + public void + copyArea (EmacsDrawable srcDrawable, EmacsDrawable dstDrawable, + EmacsGC gc, + 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 (); + } + + public void + clearWindow (EmacsWindow window) + { + window.clearWindow (); + } + + public void + clearArea (EmacsWindow window, int x, int y, int width, + int height) + { + window.clearArea (x, y, width, height); + } +}; diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java new file mode 100644 index 00000000000..194f6ad37a3 --- /dev/null +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -0,0 +1,83 @@ +/* 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.SurfaceView; +import android.view.SurfaceHolder; + +import android.graphics.Canvas; +import android.graphics.Rect; + +public class EmacsSurfaceView extends SurfaceView +{ + private boolean created; + + public + EmacsSurfaceView (final EmacsView view) + { + super (view.getContext ()); + + getHolder ().addCallback (new SurfaceHolder.Callback () { + @Override + public void + surfaceChanged (SurfaceHolder holder, int format, + int width, int height) + { + + } + + @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 (); + } + + @Override + public void + surfaceDestroyed (SurfaceHolder holder) + { + created = false; + } + }); + } + + public boolean + isCreated () + { + return created; + } + + public Canvas + lockCanvas (Rect damage) + { + return getHolder ().lockCanvas (damage); + } + + public void + unlockCanvasAndPost (Canvas canvas) + { + getHolder ().unlockCanvasAndPost (canvas); + } +}; diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java new file mode 100644 index 00000000000..e9281a13391 --- /dev/null +++ b/java/org/gnu/emacs/EmacsThread.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 java.lang.Thread; + +public class EmacsThread extends Thread +{ + EmacsService context; + + public + EmacsThread (EmacsService service) + { + context = service; + } + + public void + run () + { + String args[]; + + args = new String[] { "android-emacs", }; + + /* Run the native code now. */ + EmacsNative.initEmacs (args); + } +}; 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; + } +}; diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java new file mode 100644 index 00000000000..28db04a261d --- /dev/null +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -0,0 +1,336 @@ +/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- + +Copyright (C) 2023 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +package org.gnu.emacs; + +import java.lang.IllegalStateException; +import java.util.ArrayList; +import java.util.List; + +import android.graphics.Rect; +import android.graphics.Canvas; +import android.graphics.Bitmap; +import android.graphics.Point; + +import android.view.View; +import android.view.ViewGroup; +import android.view.KeyEvent; + +/* This defines a window, which is a handle. Windows represent a + rectangular subset of the screen with their own contents. + + Windows either have a parent window, in which case their views are + attached to the parent's view, or are "floating", in which case + their views are attached to the parent activity (if any), else + nothing. + + Views are also drawables, meaning they can accept drawing + requests. */ + +public class EmacsWindow extends EmacsHandleObject + implements EmacsDrawable +{ + /* The view associated with the window. */ + public EmacsView view; + + /* The geometry of the window. */ + private Rect rect; + + /* The parent window, or null if it is the root window. */ + private EmacsWindow parent; + + /* List of all children in stacking order. This must be kept + consistent! */ + private ArrayList children; + + /* The EmacsActivity currently attached, if it exists. */ + private EmacsActivity attached; + + /* The window background scratch GC. foreground is always the + window background. */ + private EmacsGC scratchGC; + + public + EmacsWindow (short handle, final EmacsWindow parent, int x, int y, + int width, int height) + { + super (handle); + + rect = new Rect (x, y, x + width, y + height); + + /* Create the view from the context's UI thread. */ + view = EmacsService.SERVICE.getEmacsView (this); + this.parent = parent; + children = new ArrayList (); + + /* The window is unmapped by default. */ + view.setVisibility (View.GONE); + + /* If parent is the root window, notice that there are new + children available for interested activites to pick up. */ + if (parent == null) + EmacsService.SERVICE.noticeAvailableChild (this); + else + { + /* Otherwise, directly add this window as a child of that + window's view. */ + synchronized (parent) + { + parent.children.add (this); + parent.view.post (new Runnable () { + @Override + public void + run () + { + parent.view.addView (view); + } + }); + } + } + + scratchGC = new EmacsGC ((short) 0); + } + + public void + changeWindowBackground (int pixel) + { + /* scratchGC is used as the argument to a FillRectangles req. */ + scratchGC.foreground = pixel; + scratchGC.markDirty (); + } + + public Rect + getGeometry () + { + synchronized (this) + { + /* Huh, this is it. */ + return rect; + } + } + + @Override + public void + destroyHandle () throws IllegalStateException + { + synchronized (this) + { + if (!children.isEmpty ()) + throw new IllegalStateException ("Trying to destroy window with " + + "children!"); + } + + /* Notice that the child has been destroyed. */ + EmacsService.SERVICE.noticeChildDestroyed (this); + + /* Remove the view from its parent and make it invisible. */ + view.post (new Runnable () { + public void + run () + { + view.setVisibility (View.GONE); + + if (view.getParent () != null) + ((ViewGroup) view.getParent ()).removeView (view); + + if (attached != null) + attached.makeAvailable (); + } + }); + + super.destroyHandle (); + } + + public void + setActivity (EmacsActivity activity) + { + synchronized (this) + { + activity = activity; + } + } + + public void + viewLayout (int left, int top, int right, int bottom) + { + synchronized (this) + { + rect.left = left; + rect.top = top; + rect.right = right; + rect.bottom = bottom; + + EmacsNative.sendConfigureNotify (this.handle, + System.currentTimeMillis (), + left, top, rect.width (), + rect.height ()); + } + } + + public void + requestViewLayout () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.requestLayout (); + } + }); + } + + public void + resizeWindow (int width, int height) + { + synchronized (this) + { + rect.right = rect.left + width; + rect.bottom = rect.top + height; + } + } + + public void + moveWindow (int x, int y) + { + int width, height; + + synchronized (this) + { + width = rect.width (); + height = rect.height (); + + rect.left = x; + rect.top = y; + rect.right = x + width; + rect.bottom = y + height; + + requestViewLayout (); + } + } + + public void + mapWindow () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.setVisibility (View.VISIBLE); + } + }); + } + + public void + unmapWindow () + { + view.post (new Runnable () { + @Override + public void + run () + { + view.setVisibility (View.GONE); + } + }); + } + + @Override + public Canvas + lockCanvas () + { + if (view.canvas != null) + return view.canvas; + + return null; + } + + @Override + public void + unlockCanvas () + { + + } + + @Override + public void + damageRect (Rect damageRect) + { + view.damageRect (damageRect); + } + + public void + swapBuffers () + { + /* Before calling swapBuffers, make sure to flush the paint + queue. */ + EmacsService.SERVICE.flushPaintQueue (); + view.post (new Runnable () { + @Override + public void + run () + { + view.swapBuffers (); + } + }); + } + + public void + clearWindow () + { + synchronized (this) + { + EmacsService.SERVICE.fillRectangle (this, scratchGC, + 0, 0, rect.width (), + rect.height ()); + } + } + + public void + clearArea (int x, int y, int width, int height) + { + EmacsService.SERVICE.fillRectangle (this, scratchGC, + x, y, width, height); + } + + @Override + public Bitmap + getBitmap () + { + return view.bitmap; + } + + public void + onKeyDown (int keyCode, KeyEvent event) + { + EmacsNative.sendKeyPress (this.handle, + event.getEventTime (), + event.getModifiers (), + keyCode); + } + + public void + onKeyUp (int keyCode, KeyEvent event) + { + EmacsNative.sendKeyRelease (this.handle, + event.getEventTime (), + event.getModifiers (), + keyCode); + } +}; -- cgit v1.2.1