emacs/src/androidterm.c

6918 lines
191 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Communication module for Android terminals.
Copyright (C) 2023-2024 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <config.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <semaphore.h>
#include "lisp.h"
#include "androidterm.h"
#include "keyboard.h"
#include "blockinput.h"
#include "android.h"
#include "buffer.h"
#include "window.h"
#include "textconv.h"
#include "coding.h"
#include "pdumper.h"
/* This is a chain of structures for all the X displays currently in
use. */
struct android_display_info *x_display_list;
/* Android terminal interface functions. */
#ifndef ANDROID_STUBIFY
#include <android/log.h>
/* Non-zero means that a HELP_EVENT has been generated since Emacs
start. */
static bool any_help_event_p;
/* Counters for tallying up scroll wheel events if
mwheel_coalesce_scroll_events is true. */
static double wheel_event_x, wheel_event_y;
enum
{
ANDROID_EVENT_NORMAL,
ANDROID_EVENT_GOTO_OUT,
ANDROID_EVENT_DROP,
};
/* Find the frame whose window has the identifier WDESC.
This is like x_window_to_frame in xterm.c, except that DPYINFO may
be NULL, as there is only at most one Android display, and is only
specified in order to stay consistent with X. */
static struct frame *
android_window_to_frame (struct android_display_info *dpyinfo,
android_window wdesc)
{
Lisp_Object tail, frame;
struct frame *f;
if (wdesc == ANDROID_NONE)
return NULL;
FOR_EACH_FRAME (tail, frame)
{
f = XFRAME (frame);
if (!FRAME_ANDROID_P (f))
continue;
if (FRAME_ANDROID_WINDOW (f) == wdesc)
return f;
}
return NULL;
}
static void
android_clear_frame (struct frame *f)
{
/* Clearing the frame will erase any cursor, so mark them all as no
longer visible. */
mark_window_cursors_off (XWINDOW (FRAME_ROOT_WINDOW (f)));
android_clear_window (FRAME_ANDROID_DRAWABLE (f));
}
static void
android_show_hourglass (struct frame *f)
{
struct android_output *x;
/* This isn't implemented like X because a window brings alongside
too many unneeded resources. */
x = FRAME_ANDROID_OUTPUT (f);
/* If the hourglass window is mapped inside a popup menu, input
could be lost if the menu is popped down and the grab is
relinquished, but the hourglass window is still up. Just
avoid displaying the hourglass at all while popups are
active. */
if (popup_activated ())
return;
x->hourglass = true;
if (!f->pointer_invisible)
android_define_cursor (FRAME_ANDROID_WINDOW (f),
x->hourglass_cursor);
}
static void
android_hide_hourglass (struct frame *f)
{
struct android_output *x;
x = FRAME_ANDROID_OUTPUT (f);
x->hourglass = false;
if (!f->pointer_invisible)
android_define_cursor (FRAME_ANDROID_WINDOW (f),
x->current_cursor);
}
static void
android_flash (struct frame *f)
{
struct android_gc *gc;
struct android_gc_values values;
int rc;
fd_set fds;
block_input ();
values.function = ANDROID_GC_INVERT;
gc = android_create_gc (ANDROID_GC_FUNCTION, &values);
/* Get the height not including a menu bar widget. */
int height = FRAME_PIXEL_HEIGHT (f);
/* Height of each line to flash. */
int flash_height = FRAME_LINE_HEIGHT (f);
/* These will be the left and right margins of the rectangles. */
int flash_left = FRAME_INTERNAL_BORDER_WIDTH (f);
int flash_right = FRAME_PIXEL_WIDTH (f) - FRAME_INTERNAL_BORDER_WIDTH (f);
int width = flash_right - flash_left;
/* If window is tall, flash top and bottom line. */
if (height > 3 * FRAME_LINE_HEIGHT (f))
{
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
flash_left,
(FRAME_INTERNAL_BORDER_WIDTH (f)
+ FRAME_TOP_MARGIN_HEIGHT (f)),
width, flash_height);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
flash_left,
(height - flash_height
- FRAME_INTERNAL_BORDER_WIDTH (f)
- FRAME_BOTTOM_MARGIN_HEIGHT (f)),
width, flash_height);
}
else
/* If it is short, flash it all. */
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
flash_left, FRAME_INTERNAL_BORDER_WIDTH (f),
width, (height - 2
* FRAME_INTERNAL_BORDER_WIDTH (f)));
flush_frame (f);
struct timespec delay = make_timespec (0, 150 * 1000 * 1000);
struct timespec wakeup = timespec_add (current_timespec (), delay);
/* Keep waiting until past the time wakeup or any input gets
available. */
while (! detect_input_pending ())
{
struct timespec current = current_timespec ();
struct timespec timeout;
/* Break if result would not be positive. */
if (timespec_cmp (wakeup, current) <= 0)
break;
/* How long `select' should wait. */
timeout = make_timespec (0, 10 * 1000 * 1000);
/* Wait for some input to become available on the X
connection. */
FD_ZERO (&fds);
/* Try to wait that long--but we might wake up sooner. */
rc = pselect (0, &fds, NULL, NULL, &timeout, NULL);
/* Some input is available, exit the visible bell. */
if (rc >= 0)
break;
}
/* If window is tall, flash top and bottom line. */
if (height > 3 * FRAME_LINE_HEIGHT (f))
{
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
flash_left,
(FRAME_INTERNAL_BORDER_WIDTH (f)
+ FRAME_TOP_MARGIN_HEIGHT (f)),
width, flash_height);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
flash_left,
(height - flash_height
- FRAME_INTERNAL_BORDER_WIDTH (f)
- FRAME_BOTTOM_MARGIN_HEIGHT (f)),
width, flash_height);
}
else
/* If it is short, flash it all. */
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
flash_left, FRAME_INTERNAL_BORDER_WIDTH (f),
width, (height - 2
* FRAME_INTERNAL_BORDER_WIDTH (f)));
android_free_gc (gc);
flush_frame (f);
unblock_input ();
}
static void
android_ring_bell (struct frame *f)
{
if (visible_bell)
android_flash (f);
else
{
block_input ();
android_bell ();
unblock_input ();
}
}
static android_cursor
make_invisible_cursor (struct android_display_info *dpyinfo)
{
return android_create_font_cursor (ANDROID_XC_NULL);
}
static void
android_toggle_visible_pointer (struct frame *f, bool invisible)
{
struct android_display_info *dpyinfo;
dpyinfo = FRAME_DISPLAY_INFO (f);
if (!dpyinfo->invisible_cursor)
dpyinfo->invisible_cursor = make_invisible_cursor (dpyinfo);
if (invisible)
android_define_cursor (FRAME_ANDROID_WINDOW (f),
dpyinfo->invisible_cursor);
else
android_define_cursor (FRAME_ANDROID_WINDOW (f),
(FRAME_ANDROID_OUTPUT (f)->hourglass
? f->output_data.android->hourglass_cursor
: f->output_data.android->current_cursor));
f->pointer_invisible = invisible;
}
static void
android_toggle_invisible_pointer (struct frame *f, bool invisible)
{
block_input ();
android_toggle_visible_pointer (f, invisible);
unblock_input ();
}
/* Start an update of frame F. This function is installed as a hook
for update_begin, i.e. it is called when update_begin is called.
This function is called prior to calls to gui_update_window_begin
for each window being updated. Currently, there is nothing to do
here because all interesting stuff is done on a window basis. */
static void
android_update_begin (struct frame *f)
{
/* The frame is no longer complete, as it is in the midst of an
update. */
FRAME_ANDROID_COMPLETE_P (f) = false;
}
/* End update of frame F. This function is installed as a hook in
update_end. */
static void
android_update_end (struct frame *f)
{
/* Mouse highlight may be displayed again. */
MOUSE_HL_INFO (f)->mouse_face_defer = false;
}
static void
show_back_buffer (struct frame *f)
{
struct android_swap_info swap_info;
memset (&swap_info, 0, sizeof (swap_info));
swap_info.swap_window = FRAME_ANDROID_WINDOW (f);
swap_info.swap_action = ANDROID_COPIED;
android_swap_buffers (&swap_info, 1);
/* Now the back buffer no longer needs to be flipped. */
FRAME_ANDROID_NEED_BUFFER_FLIP (f) = false;
}
/* Flip back buffers on F if it has undrawn content. */
static void
android_flush_dirty_back_buffer_on (struct frame *f)
{
if (FRAME_GARBAGED_P (f)
|| buffer_flipping_blocked_p ()
/* If the frame is not already up to date, do not flush buffers
on input, as that will result in flicker. */
|| !FRAME_ANDROID_COMPLETE_P (f)
|| !FRAME_ANDROID_NEED_BUFFER_FLIP (f))
return;
show_back_buffer (f);
}
/* Convert between the modifier bits Android uses and the modifier
bits Emacs uses. */
static int
android_android_to_emacs_modifiers (struct android_display_info *dpyinfo,
int state)
{
int mod_ctrl = ctrl_modifier;
int mod_meta = meta_modifier;
int mod_alt = alt_modifier;
int mod_super = super_modifier;
Lisp_Object tem;
tem = Fget (Vx_ctrl_keysym, Qmodifier_value);
if (FIXNUMP (tem)) mod_ctrl = XFIXNUM (tem) & INT_MAX;
tem = Fget (Vx_alt_keysym, Qmodifier_value);
if (FIXNUMP (tem)) mod_alt = XFIXNUM (tem) & INT_MAX;
tem = Fget (Vx_meta_keysym, Qmodifier_value);
if (FIXNUMP (tem)) mod_meta = XFIXNUM (tem) & INT_MAX;
tem = Fget (Vx_super_keysym, Qmodifier_value);
if (FIXNUMP (tem)) mod_super = XFIXNUM (tem) & INT_MAX;
return (((state & ANDROID_CONTROL_MASK) ? mod_ctrl : 0)
| ((state & ANDROID_SHIFT_MASK) ? shift_modifier : 0)
| ((state & ANDROID_ALT_MASK) ? mod_meta : 0)
| ((state & ANDROID_SUPER_MASK) ? mod_super : 0)
| ((state & ANDROID_META_MASK) ? mod_alt : 0));
}
static int
android_emacs_to_android_modifiers (struct android_display_info *dpyinfo,
intmax_t state)
{
EMACS_INT mod_ctrl = ctrl_modifier;
EMACS_INT mod_meta = meta_modifier;
EMACS_INT mod_alt = alt_modifier;
EMACS_INT mod_super = super_modifier;
Lisp_Object tem;
tem = Fget (Vx_ctrl_keysym, Qmodifier_value);
if (FIXNUMP (tem)) mod_ctrl = XFIXNUM (tem);
tem = Fget (Vx_alt_keysym, Qmodifier_value);
if (FIXNUMP (tem)) mod_alt = XFIXNUM (tem);
tem = Fget (Vx_meta_keysym, Qmodifier_value);
if (FIXNUMP (tem)) mod_meta = XFIXNUM (tem);
tem = Fget (Vx_super_keysym, Qmodifier_value);
if (FIXNUMP (tem)) mod_super = XFIXNUM (tem);
return (((state & mod_ctrl) ? ANDROID_CONTROL_MASK : 0)
| ((state & shift_modifier) ? ANDROID_SHIFT_MASK : 0)
| ((state & mod_meta) ? ANDROID_ALT_MASK : 0)
| ((state & mod_super) ? ANDROID_SUPER_MASK : 0)
| ((state & mod_alt) ? ANDROID_META_MASK : 0));
}
static void android_frame_rehighlight (struct android_display_info *);
static void
android_lower_frame (struct frame *f)
{
android_lower_window (FRAME_ANDROID_WINDOW (f));
}
static void
android_raise_frame (struct frame *f)
{
android_raise_window (FRAME_ANDROID_WINDOW (f));
}
static void
android_new_focus_frame (struct android_display_info *dpyinfo,
struct frame *frame)
{
struct frame *old_focus;
old_focus = dpyinfo->focus_frame;
if (frame != dpyinfo->focus_frame)
{
/* Set this before calling other routines, so that they see
the correct value of x_focus_frame. */
dpyinfo->focus_frame = frame;
if (old_focus && old_focus->auto_lower)
android_lower_frame (old_focus);
if (dpyinfo->focus_frame && dpyinfo->focus_frame->auto_raise)
dpyinfo->pending_autoraise_frame = dpyinfo->focus_frame;
else
dpyinfo->pending_autoraise_frame = NULL;
}
android_frame_rehighlight (dpyinfo);
}
static void
android_focus_changed (int type, int state,
struct android_display_info *dpyinfo,
struct frame *frame, struct input_event *bufp)
{
if (type == ANDROID_FOCUS_IN)
{
if (dpyinfo->x_focus_event_frame != frame)
{
android_new_focus_frame (dpyinfo, frame);
dpyinfo->x_focus_event_frame = frame;
bufp->kind = FOCUS_IN_EVENT;
XSETFRAME (bufp->frame_or_window, frame);
}
frame->output_data.android->focus_state |= state;
}
else if (type == ANDROID_FOCUS_OUT)
{
frame->output_data.android->focus_state &= ~state;
if (dpyinfo->x_focus_event_frame == frame)
{
dpyinfo->x_focus_event_frame = 0;
android_new_focus_frame (dpyinfo, 0);
bufp->kind = FOCUS_OUT_EVENT;
XSETFRAME (bufp->frame_or_window, frame);
}
if (frame->pointer_invisible)
android_toggle_invisible_pointer (frame, false);
}
}
static void
android_detect_focus_change (struct android_display_info *dpyinfo,
struct frame *frame,
union android_event *event,
struct input_event *bufp)
{
if (!frame)
return;
switch (event->type)
{
case ANDROID_FOCUS_IN:
case ANDROID_FOCUS_OUT:
android_focus_changed (event->type, FOCUS_EXPLICIT,
dpyinfo, frame, bufp);
break;
default:
break;
}
}
static bool
android_note_mouse_movement (struct frame *frame,
struct android_motion_event *event)
{
struct android_display_info *dpyinfo;
Emacs_Rectangle *r;
if (!FRAME_ANDROID_OUTPUT (frame))
return false;
dpyinfo = FRAME_DISPLAY_INFO (frame);
dpyinfo->last_mouse_motion_frame = frame;
dpyinfo->last_mouse_motion_x = event->x;
dpyinfo->last_mouse_motion_y = event->y;
dpyinfo->last_mouse_movement_time = event->time;
/* Has the mouse moved off the glyph it was on at the last sighting? */
r = &dpyinfo->last_mouse_glyph;
if (frame != dpyinfo->last_mouse_glyph_frame
|| event->x < r->x || event->x >= r->x + (int) r->width
|| event->y < r->y || event->y >= r->y + (int) r->height)
{
frame->mouse_moved = true;
note_mouse_highlight (frame, event->x, event->y);
/* Remember which glyph we're now on. */
remember_mouse_glyph (frame, event->x, event->y, r);
dpyinfo->last_mouse_glyph_frame = frame;
return true;
}
return false;
}
static struct frame *
mouse_or_wdesc_frame (struct android_display_info *dpyinfo, int wdesc)
{
struct frame *lm_f = (gui_mouse_grabbed (dpyinfo)
? dpyinfo->last_mouse_frame
: NULL);
if (lm_f && !EQ (track_mouse, Qdropping)
&& !EQ (track_mouse, Qdrag_source))
return lm_f;
else
{
struct frame *w_f = android_window_to_frame (dpyinfo, wdesc);
/* Do not return a tooltip frame. */
if (!w_f || FRAME_TOOLTIP_P (w_f))
return EQ (track_mouse, Qdropping) ? lm_f : NULL;
else
/* When dropping it would be probably nice to raise w_f
here. */
return w_f;
}
}
static Lisp_Object
android_construct_mouse_click (struct input_event *result,
struct android_button_event *event,
struct frame *f)
{
struct android_display_info *dpyinfo;
int x, y;
dpyinfo = FRAME_DISPLAY_INFO (f);
x = event->x;
y = event->y;
/* Make the event type NO_EVENT; we'll change that when we decide
otherwise. */
result->kind = MOUSE_CLICK_EVENT;
result->code = event->button - 1;
result->timestamp = event->time;
result->modifiers = (android_android_to_emacs_modifiers (dpyinfo,
event->state)
| (event->type == ANDROID_BUTTON_RELEASE
? up_modifier : down_modifier));
XSETINT (result->x, x);
XSETINT (result->y, y);
XSETFRAME (result->frame_or_window, f);
result->arg = Qnil;
return Qnil;
}
/* Generate a TOUCHSCREEN_UPDATE_EVENT for all pressed tools in FRAME.
Return the event in IE. Do not set IE->timestamp, as that is left
to the caller. */
static void
android_update_tools (struct frame *f, struct input_event *ie)
{
struct android_touch_point *touchpoint;
ie->kind = TOUCHSCREEN_UPDATE_EVENT;
XSETFRAME (ie->frame_or_window, f);
ie->arg = Qnil;
/* Build the list of active touches. */
for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points;
touchpoint; touchpoint = touchpoint->next)
{
/* Skip touch points which originated on the tool bar. */
if (touchpoint->tool_bar_p)
continue;
ie->arg = Fcons (list3i (touchpoint->x,
touchpoint->y,
touchpoint->tool_id),
ie->arg);
}
}
/* Find and return an existing tool pressed against FRAME, identified
by POINTER_ID. Return NULL if no tool by that ID was found. */
static struct android_touch_point *
android_find_tool (struct frame *f, int pointer_id)
{
struct android_touch_point *touchpoint;
for (touchpoint = FRAME_OUTPUT_DATA (f)->touch_points;
touchpoint; touchpoint = touchpoint->next)
{
if (touchpoint->tool_id == pointer_id)
return touchpoint;
}
return NULL;
}
/* Decode STRING, an array of N little endian UTF-16 characters, into
a Lisp string. Return Qnil if the string is too large, and the
encoded string otherwise. */
static Lisp_Object
android_decode_utf16 (unsigned short *utf16, size_t n)
{
struct coding_system coding;
ptrdiff_t size;
if (ckd_mul (&size, n, sizeof *utf16))
return Qnil;
/* Set up the coding system. Decoding a UTF-16 string (with no BOM)
should not signal. */
memset (&coding, 0, sizeof coding);
setup_coding_system (Qutf_16le, &coding);
coding.source = (const unsigned char *) utf16;
decode_coding_object (&coding, Qnil, 0, 0, size,
size, Qt);
return coding.dst_object;
}
/* Handle a cursor update request for F from the input method.
MODE specifies whether or not an update should be sent immediately,
and whether or not they are needed in the future.
If MODE & ANDROID_CURSOR_UPDATE_IMMEDIATE, report the position of
F's old selected window's phys cursor now.
If MODE & ANDROID_CURSOR_UPDATE_MONITOR, set
`need_cursor_updates'. */
static void
android_request_cursor_updates (struct frame *f, int mode)
{
struct window *w;
if (mode & ANDROID_CURSOR_UPDATE_IMMEDIATE
&& WINDOWP (WINDOW_LIVE_P (f->old_selected_window)
? f->old_selected_window
: f->selected_window))
{
/* Prefer the old selected window, as its selection is what was
reported to the IME previously. */
w = XWINDOW (WINDOW_LIVE_P (f->old_selected_window)
? f->old_selected_window
: f->selected_window);
android_set_preeditarea (w, w->cursor.x, w->cursor.y);
}
/* Now say whether or not updates are needed in the future. */
FRAME_OUTPUT_DATA (f)->need_cursor_updates
= (mode & ANDROID_CURSOR_UPDATE_MONITOR);
}
/* Handle a single input method event EVENT, delivered to the frame
F.
Perform the text conversion action specified inside. */
static void
android_handle_ime_event (union android_event *event, struct frame *f)
{
Lisp_Object text UNINIT;
struct android_output *output;
/* First, decode the text if necessary. */
switch (event->ime.operation)
{
case ANDROID_IME_COMMIT_TEXT:
case ANDROID_IME_SET_COMPOSING_TEXT:
case ANDROID_IME_REPLACE_TEXT:
text = android_decode_utf16 (event->ime.text,
event->ime.length);
xfree (event->ime.text);
/* Return should text be long enough that it overflows ptrdiff_t.
Such circumstances are detected within android_decode_utf16. */
if (NILP (text))
return;
break;
default:
break;
}
/* Finally, perform the appropriate conversion action. */
switch (event->ime.operation)
{
case ANDROID_IME_COMMIT_TEXT:
commit_text (f, text, event->ime.position,
event->ime.counter);
break;
case ANDROID_IME_DELETE_SURROUNDING_TEXT:
delete_surrounding_text (f, event->ime.start,
event->ime.end,
event->ime.counter);
break;
case ANDROID_IME_FINISH_COMPOSING_TEXT:
if (event->ime.length == 2)
{
output = FRAME_ANDROID_OUTPUT (f);
/* A new input method has connected to Emacs. Stop
reporting changes that the previous input method has
asked to monitor. */
output->extracted_text_flags = 0;
output->extracted_text_token = 0;
output->extracted_text_hint = 0;
output->need_cursor_updates = false;
}
finish_composing_text (f, event->ime.counter,
event->ime.length == 1);
if (event->ime.length == 2)
{
/* Now cancel outstanding batch edits if a new input method
has connected. */
f->conversion.batch_edit_flags = 0;
f->conversion.batch_edit_count = 0;
}
break;
case ANDROID_IME_SET_COMPOSING_TEXT:
set_composing_text (f, text, event->ime.position,
event->ime.counter);
break;
case ANDROID_IME_SET_COMPOSING_REGION:
set_composing_region (f, event->ime.start,
event->ime.end,
event->ime.counter);
break;
case ANDROID_IME_SET_POINT:
textconv_set_point_and_mark (f, event->ime.start,
event->ime.end,
event->ime.counter);
break;
case ANDROID_IME_START_BATCH_EDIT:
start_batch_edit (f, event->ime.counter);
break;
case ANDROID_IME_END_BATCH_EDIT:
end_batch_edit (f, event->ime.counter);
break;
case ANDROID_IME_REQUEST_SELECTION_UPDATE:
request_point_update (f, event->ime.counter);
break;
case ANDROID_IME_REQUEST_CURSOR_UPDATES:
android_request_cursor_updates (f, event->ime.length);
break;
case ANDROID_IME_REPLACE_TEXT:
replace_text (f, event->ime.start, event->ime.end,
text, event->ime.position,
event->ime.counter);
break;
}
}
/* Forward declaration. */
static void android_notify_conversion (unsigned long);
static int
handle_one_android_event (struct android_display_info *dpyinfo,
union android_event *event, int *finish,
struct input_event *hold_quit)
{
union android_event configureEvent;
struct frame *f, *any, *mouse_frame;
Mouse_HLInfo *hlinfo;
union buffered_input_event inev;
int modifiers, count, do_help;
struct android_touch_point *touchpoint, **last;
Lisp_Object window;
int scroll_height;
double scroll_unit;
int keysym;
ptrdiff_t nchars, i;
struct window *w;
static struct android_compose_status compose_status;
/* It is okay for this to not resemble handle_one_xevent so much.
Differences in event handling code are much less nasty than
stuble differences in the graphics code. */
do_help = count = 0;
hlinfo = &dpyinfo->mouse_highlight;
*finish = ANDROID_EVENT_NORMAL;
any = android_window_to_frame (dpyinfo, event->xany.window);
nchars = 0;
if (any && any->wait_event_type == event->type)
any->wait_event_type = 0; /* Indicates we got it. */
EVENT_INIT (inev.ie);
switch (event->type)
{
case ANDROID_CONFIGURE_NOTIFY:
configureEvent = *event;
f = android_window_to_frame (dpyinfo,
configureEvent.xconfigure.window);
if (!f)
goto OTHER;
if (FRAME_TOOLTIP_P (f))
{
if (FRAME_PIXEL_HEIGHT (f) != configureEvent.xconfigure.height
|| FRAME_PIXEL_WIDTH (f) != configureEvent.xconfigure.width)
SET_FRAME_GARBAGED (f);
FRAME_PIXEL_HEIGHT (f) = configureEvent.xconfigure.height;
FRAME_PIXEL_WIDTH (f) = configureEvent.xconfigure.width;
}
int width = configureEvent.xconfigure.width;
int height = configureEvent.xconfigure.height;
if (CONSP (frame_size_history))
frame_size_history_extra (f, build_string ("ConfigureNotify"),
FRAME_PIXEL_WIDTH (f),
FRAME_PIXEL_HEIGHT (f),
width, height, f->new_width,
f->new_height);
/* Even if the number of character rows and columns has
not changed, the font size may have changed, so we need
to check the pixel dimensions as well. */
if (width != FRAME_PIXEL_WIDTH (f)
|| height != FRAME_PIXEL_HEIGHT (f)
|| (f->new_size_p
&& ((f->new_width >= 0 && width != f->new_width)
|| (f->new_height >= 0 && height != f->new_height))))
{
change_frame_size (f, width, height, false, true, false);
android_clear_under_internal_border (f);
SET_FRAME_GARBAGED (f);
cancel_mouse_face (f);
}
/* Now change the left and top position of this window. */
{
int old_left = f->left_pos;
int old_top = f->top_pos;
Lisp_Object frame;
XSETFRAME (frame, f);
{
android_window root;
unsigned int dummy_uint;
android_get_geometry (FRAME_ANDROID_WINDOW (f),
&root, &f->left_pos, &f->top_pos,
&dummy_uint, &dummy_uint,
&dummy_uint);
}
if (!FRAME_TOOLTIP_P (f)
&& (old_left != f->left_pos || old_top != f->top_pos))
{
inev.ie.kind = MOVE_FRAME_EVENT;
XSETFRAME (inev.ie.frame_or_window, f);
}
if (f && FRAME_OUTPUT_DATA (f)->need_cursor_updates)
{
w = XWINDOW (f->selected_window);
android_set_preeditarea (w, w->cursor.x, w->cursor.y);
}
}
goto OTHER;
case ANDROID_KEY_PRESS:
/* Set f to any. There are no ``outer windows'' on Android. */
f = any;
/* If mouse-highlight is an integer, input clears out
mouse highlighting. */
if (!hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight)
&& (any == 0
|| !EQ (any->tool_bar_window, hlinfo->mouse_face_window)
|| !EQ (any->tab_bar_window, hlinfo->mouse_face_window)))
{
mouse_frame = hlinfo->mouse_face_mouse_frame;
clear_mouse_face (hlinfo);
hlinfo->mouse_face_hidden = true;
if (mouse_frame)
android_flush_dirty_back_buffer_on (mouse_frame);
}
if (!f)
goto OTHER;
if (event->xkey.counter)
/* This event was generated by `performEditorAction'. Make
sure it is processed before any subsequent edits. */
textconv_barrier (f, event->xkey.counter);
wchar_t copy_buffer[512];
wchar_t *copy_bufptr = copy_buffer;
int copy_bufsiz = 512;
event->xkey.state
|= android_emacs_to_android_modifiers (dpyinfo,
extra_keyboard_modifiers);
modifiers = event->xkey.state;
/* In case Meta is ComposeCharacter, clear its status. According
to Markus Ehrnsperger
Markus.Ehrnsperger@lehrstuhl-bross.physik.uni-muenchen.de this
enables ComposeCharacter to work whether or not it is combined
with Meta. */
if (modifiers & ANDROID_ALT_MASK)
memset (&compose_status, 0, sizeof (compose_status));
/* Common for all keysym input events. */
XSETFRAME (inev.ie.frame_or_window, any);
inev.ie.modifiers
= android_android_to_emacs_modifiers (dpyinfo, modifiers);
inev.ie.timestamp = event->xkey.time;
keysym = event->xkey.keycode;
{
enum android_lookup_status status_return;
nchars = android_wc_lookup_string (&event->xkey, copy_bufptr,
copy_bufsiz, &keysym,
&status_return,
&compose_status);
/* android_lookup_string can't be called twice, so there's no
way to recover from buffer overflow. */
if (status_return == ANDROID_BUFFER_OVERFLOW)
goto done_keysym;
else if (status_return == ANDROID_LOOKUP_NONE)
{
/* Don't skip preedit text events. */
if (event->xkey.keycode != (uint32_t) -1)
goto done_keysym;
}
else if (status_return == ANDROID_LOOKUP_CHARS)
keysym = ANDROID_NO_SYMBOL;
else if (status_return != ANDROID_LOOKUP_KEYSYM
&& status_return != ANDROID_LOOKUP_BOTH)
emacs_abort ();
/* Deal with pre-edit text events. On Android, these are
simply encoded as events with associated strings and a
keycode set to ``-1''. */
if (event->xkey.keycode == (uint32_t) -1)
{
inev.ie.kind = PREEDIT_TEXT_EVENT;
inev.ie.arg = Qnil;
/* If text was looked up, decode it and make it the
preedit text. */
if (status_return == ANDROID_LOOKUP_CHARS && nchars)
{
copy_bufptr[nchars] = 0;
inev.ie.arg = from_unicode_buffer (copy_bufptr);
}
goto done_keysym;
}
}
/* If a compose sequence is in progress, we break here.
Otherwise, chars_matched is always 0. */
if (compose_status.chars_matched > 0 && nchars == 0)
break;
memset (&compose_status, 0, sizeof (compose_status));
if (nchars == 1 && copy_bufptr[0] >= 32)
{
/* Deal with characters. */
if (copy_bufptr[0] < 128)
inev.ie.kind = ASCII_KEYSTROKE_EVENT;
else
inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
inev.ie.code = copy_bufptr[0];
}
else if (nchars < 2 && keysym)
{
/* If the key is a modifier key, just return. */
if (ANDROID_IS_MODIFIER_KEY (keysym))
goto done_keysym;
/* Next, deal with special ``characters'' by giving the
keycode to keyboard.c. */
inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
inev.ie.code = keysym;
}
else
{
/* Finally, deal with strings. */
for (i = 0; i < nchars; ++i)
{
inev.ie.kind = (SINGLE_BYTE_CHAR_P (copy_bufptr[i])
? ASCII_KEYSTROKE_EVENT
: MULTIBYTE_CHAR_KEYSTROKE_EVENT);
inev.ie.code = copy_bufptr[i];
/* If the character is actually '\n', then change this
to RET. */
if (copy_bufptr[i] == '\n')
{
inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
inev.ie.code = 66;
}
kbd_buffer_store_buffered_event (&inev, hold_quit);
}
count += nchars;
inev.ie.kind = NO_EVENT; /* Already stored above. */
}
goto done_keysym;
done_keysym:
/* Now proceed to tell the input method the current position of
the cursor, if required. */
if (f && FRAME_OUTPUT_DATA (f)->need_cursor_updates)
{
w = XWINDOW (f->selected_window);
android_set_preeditarea (w, w->cursor.x, w->cursor.y);
}
goto OTHER;
case ANDROID_FOCUS_IN:
case ANDROID_FOCUS_OUT:
android_detect_focus_change (dpyinfo, any, event, &inev.ie);
goto OTHER;
case ANDROID_WINDOW_ACTION:
/* This is a special event sent by android_run_in_emacs_thread
used to make Android run stuff. */
if (!event->xaction.window && !event->xaction.action)
/* Don't run queries here, as it may run inside editor
commands, which can expose an inconsistent view of buffer
contents to the input method during command execution.
Instead, wait for Emacs to return to `android_select'. */
goto OTHER;
f = any;
if (event->xaction.action == 0)
{
/* Action 0 either means that a window has been destroyed
and its associated frame should be as well. */
if (event->xaction.window)
{
if (!f)
goto OTHER;
inev.ie.kind = DELETE_WINDOW_EVENT;
XSETFRAME (inev.ie.frame_or_window, f);
}
}
case ANDROID_ENTER_NOTIFY:
f = any;
if (f)
android_note_mouse_movement (f, &event->xmotion);
goto OTHER;
case ANDROID_MOTION_NOTIFY:
previous_help_echo_string = help_echo_string;
help_echo_string = Qnil;
if (hlinfo->mouse_face_hidden)
{
hlinfo->mouse_face_hidden = false;
clear_mouse_face (hlinfo);
}
f = any;
if (f)
{
/* Maybe generate a SELECT_WINDOW_EVENT for
`mouse-autoselect-window' but don't let popup menus
interfere with this (Bug#1261). */
if (!NILP (Vmouse_autoselect_window)
&& !popup_activated ()
/* Don't switch if we're currently in the minibuffer.
This tries to work around problems where the
minibuffer gets unselected unexpectedly, and where
you then have to move your mouse all the way down to
the minibuffer to select it. */
&& !MINI_WINDOW_P (XWINDOW (selected_window))
/* With `focus-follows-mouse' non-nil create an event
also when the target window is on another frame. */
&& (f == XFRAME (selected_frame)
|| !NILP (focus_follows_mouse)))
{
static Lisp_Object last_mouse_window;
Lisp_Object window
= window_from_coordinates (f, event->xmotion.x,
event->xmotion.y, 0,
false, false, false);
/* A window will be autoselected only when it is not
selected now and the last mouse movement event was
not in it. The remainder of the code is a bit vague
wrt what a "window" is. For immediate autoselection,
the window is usually the entire window but for GTK
where the scroll bars don't count. For delayed
autoselection the window is usually the window's text
area including the margins. */
if (WINDOWP (window)
&& !EQ (window, last_mouse_window)
&& !EQ (window, selected_window))
{
inev.ie.kind = SELECT_WINDOW_EVENT;
inev.ie.frame_or_window = window;
}
/* Remember the last window where we saw the mouse. */
last_mouse_window = window;
}
if (!android_note_mouse_movement (f, &event->xmotion))
help_echo_string = previous_help_echo_string;
}
/* If the contents of the global variable help_echo_string
has changed, generate a HELP_EVENT. */
if (!NILP (help_echo_string)
|| !NILP (previous_help_echo_string))
do_help = 1;
if (f)
android_flush_dirty_back_buffer_on (f);
goto OTHER;
case ANDROID_LEAVE_NOTIFY:
f = any;
if (f)
{
/* Now clear dpyinfo->last_mouse_motion_frame, or
gui_redo_mouse_highlight will end up highlighting the
last known position of the mouse if a tooltip frame is
later unmapped. */
if (f == dpyinfo->last_mouse_motion_frame)
dpyinfo->last_mouse_motion_frame = NULL;
/* Something similar applies to
dpyinfo->last_mouse_glyph_frame. */
if (f == dpyinfo->last_mouse_glyph_frame)
dpyinfo->last_mouse_glyph_frame = NULL;
if (f == hlinfo->mouse_face_mouse_frame)
{
/* If we move outside the frame, then we're
certainly no longer on any text in the frame. */
clear_mouse_face (hlinfo);
hlinfo->mouse_face_mouse_frame = 0;
android_flush_dirty_back_buffer_on (f);
}
/* Generate a nil HELP_EVENT to cancel a help-echo.
Do it only if there's something to cancel.
Otherwise, the startup message is cleared when
the mouse leaves the frame. */
if (any_help_event_p
/* But never if `mouse-drag-and-drop-region' is in
progress, since that results in the tooltip being
dismissed when the mouse moves on top. */
&& !((EQ (track_mouse, Qdrag_source)
|| EQ (track_mouse, Qdropping))
&& gui_mouse_grabbed (dpyinfo)))
do_help = -1;
}
goto OTHER;
case ANDROID_EXPOSE:
f = any;
if (f)
{
if (!FRAME_VISIBLE_P (f))
{
f->output_data.android->has_been_visible = true;
SET_FRAME_GARBAGED (f);
}
if (!FRAME_GARBAGED_P (f))
{
expose_frame (f, event->xexpose.x, event->xexpose.y,
event->xexpose.width, event->xexpose.height);
show_back_buffer (f);
}
}
goto OTHER;
case ANDROID_BUTTON_PRESS:
case ANDROID_BUTTON_RELEASE:
/* If we decide we want to generate an event to be seen
by the rest of Emacs, we put it here. */
f = any;
Lisp_Object tab_bar_arg = Qnil;
bool tab_bar_p = false;
bool tool_bar_p = false;
dpyinfo->last_mouse_glyph_frame = NULL;
f = mouse_or_wdesc_frame (dpyinfo, event->xbutton.window);
if (f && event->xbutton.type == ANDROID_BUTTON_PRESS
&& !popup_activated ()
/* && !x_window_to_scroll_bar (event->xbutton.display, */
/* event->xbutton.window, 2) */
&& !FRAME_NO_ACCEPT_FOCUS (f))
{
/* When clicking into a child frame or when clicking
into a parent frame with the child frame selected and
`no-accept-focus' is not set, select the clicked
frame. */
struct frame *hf = dpyinfo->highlight_frame;
if (FRAME_PARENT_FRAME (f) || (hf && frame_ancestor_p (f, hf)))
{
android_set_input_focus (FRAME_ANDROID_WINDOW (f),
event->xbutton.time);
if (FRAME_PARENT_FRAME (f))
android_raise_window (FRAME_ANDROID_WINDOW (f));
}
}
if (f)
{
/* Is this in the tab-bar? */
if (WINDOWP (f->tab_bar_window)
&& WINDOW_TOTAL_LINES (XWINDOW (f->tab_bar_window)))
{
Lisp_Object window;
int x = event->xbutton.x;
int y = event->xbutton.y;
window = window_from_coordinates (f, x, y, 0, true, true, true);
tab_bar_p = EQ (window, f->tab_bar_window);
if (tab_bar_p)
{
tab_bar_arg = handle_tab_bar_click
(f, x, y, (event->xbutton.type
== ANDROID_BUTTON_PRESS),
android_android_to_emacs_modifiers (dpyinfo,
event->xbutton.state));
android_flush_dirty_back_buffer_on (f);
}
}
/* Is this in the tool-bar? */
if (WINDOWP (f->tool_bar_window)
&& WINDOW_TOTAL_LINES (XWINDOW (f->tool_bar_window)))
{
Lisp_Object window;
int x = event->xbutton.x;
int y = event->xbutton.y;
window = window_from_coordinates (f, x, y, 0, true, true, true);
tool_bar_p = (EQ (window, f->tool_bar_window)
&& ((event->xbutton.type
!= ANDROID_BUTTON_RELEASE)
|| f->last_tool_bar_item != -1));
if (tool_bar_p && event->xbutton.button < 4)
{
handle_tool_bar_click
(f, x, y, (event->xbutton.type
== ANDROID_BUTTON_PRESS),
android_android_to_emacs_modifiers (dpyinfo,
event->xbutton.state));
android_flush_dirty_back_buffer_on (f);
}
}
if (!(tab_bar_p && NILP (tab_bar_arg)) && !tool_bar_p)
if (! popup_activated ())
{
android_construct_mouse_click (&inev.ie, &event->xbutton, f);
if (!NILP (tab_bar_arg))
inev.ie.arg = tab_bar_arg;
}
}
if (event->type == ANDROID_BUTTON_PRESS)
{
dpyinfo->grabbed |= (1 << event->xbutton.button);
dpyinfo->last_mouse_frame = f;
if (f && !tab_bar_p)
f->last_tab_bar_item = -1;
if (f && !tool_bar_p)
f->last_tool_bar_item = -1;
}
else
dpyinfo->grabbed &= ~(1 << event->xbutton.button);
/* Ignore any mouse motion that happened before this event;
any subsequent mouse-movement Emacs events should reflect
only motion after the ButtonPress/Release. */
if (f != 0)
f->mouse_moved = false;
goto OTHER;
/* Touch events. The events here don't parallel X so much. */
case ANDROID_TOUCH_DOWN:
if (!any)
goto OTHER;
/* This event is sent when a tool is put on the screen. X and Y
are the location of the finger, and pointer_id identifies the
tool for as long as it is still held down. First, see if the
touch point already exists and can be reused (this shouldn't
happen, but be safe.) */
touchpoint = android_find_tool (any, event->touch.pointer_id);
if (touchpoint)
{
/* Simply update the tool position and send an update. */
touchpoint->x = event->touch.x;
touchpoint->y = event->touch.y;
android_update_tools (any, &inev.ie);
inev.ie.timestamp = event->touch.time;
goto OTHER;
}
/* Otherwise, link a new touchpoint onto the output's list of
pressed tools. */
touchpoint = xmalloc (sizeof *touchpoint);
touchpoint->tool_id = event->touch.pointer_id;
touchpoint->x = event->touch.x;
touchpoint->y = event->touch.y;
touchpoint->next = FRAME_OUTPUT_DATA (any)->touch_points;
touchpoint->tool_bar_p = false;
FRAME_OUTPUT_DATA (any)->touch_points = touchpoint;
/* Figure out whether or not the tool was pressed on the tool
bar. Note that the code which runs when it was is more or
less an abuse of the mouse highlight machinery, but it works
well enough in practice. */
if (WINDOWP (any->tool_bar_window)
&& WINDOW_TOTAL_LINES (XWINDOW (any->tool_bar_window)))
{
Lisp_Object window;
int x = event->touch.x;
int y = event->touch.y;
window = window_from_coordinates (any, x, y, 0, true,
true, true);
/* If this touch has started in the tool bar, do not
send it to Lisp. Instead, simulate a tool bar
click, releasing it once it goes away. */
if (EQ (window, any->tool_bar_window))
{
/* Call note_mouse_highlight on the tool bar
item. Otherwise, get_tool_bar_item will
return 1.
This is not necessary when mouse-highlight is
nil. */
if (!NILP (Vmouse_highlight))
{
/* Clear the pointer invisible flag to always make
note_mouse_highlight do its thing. */
any->pointer_invisible = false;
note_mouse_highlight (any, x, y);
/* Always allow future mouse motion to
update the mouse highlight, no matter
where it is. */
memset (&dpyinfo->last_mouse_glyph, 0,
sizeof dpyinfo->last_mouse_glyph);
dpyinfo->last_mouse_glyph_frame = any;
}
handle_tool_bar_click (any, x, y, true, 0);
/* Flush any changes made by that to the front
buffer. */
android_flush_dirty_back_buffer_on (any);
/* Mark the touch point as being grabbed by the tool
bar. */
touchpoint->tool_bar_p = true;
goto OTHER;
}
}
/* Now generate the Emacs event. */
inev.ie.kind = TOUCHSCREEN_BEGIN_EVENT;
inev.ie.timestamp = event->touch.time;
XSETFRAME (inev.ie.frame_or_window, any);
XSETINT (inev.ie.x, event->touch.x);
XSETINT (inev.ie.y, event->touch.y);
XSETINT (inev.ie.arg, event->touch.pointer_id);
goto OTHER;
case ANDROID_TOUCH_MOVE:
if (!any)
goto OTHER;
/* Look for the tool that moved. */
touchpoint = android_find_tool (any, event->touch.pointer_id);
/* If it doesn't exist or has been grabbed by the tool bar, skip
processing this event. */
if (!touchpoint || touchpoint->tool_bar_p)
goto OTHER;
/* Otherwise, update the position and send the update event. */
touchpoint->x = event->touch.x;
touchpoint->y = event->touch.y;
android_update_tools (any, &inev.ie);
inev.ie.timestamp = event->touch.time;
goto OTHER;
case ANDROID_TOUCH_UP:
if (!any)
goto OTHER;
/* Now find and unlink the tool in question. */
last = &FRAME_OUTPUT_DATA (any)->touch_points;
while ((touchpoint = *last))
{
if (touchpoint->tool_id == event->touch.pointer_id)
{
*last = touchpoint->next;
if (touchpoint->tool_bar_p)
{
xfree (touchpoint);
/* Do what is necessary to release the tool bar and
possibly trigger a click. */
if (any->last_tool_bar_item != -1)
handle_tool_bar_click (any, event->touch.x,
event->touch.y, false,
0);
/* Cancel any outstanding mouse highlight. */
note_mouse_highlight (any, -1, -1);
android_flush_dirty_back_buffer_on (any);
goto OTHER;
}
/* The tool was unlinked. Free it and generate the
appropriate Emacs event (assuming that it was not
grabbed by the tool bar). */
xfree (touchpoint);
inev.ie.kind = TOUCHSCREEN_END_EVENT;
inev.ie.timestamp = event->touch.time;
/* Report whether the sequence has been canceled. */
if (event->touch.flags & ANDROID_TOUCH_SEQUENCE_CANCELED)
inev.ie.modifiers = 1;
XSETFRAME (inev.ie.frame_or_window, any);
XSETINT (inev.ie.x, event->touch.x);
XSETINT (inev.ie.y, event->touch.y);
XSETINT (inev.ie.arg, event->touch.pointer_id);
/* Break out of the loop. */
goto OTHER;
}
else
last = &touchpoint->next;
}
/* No touch point was found. This shouldn't happen. */
goto OTHER;
/* Wheel motion. The events here don't parallel X because
Android doesn't have scroll valuators. */
case ANDROID_WHEEL:
if (!any)
goto OTHER;
if (fabs (event->wheel.x_delta) > 0
|| fabs (event->wheel.y_delta) > 0)
{
if (mwheel_coalesce_scroll_events)
{
if (signbit (event->wheel.x_delta)
!= signbit (wheel_event_x))
wheel_event_x = 0.0;
if (signbit (event->wheel.y_delta)
!= signbit (wheel_event_y))
wheel_event_y = 0.0;
/* Tally up deltas until one of them exceeds 1.0. */
wheel_event_x += event->wheel.x_delta;
wheel_event_y += event->wheel.y_delta;
if (fabs (wheel_event_x) < 1.0
&& fabs (wheel_event_y) < 1.0)
goto OTHER;
}
else
{
/* Use the deltas in the event. */
wheel_event_x = event->wheel.x_delta;
wheel_event_y = event->wheel.y_delta;
}
/* Determine what kind of event to send. */
inev.ie.kind = ((fabs (wheel_event_y)
>= fabs (wheel_event_x))
? WHEEL_EVENT : HORIZ_WHEEL_EVENT);
inev.ie.timestamp = event->wheel.time;
/* Set the event coordinates. */
XSETINT (inev.ie.x, event->wheel.x);
XSETINT (inev.ie.y, event->wheel.y);
/* Set the frame. */
XSETFRAME (inev.ie.frame_or_window, any);
/* Figure out the scroll direction. */
inev.ie.modifiers = (signbit ((fabs (wheel_event_x)
>= fabs (wheel_event_y))
? wheel_event_x
: wheel_event_y)
? down_modifier : up_modifier);
/* Figure out how much to scale the deltas by. */
window = window_from_coordinates (any, event->wheel.x,
event->wheel.y, NULL,
false, false, false);
if (WINDOWP (window))
scroll_height = XWINDOW (window)->pixel_height;
else
/* EVENT_X and EVENT_Y can be outside the
frame if F holds the input grab, so fall
back to the height of the frame instead. */
scroll_height = FRAME_PIXEL_HEIGHT (any);
scroll_unit = pow (scroll_height, 2.0 / 3.0);
/* Add the keyboard modifiers. */
inev.ie.modifiers
|= android_android_to_emacs_modifiers (dpyinfo,
event->wheel.state);
/* Finally include the scroll deltas. */
inev.ie.arg = list3 (Qnil,
make_float (wheel_event_x
* scroll_unit),
make_float (wheel_event_y
* scroll_unit));
wheel_event_x = 0.0;
wheel_event_y = 0.0;
}
goto OTHER;
/* Iconification. This is vastly simpler than on X. */
case ANDROID_ICONIFIED:
if (!any)
goto OTHER;
if (FRAME_ICONIFIED_P (any))
goto OTHER;
SET_FRAME_VISIBLE (any, false);
SET_FRAME_ICONIFIED (any, true);
inev.ie.kind = ICONIFY_EVENT;
XSETFRAME (inev.ie.frame_or_window, any);
goto OTHER;
case ANDROID_DEICONIFIED:
if (!any)
goto OTHER;
if (!FRAME_ICONIFIED_P (any))
goto OTHER;
SET_FRAME_VISIBLE (any, true);
SET_FRAME_ICONIFIED (any, false);
inev.ie.kind = DEICONIFY_EVENT;
XSETFRAME (inev.ie.frame_or_window, any);
goto OTHER;
/* Context menu handling. */
case ANDROID_CONTEXT_MENU:
if (dpyinfo->menu_event_id == -1
/* Previously displayed popup menus might generate events
after dismissal, which might interfere.
`current_menu_serial' is always set to an identifier
identifying the last context menu to be displayed. */
&& event->menu.menu_event_serial == current_menu_serial)
dpyinfo->menu_event_id = event->menu.menu_event_id;
goto OTHER;
/* Input method events. textconv.c functions are called here to
queue events, which are then executed in a safe context
inside keyboard.c. */
case ANDROID_INPUT_METHOD:
if (!any)
{
/* Free any text allocated for this event. */
xfree (event->ime.text);
/* If edits associated with this event haven't been
processed yet, signal their completion to avoid delays
the next time a call to `android_sync_edit' is made.
If events for a deleted frame are interleaved with events
for another frame, the edit counter may be prematurely
incremented before edits associated with the other frames
are processed. This is not a problem in practice. */
android_notify_conversion (event->ime.counter);
}
else
android_handle_ime_event (event, any);
goto OTHER;
case ANDROID_DND_DRAG_EVENT:
if (!any)
goto OTHER;
/* Generate a drag and drop event to convey its position. */
inev.ie.kind = DRAG_N_DROP_EVENT;
XSETFRAME (inev.ie.frame_or_window, any);
inev.ie.timestamp = ANDROID_CURRENT_TIME;
XSETINT (inev.ie.x, event->dnd.x);
XSETINT (inev.ie.y, event->dnd.y);
inev.ie.arg = Fcons (inev.ie.x, inev.ie.y);
goto OTHER;
case ANDROID_DND_URI_EVENT:
case ANDROID_DND_TEXT_EVENT:
if (!any)
{
free (event->dnd.uri_or_string);
goto OTHER;
}
/* An item was dropped over ANY, and is a file in the form of a
content or file URI or a string to be inserted. Generate an
event with this information. */
inev.ie.kind = DRAG_N_DROP_EVENT;
XSETFRAME (inev.ie.frame_or_window, any);
inev.ie.timestamp = ANDROID_CURRENT_TIME;
XSETINT (inev.ie.x, event->dnd.x);
XSETINT (inev.ie.y, event->dnd.y);
inev.ie.arg = Fcons ((event->type == ANDROID_DND_TEXT_EVENT
? Qtext : Quri),
android_decode_utf16 (event->dnd.uri_or_string,
event->dnd.length));
free (event->dnd.uri_or_string);
goto OTHER;
case ANDROID_NOTIFICATION_DELETED:
case ANDROID_NOTIFICATION_ACTION:
if (event->notification.type == ANDROID_NOTIFICATION_DELETED)
android_notification_deleted (&event->notification, &inev.ie);
else
{
Lisp_Object action;
action = android_decode_utf16 (event->notification.action,
event->notification.length);
android_notification_action (&event->notification, &inev.ie,
action);
}
/* Free dynamically allocated data. */
free (event->notification.tag);
free (event->notification.action);
goto OTHER;
default:
goto OTHER;
}
OTHER:
if (inev.ie.kind != NO_EVENT)
{
kbd_buffer_store_buffered_event (&inev, hold_quit);
count++;
}
if (do_help
&& !(hold_quit && hold_quit->kind != NO_EVENT))
{
Lisp_Object frame;
if (f)
XSETFRAME (frame, f);
else
frame = Qnil;
if (do_help > 0)
{
any_help_event_p = true;
gen_help_event (help_echo_string, frame, help_echo_window,
help_echo_object, help_echo_pos);
}
else
{
help_echo_string = Qnil;
gen_help_event (Qnil, frame, Qnil, Qnil, 0);
}
count++;
}
return count;
}
static int
android_read_socket (struct terminal *terminal,
struct input_event *hold_quit)
{
int count = 0;
struct android_display_info *dpyinfo;
dpyinfo = terminal->display_info.android;
block_input ();
while (android_pending ())
{
int finish;
union android_event event;
android_next_event (&event);
count += handle_one_android_event (dpyinfo, &event, &finish,
hold_quit);
if (finish == ANDROID_EVENT_GOTO_OUT)
break;
}
unblock_input ();
/* If the focus was just given to an auto-raising frame, raise it
now. */
if (dpyinfo->pending_autoraise_frame)
{
android_raise_frame (dpyinfo->pending_autoraise_frame);
dpyinfo->pending_autoraise_frame = NULL;
}
return count;
}
static void
android_frame_up_to_date (struct frame *f)
{
eassert (FRAME_ANDROID_P (f));
block_input ();
FRAME_MOUSE_UPDATE (f);
if (!buffer_flipping_blocked_p ()
&& FRAME_ANDROID_NEED_BUFFER_FLIP (f))
show_back_buffer (f);
/* The frame is now complete, as its contents have been drawn. */
FRAME_ANDROID_COMPLETE_P (f) = true;
/* Shrink the scanline buffer used by the font backend. */
sfntfont_android_shrink_scanline_buffer ();
unblock_input ();
}
static void
android_buffer_flipping_unblocked_hook (struct frame *f)
{
block_input ();
if (FRAME_ANDROID_NEED_BUFFER_FLIP (f))
show_back_buffer (f);
unblock_input ();
}
static void
android_query_frame_background_color (struct frame *f, Emacs_Color *bgcolor)
{
unsigned long background;
background = FRAME_BACKGROUND_PIXEL (f);
bgcolor->pixel = background;
android_query_colors (f, bgcolor, 1);
}
int
android_parse_color (struct frame *f, const char *color_name,
Emacs_Color *color)
{
unsigned short r, g, b;
Lisp_Object tem, tem1;
unsigned long lisp_color;
if (parse_color_spec (color_name, &r, &g, &b))
{
color->red = r;
color->green = g;
color->blue = b;
return 1;
}
tem = x_display_list->color_map;
for (; CONSP (tem); tem = XCDR (tem))
{
tem1 = XCAR (tem);
if (CONSP (tem1)
&& !xstrcasecmp (SSDATA (XCAR (tem1)), color_name))
{
lisp_color = XFIXNUM (XCDR (tem1));
color->red = RED_FROM_ULONG (lisp_color) * 257;
color->green = GREEN_FROM_ULONG (lisp_color) * 257;
color->blue = BLUE_FROM_ULONG (lisp_color) * 257;
return 1;
}
}
return 0;
}
bool
android_alloc_nearest_color (struct frame *f, Emacs_Color *color)
{
unsigned int ntsc;
gamma_correct (f, color);
if (FRAME_DISPLAY_INFO (f)->n_planes == 1)
{
/* Black and white. I think this is the luminance formula applied
by the X server on generic monochrome framebuffers. */
color->pixel = ((((30l * color->red
+ 59l * color->green
+ 11l * color->blue) >> 8)
>= (((1 << 8) -1) * 50))
? 0xffffff : 0);
}
else if (FRAME_DISPLAY_INFO (f)->n_planes <= 8)
{
/* 256 grays. */
ntsc = min (255, ((color->red * 0.299
+ color->green * 0.587
+ color->blue * 0.114)
/ 256));
color->pixel = RGB_TO_ULONG (ntsc, ntsc, ntsc);
}
else
color->pixel = RGB_TO_ULONG (color->red / 256,
color->green / 256,
color->blue / 256);
return true;
}
void
android_query_colors (struct frame *f, Emacs_Color *colors, int ncolors)
{
int i;
for (i = 0; i < ncolors; ++i)
{
colors[i].red = RED_FROM_ULONG (colors[i].pixel) * 257;
colors[i].green = GREEN_FROM_ULONG (colors[i].pixel) * 257;
colors[i].blue = BLUE_FROM_ULONG (colors[i].pixel) * 257;
}
}
static void
android_mouse_position (struct frame **fp, int insist,
Lisp_Object *bar_window,
enum scroll_bar_part *part, Lisp_Object *x,
Lisp_Object *y, Time *timestamp)
{
Lisp_Object tail, frame;
struct android_display_info *dpyinfo;
dpyinfo = FRAME_DISPLAY_INFO (*fp);
/* This is the best implementation possible on Android, where the
system doesn't let Emacs obtain any information about the mouse
pointer at all. */
if (dpyinfo->last_mouse_motion_frame)
{
*fp = dpyinfo->last_mouse_motion_frame;
*timestamp = dpyinfo->last_mouse_movement_time;
*x = make_fixnum (dpyinfo->last_mouse_motion_x);
*y = make_fixnum (dpyinfo->last_mouse_motion_y);
*bar_window = Qnil;
*part = scroll_bar_nowhere;
FOR_EACH_FRAME (tail, frame)
{
if (FRAME_ANDROID_P (XFRAME (frame)))
XFRAME (frame)->mouse_moved = false;
}
dpyinfo->last_mouse_motion_frame->mouse_moved = false;
}
}
static Lisp_Object
android_get_focus_frame (struct frame *f)
{
Lisp_Object lisp_focus;
struct frame *focus;
focus = FRAME_DISPLAY_INFO (f)->focus_frame;
if (!focus)
return Qnil;
XSETFRAME (lisp_focus, focus);
return lisp_focus;
}
static void
android_focus_frame (struct frame *f, bool noactivate)
{
/* Set the input focus to the frame's window. The system only lets
this work on child frames. */
android_set_input_focus (FRAME_ANDROID_WINDOW (f),
ANDROID_CURRENT_TIME);
}
/* The two procedures below only have to update the cursor on Android,
as there are no window borders there. */
static void
android_frame_highlight (struct frame *f)
{
gui_update_cursor (f, true);
}
static void
android_frame_unhighlight (struct frame *f)
{
gui_update_cursor (f, true);
}
static void
android_frame_rehighlight (struct android_display_info *dpyinfo)
{
struct frame *old_highlight;
old_highlight = dpyinfo->highlight_frame;
if (dpyinfo->focus_frame)
{
dpyinfo->highlight_frame
= ((FRAMEP (FRAME_FOCUS_FRAME (dpyinfo->focus_frame)))
? XFRAME (FRAME_FOCUS_FRAME (dpyinfo->focus_frame))
: dpyinfo->focus_frame);
if (!FRAME_LIVE_P (dpyinfo->highlight_frame))
{
fset_focus_frame (dpyinfo->focus_frame, Qnil);
dpyinfo->highlight_frame = dpyinfo->focus_frame;
}
}
else
dpyinfo->highlight_frame = 0;
if (dpyinfo->highlight_frame != old_highlight)
{
/* This is not yet required on Android. */
if (old_highlight)
android_frame_unhighlight (old_highlight);
if (dpyinfo->highlight_frame)
android_frame_highlight (dpyinfo->highlight_frame);
}
}
static void
android_frame_rehighlight_hook (struct frame *f)
{
android_frame_rehighlight (FRAME_DISPLAY_INFO (f));
}
static void
android_frame_raise_lower (struct frame *f, bool raise_flag)
{
if (raise_flag)
android_raise_frame (f);
else
android_lower_frame (f);
}
void
android_make_frame_visible (struct frame *f)
{
android_map_window (FRAME_ANDROID_WINDOW (f));
SET_FRAME_VISIBLE (f, true);
SET_FRAME_ICONIFIED (f, false);
}
void
android_make_frame_invisible (struct frame *f)
{
/* Don't keep the highlight on an invisible frame. */
if (FRAME_DISPLAY_INFO (f)->highlight_frame == f)
FRAME_DISPLAY_INFO (f)->highlight_frame = 0;
android_unmap_window (FRAME_ANDROID_WINDOW (f));
SET_FRAME_VISIBLE (f, false);
SET_FRAME_ICONIFIED (f, false);
}
static void
android_make_frame_visible_invisible (struct frame *f, bool visible)
{
if (visible)
android_make_frame_visible (f);
else
android_make_frame_invisible (f);
}
static void
android_fullscreen_hook (struct frame *f)
{
Lisp_Object wanted;
if (!FRAME_PARENT_FRAME (f))
{
/* Explicitly setting fullscreen is not supported on older
Android versions. */
wanted = (f->want_fullscreen == FULLSCREEN_BOTH
? Qfullscreen : Qmaximized);
if (android_set_fullscreen (FRAME_ANDROID_WINDOW (f),
EQ (wanted, Qfullscreen)))
store_frame_param (f, Qfullscreen, Qmaximized);
else
store_frame_param (f, Qfullscreen, wanted);
}
else
{
store_frame_param (f, Qfullscreen, Qnil);
/* If this is a child frame, don't keep it fullscreen
anymore. */
android_set_fullscreen (FRAME_ANDROID_WINDOW (f), false);
}
}
void
android_iconify_frame (struct frame *f)
{
/* This really doesn't work on Android. */
error ("Can't notify window manager of iconification");
}
static void
android_wait_for_event (struct frame *f, int eventtype)
{
if (!FLOATP (Vandroid_wait_for_event_timeout))
return;
int level = interrupt_input_blocked;
struct timespec tmo, tmo_at, time_now;
f->wait_event_type = eventtype;
/* Default timeout is 0.1 second. Hopefully not noticeable. */
double timeout = XFLOAT_DATA (Vandroid_wait_for_event_timeout);
time_t timeout_seconds = (time_t) timeout;
tmo = make_timespec (timeout_seconds,
(long int) ((timeout - timeout_seconds)
* 1000 * 1000 * 1000));
tmo_at = timespec_add (current_timespec (), tmo);
while (f->wait_event_type)
{
pending_signals = true;
totally_unblock_input ();
/* XTread_socket is called after unblock. */
block_input ();
interrupt_input_blocked = level;
time_now = current_timespec ();
if (timespec_cmp (tmo_at, time_now) < 0)
break;
tmo = timespec_sub (tmo_at, time_now);
if (android_select (0, NULL, NULL, NULL, &tmo) == 0)
break; /* Timeout */
}
f->wait_event_type = 0;
}
static void
android_set_window_size_1 (struct frame *f, bool change_gravity,
int width, int height)
{
if (change_gravity)
f->win_gravity = NorthWestGravity;
android_resize_window (FRAME_ANDROID_WINDOW (f), width,
height);
SET_FRAME_GARBAGED (f);
if (FRAME_VISIBLE_P (f))
{
android_wait_for_event (f, ANDROID_CONFIGURE_NOTIFY);
if (CONSP (frame_size_history))
frame_size_history_extra (f, build_string ("set_window_size_1 visible"),
FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f),
width, height, f->new_width, f->new_height);
}
else
{
if (CONSP (frame_size_history))
frame_size_history_extra (f, build_string ("set_window_size_1 "
"invisible"),
FRAME_PIXEL_WIDTH (f), FRAME_PIXEL_HEIGHT (f),
width, height, f->new_width, f->new_height);
adjust_frame_size (f, FRAME_PIXEL_TO_TEXT_WIDTH (f, width),
FRAME_PIXEL_TO_TEXT_HEIGHT (f, height),
5, 0, Qx_set_window_size_1);
}
}
void
android_set_window_size (struct frame *f, bool change_gravity,
int width, int height)
{
block_input ();
android_set_window_size_1 (f, change_gravity, width, height);
android_clear_under_internal_border (f);
/* If cursor was outside the new size, mark it as off. */
mark_window_cursors_off (XWINDOW (f->root_window));
/* Clear out any recollection of where the mouse highlighting was,
since it might be in a place that's outside the new frame size.
Actually checking whether it is outside is a pain in the neck,
so don't try--just let the highlighting be done afresh with new size. */
cancel_mouse_face (f);
unblock_input ();
do_pending_window_change (false);
}
static void
android_set_offset (struct frame *f, int xoff, int yoff,
int change_gravity)
{
if (change_gravity > 0)
{
f->top_pos = yoff;
f->left_pos = xoff;
f->size_hint_flags &= ~ (XNegative | YNegative);
if (xoff < 0)
f->size_hint_flags |= XNegative;
if (yoff < 0)
f->size_hint_flags |= YNegative;
f->win_gravity = NorthWestGravity;
}
android_move_window (FRAME_ANDROID_WINDOW (f), xoff, yoff);
}
static void
android_set_alpha (struct frame *f)
{
/* Not supported on Android. */
}
static Lisp_Object
android_new_font (struct frame *f, Lisp_Object font_object, int fontset)
{
struct font *font = XFONT_OBJECT (font_object);
int unit, font_ascent, font_descent;
if (fontset < 0)
fontset = fontset_from_font (font_object);
FRAME_FONTSET (f) = fontset;
if (FRAME_FONT (f) == font)
/* This font is already set in frame F. There's nothing more to
do. */
return font_object;
FRAME_FONT (f) = font;
FRAME_BASELINE_OFFSET (f) = font->baseline_offset;
FRAME_COLUMN_WIDTH (f) = font->average_width;
get_font_ascent_descent (font, &font_ascent, &font_descent);
FRAME_LINE_HEIGHT (f) = font_ascent + font_descent;
/* We could use a more elaborate calculation here. */
FRAME_TAB_BAR_HEIGHT (f) = FRAME_TAB_BAR_LINES (f) * FRAME_LINE_HEIGHT (f);
/* Compute character columns occupied by scrollbar.
Don't do things differently for non-toolkit scrollbars
(Bug#17163). */
unit = FRAME_COLUMN_WIDTH (f);
if (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) > 0)
FRAME_CONFIG_SCROLL_BAR_COLS (f)
= (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) + unit - 1) / unit;
else
FRAME_CONFIG_SCROLL_BAR_COLS (f) = (14 + unit - 1) / unit;
/* Don't change the size of a tip frame; there's no point in doing it
because it's done in Fx_show_tip, and it leads to problems because
the tip frame has no widget. */
if (FRAME_ANDROID_WINDOW (f) != 0 && !FRAME_TOOLTIP_P (f))
adjust_frame_size (f, FRAME_COLS (f) * FRAME_COLUMN_WIDTH (f),
FRAME_LINES (f) * FRAME_LINE_HEIGHT (f), 3,
false, Qfont);
return font_object;
}
static bool
android_bitmap_icon (struct frame *f, Lisp_Object file)
{
return false;
}
static void
android_free_pixmap_hook (struct frame *f, Emacs_Pixmap pixmap)
{
android_free_pixmap (pixmap);
}
void
android_free_frame_resources (struct frame *f)
{
struct android_display_info *dpyinfo;
Mouse_HLInfo *hlinfo;
struct android_touch_point *last, *next;
dpyinfo = FRAME_DISPLAY_INFO (f);
hlinfo = &dpyinfo->mouse_highlight;
block_input ();
free_frame_faces (f);
/* FRAME_ANDROID_WINDOW can be 0 if frame creation failed. */
if (FRAME_ANDROID_WINDOW (f))
android_destroy_window (FRAME_ANDROID_WINDOW (f));
android_free_gcs (f);
/* Free cursors. */
if (f->output_data.android->text_cursor)
android_free_cursor (f->output_data.android->text_cursor);
if (f->output_data.android->nontext_cursor)
android_free_cursor (f->output_data.android->nontext_cursor);
if (f->output_data.android->modeline_cursor)
android_free_cursor (f->output_data.android->modeline_cursor);
if (f->output_data.android->hand_cursor)
android_free_cursor (f->output_data.android->hand_cursor);
if (f->output_data.android->hourglass_cursor)
android_free_cursor (f->output_data.android->hourglass_cursor);
if (f->output_data.android->horizontal_drag_cursor)
android_free_cursor (f->output_data.android->horizontal_drag_cursor);
if (f->output_data.android->vertical_drag_cursor)
android_free_cursor (f->output_data.android->vertical_drag_cursor);
if (f->output_data.android->left_edge_cursor)
android_free_cursor (f->output_data.android->left_edge_cursor);
if (f->output_data.android->top_left_corner_cursor)
android_free_cursor (f->output_data.android->top_left_corner_cursor);
if (f->output_data.android->top_edge_cursor)
android_free_cursor (f->output_data.android->top_edge_cursor);
if (f->output_data.android->top_right_corner_cursor)
android_free_cursor (f->output_data.android->top_right_corner_cursor);
if (f->output_data.android->right_edge_cursor)
android_free_cursor (f->output_data.android->right_edge_cursor);
if (f->output_data.android->bottom_right_corner_cursor)
android_free_cursor (f->output_data.android->bottom_right_corner_cursor);
if (f->output_data.android->bottom_edge_cursor)
android_free_cursor (f->output_data.android->bottom_edge_cursor);
if (f->output_data.android->bottom_left_corner_cursor)
android_free_cursor (f->output_data.android->bottom_left_corner_cursor);
/* Free extra GCs allocated by android_setup_relief_colors. */
if (f->output_data.android->white_relief.gc)
{
android_free_gc (f->output_data.android->white_relief.gc);
f->output_data.android->white_relief.gc = 0;
}
if (f->output_data.android->black_relief.gc)
{
android_free_gc (f->output_data.android->black_relief.gc);
f->output_data.android->black_relief.gc = 0;
}
if (f == dpyinfo->focus_frame)
dpyinfo->focus_frame = 0;
if (f == dpyinfo->x_focus_event_frame)
dpyinfo->x_focus_event_frame = 0;
if (f == dpyinfo->highlight_frame)
dpyinfo->highlight_frame = 0;
if (f == hlinfo->mouse_face_mouse_frame)
reset_mouse_highlight (hlinfo);
/* These two need to be freed now that they are used to compute the
mouse position, I think. */
if (f == dpyinfo->last_mouse_motion_frame)
dpyinfo->last_mouse_motion_frame = NULL;
if (f == dpyinfo->last_mouse_frame)
dpyinfo->last_mouse_frame = NULL;
/* Free all tool presses currently active on this frame. */
next = FRAME_OUTPUT_DATA (f)->touch_points;
while (next)
{
last = next;
next = next->next;
xfree (last);
}
/* Clear this in case unblock_input reads events. */
FRAME_OUTPUT_DATA (f)->touch_points = NULL;
unblock_input ();
}
static void
android_delete_frame (struct frame *f)
{
android_free_frame_resources (f);
xfree (f->output_data.android);
f->output_data.android = NULL;
}
static void
android_delete_terminal (struct terminal *terminal)
{
error ("Cannot terminate connection to Android display server");
}
/* RIF functions. */
static void
android_scroll_run (struct window *w, struct run *run)
{
struct frame *f = XFRAME (w->frame);
int x, y, width, height, from_y, to_y, bottom_y;
/* Get frame-relative bounding box of the text display area of W,
without mode lines. Include in this box the left and right
fringe of W. */
window_box (w, ANY_AREA, &x, &y, &width, &height);
from_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->current_y);
to_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->desired_y);
bottom_y = y + height;
if (to_y < from_y)
{
/* Scrolling up. Make sure we don't copy part of the mode
line at the bottom. */
if (from_y + run->height > bottom_y)
height = bottom_y - from_y;
else
height = run->height;
}
else
{
/* Scrolling down. Make sure we don't copy over the mode line.
at the bottom. */
if (to_y + run->height > bottom_y)
height = bottom_y - to_y;
else
height = run->height;
}
block_input ();
/* Cursor off. Will be switched on again in gui_update_window_end. */
gui_clear_cursor (w);
/* To avoid sequence point problems, make sure to only call
FRAME_ANDROID_DRAWABLE once. */
android_copy_area (FRAME_ANDROID_DRAWABLE (f),
FRAME_ANDROID_WINDOW (f),
f->output_data.android->normal_gc,
x, from_y, width, height, x, to_y);
unblock_input ();
}
static void
android_after_update_window_line (struct window *w, struct glyph_row *desired_row)
{
eassert (w);
if (!desired_row->mode_line_p && !w->pseudo_window_p)
desired_row->redraw_fringe_bitmaps_p = true;
}
static void
android_flip_and_flush (struct frame *f)
{
block_input ();
if (FRAME_ANDROID_NEED_BUFFER_FLIP (f))
show_back_buffer (f);
/* The frame is complete again as its contents were just
flushed. */
FRAME_ANDROID_COMPLETE_P (f) = true;
unblock_input ();
}
static void
android_clear_rectangle (struct frame *f, struct android_gc *gc, int x,
int y, int width, int height)
{
struct android_gc_values xgcv;
android_get_gc_values (gc, (ANDROID_GC_BACKGROUND
| ANDROID_GC_FOREGROUND),
&xgcv);
android_set_foreground (gc, xgcv.background);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
x, y, width, height);
android_set_foreground (gc, xgcv.foreground);
}
static void
android_reset_clip_rectangles (struct frame *f, struct android_gc *gc)
{
android_set_clip_mask (gc, ANDROID_NONE);
}
static void
android_clip_to_row (struct window *w, struct glyph_row *row,
enum glyph_row_area area, struct android_gc *gc,
struct android_rectangle *rect_return)
{
struct android_rectangle clip_rect;
int window_x, window_y, window_width;
window_box (w, area, &window_x, &window_y, &window_width, 0);
clip_rect.x = window_x;
clip_rect.y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, row->y));
clip_rect.y = max (clip_rect.y, window_y);
clip_rect.width = window_width;
clip_rect.height = row->visible_height;
android_set_clip_rectangles (gc, 0, 0, &clip_rect, 1);
if (rect_return)
*rect_return = clip_rect;
}
static void
android_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
struct draw_fringe_bitmap_params *p)
{
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct android_gc *gc = f->output_data.android->normal_gc;
struct face *face = p->face;
struct android_rectangle clip_rect;
/* Must clip because of partially visible lines. */
android_clip_to_row (w, row, ANY_AREA, gc, &clip_rect);
if (p->bx >= 0 && !p->overlay_p)
{
/* In case the same realized face is used for fringes and for
something displayed in the text (e.g. face `region' on
mono-displays, the fill style may have been changed to
ANDROID_FILL_SOLID in
android_draw_glyph_string_background. */
if (face->stipple)
{
android_set_fill_style (face->gc, ANDROID_FILL_OPAQUE_STIPPLED);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), face->gc,
p->bx, p->by, p->nx, p->ny);
android_set_fill_style (face->gc, ANDROID_FILL_SOLID);
row->stipple_p = true;
}
else
{
android_set_background (face->gc, face->background);
android_clear_rectangle (f, face->gc, p->bx, p->by, p->nx, p->ny);
android_set_foreground (face->gc, face->foreground);
}
}
if (p->which)
{
android_drawable drawable;
char *bits;
android_pixmap pixmap, clipmask;
struct android_gc_values gcv;
unsigned long background, cursor_pixel;
int depth;
struct android_rectangle image_rect, dest;
int px, py, pwidth, pheight;
drawable = FRAME_ANDROID_DRAWABLE (f);
clipmask = ANDROID_NONE;
background = face->background;
cursor_pixel = f->output_data.android->cursor_pixel;
depth = FRAME_DISPLAY_INFO (f)->n_image_planes;
/* Intersect the destination rectangle with that of the row.
Setting a clip mask overrides the clip rectangles provided by
android_clip_to_row, so clipping must be performed by
hand. */
image_rect.x = p->x;
image_rect.y = p->y;
image_rect.width = p->wd;
image_rect.height = p->h;
if (!gui_intersect_rectangles (&clip_rect, &image_rect, &dest))
/* The entire destination rectangle falls outside the row. */
goto undo_clip;
/* Extrapolate the source rectangle from the difference between
the destination and image rectangles. */
px = dest.x - image_rect.x;
py = dest.y - image_rect.y;
pwidth = dest.width;
pheight = dest.height;
if (p->wd > 8)
bits = (char *) (p->bits + p->dh);
else
bits = (char *) p->bits + p->dh;
pixmap = android_create_pixmap_from_bitmap_data (bits, p->wd, p->h,
(p->cursor_p
? (p->overlay_p
? face->background
: cursor_pixel)
: face->foreground),
background, depth);
if (p->overlay_p)
{
clipmask = android_create_pixmap_from_bitmap_data (bits, p->wd, p->h,
1, 0, 1);
gcv.clip_mask = clipmask;
gcv.clip_x_origin = p->x;
gcv.clip_y_origin = p->y;
android_change_gc (gc, (ANDROID_GC_CLIP_MASK
| ANDROID_GC_CLIP_X_ORIGIN
| ANDROID_GC_CLIP_Y_ORIGIN),
&gcv);
}
android_copy_area (pixmap, drawable, gc, px, py,
pwidth, pheight, dest.x, dest.y);
android_free_pixmap (pixmap);
if (p->overlay_p)
{
gcv.clip_mask = ANDROID_NONE;
android_change_gc (gc, ANDROID_GC_CLIP_MASK, &gcv);
android_free_pixmap (clipmask);
}
}
undo_clip:
android_reset_clip_rectangles (f, gc);
}
/* Set S->gc to a suitable GC for drawing glyph string S in cursor
face. */
static void
android_set_cursor_gc (struct glyph_string *s)
{
if (s->font == FRAME_FONT (s->f)
&& s->face->background == FRAME_BACKGROUND_PIXEL (s->f)
&& s->face->foreground == FRAME_FOREGROUND_PIXEL (s->f)
&& !s->cmp)
s->gc = s->f->output_data.android->cursor_gc;
else
{
/* Cursor on non-default face: must merge. */
struct android_gc_values xgcv;
unsigned long mask;
xgcv.background = s->f->output_data.android->cursor_pixel;
xgcv.foreground = s->face->background;
/* If the glyph would be invisible, try a different foreground. */
if (xgcv.foreground == xgcv.background)
xgcv.foreground = s->face->foreground;
if (xgcv.foreground == xgcv.background)
xgcv.foreground = s->f->output_data.android->cursor_foreground_pixel;
if (xgcv.foreground == xgcv.background)
xgcv.foreground = s->face->foreground;
/* Make sure the cursor is distinct from text in this face. */
if (xgcv.background == s->face->background
&& xgcv.foreground == s->face->foreground)
{
xgcv.background = s->face->foreground;
xgcv.foreground = s->face->background;
}
mask = (ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND);
if (FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc)
android_change_gc (FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc,
mask, &xgcv);
else
FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc
= android_create_gc (mask, &xgcv);
s->gc = FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc;
}
}
/* Set up S->gc of glyph string S for drawing text in mouse face. */
static void
android_set_mouse_face_gc (struct glyph_string *s)
{
if (s->font == s->face->font)
s->gc = s->face->gc;
else
{
/* Otherwise construct scratch_cursor_gc with values from FACE
except for FONT. */
struct android_gc_values xgcv;
unsigned long mask;
xgcv.background = s->face->background;
xgcv.foreground = s->face->foreground;
mask = (ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND);
if (FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc)
android_change_gc (FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc,
mask, &xgcv);
else
FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc
= android_create_gc (mask, &xgcv);
s->gc = FRAME_DISPLAY_INFO (s->f)->scratch_cursor_gc;
}
eassert (s->gc != 0);
}
/* Set S->gc of glyph string S to a GC suitable for drawing a mode line.
Faces to use in the mode line have already been computed when the
matrix was built, so there isn't much to do, here. */
static void
android_set_mode_line_face_gc (struct glyph_string *s)
{
s->gc = s->face->gc;
}
/* Set S->gc of glyph string S for drawing that glyph string. Set
S->stippled_p to a non-zero value if the face of S has a stipple
pattern. */
static void
android_set_glyph_string_gc (struct glyph_string *s)
{
prepare_face_for_display (s->f, s->face);
if (s->hl == DRAW_NORMAL_TEXT)
{
s->gc = s->face->gc;
s->stippled_p = s->face->stipple != 0;
}
else if (s->hl == DRAW_INVERSE_VIDEO)
{
android_set_mode_line_face_gc (s);
s->stippled_p = s->face->stipple != 0;
}
else if (s->hl == DRAW_CURSOR)
{
android_set_cursor_gc (s);
s->stippled_p = false;
}
else if (s->hl == DRAW_MOUSE_FACE)
{
android_set_mouse_face_gc (s);
s->stippled_p = s->face->stipple != 0;
}
else if (s->hl == DRAW_IMAGE_RAISED
|| s->hl == DRAW_IMAGE_SUNKEN)
{
s->gc = s->face->gc;
s->stippled_p = s->face->stipple != 0;
}
else
emacs_abort ();
/* GC must have been set. */
eassert (s->gc != 0);
}
/* Set clipping for output of glyph string S. S may be part of a mode
line or menu if we don't have X toolkit support. */
static void
android_set_glyph_string_clipping (struct glyph_string *s)
{
struct android_rectangle *r = s->clip;
int n = get_glyph_string_clip_rects (s, r, 2);
if (n > 0)
android_set_clip_rectangles (s->gc, 0, 0, r, n);
s->num_clips = n;
}
/* Set SRC's clipping for output of glyph string DST. This is called
when we are drawing DST's left_overhang or right_overhang only in
the area of SRC. */
static void
android_set_glyph_string_clipping_exactly (struct glyph_string *src,
struct glyph_string *dst)
{
struct android_rectangle r;
r.x = src->x;
r.width = src->width;
r.y = src->y;
r.height = src->height;
dst->clip[0] = r;
dst->num_clips = 1;
android_set_clip_rectangles (dst->gc, 0, 0, &r, 1);
}
static void
android_compute_glyph_string_overhangs (struct glyph_string *s)
{
if (s->cmp == NULL
&& (s->first_glyph->type == CHAR_GLYPH
|| s->first_glyph->type == COMPOSITE_GLYPH))
{
struct font_metrics metrics;
if (s->first_glyph->type == CHAR_GLYPH)
{
struct font *font = s->font;
font->driver->text_extents (font, s->char2b, s->nchars, &metrics);
}
else
{
Lisp_Object gstring = composition_gstring_from_id (s->cmp_id);
composition_gstring_width (gstring, s->cmp_from, s->cmp_to, &metrics);
}
s->right_overhang = (metrics.rbearing > metrics.width
? metrics.rbearing - metrics.width : 0);
s->left_overhang = metrics.lbearing < 0 ? - metrics.lbearing : 0;
}
else if (s->cmp)
{
s->right_overhang = s->cmp->rbearing - s->cmp->pixel_width;
s->left_overhang = - s->cmp->lbearing;
}
}
static void
android_clear_glyph_string_rect (struct glyph_string *s, int x, int y,
int w, int h)
{
android_clear_rectangle (s->f, s->gc, x, y, w, h);
}
static void
android_draw_glyph_string_background (struct glyph_string *s, bool force_p)
{
/* Nothing to do if background has already been drawn or if it
shouldn't be drawn in the first place. */
if (!s->background_filled_p)
{
int box_line_width = max (s->face->box_horizontal_line_width, 0);
if (s->stippled_p)
{
/* Fill background with a stipple pattern. */
android_set_fill_style (s->gc, ANDROID_FILL_OPAQUE_STIPPLED);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
s->x, s->y + box_line_width,
s->background_width,
s->height - 2 * box_line_width);
android_set_fill_style (s->gc, ANDROID_FILL_SOLID);
s->background_filled_p = true;
}
else if (FONT_HEIGHT (s->font) < s->height - 2 * box_line_width
/* When xdisp.c ignores FONT_HEIGHT, we cannot trust
font dimensions, since the actual glyphs might be
much smaller. So in that case we always clear the
rectangle with background color. */
|| FONT_TOO_HIGH (s->font)
|| s->font_not_found_p
|| s->extends_to_end_of_line_p
|| force_p)
{
android_clear_glyph_string_rect (s, s->x, s->y + box_line_width,
s->background_width,
s->height - 2 * box_line_width);
s->background_filled_p = true;
}
}
}
static void
android_fill_triangle (struct frame *f, struct android_gc *gc,
struct android_point point1,
struct android_point point2,
struct android_point point3)
{
struct android_point abc[3];
abc[0] = point1;
abc[1] = point2;
abc[2] = point3;
android_fill_polygon (FRAME_ANDROID_DRAWABLE (f),
gc, abc, 3, ANDROID_CONVEX,
ANDROID_COORD_MODE_ORIGIN);
}
static struct android_point
android_make_point (int x, int y)
{
struct android_point pt;
pt.x = x;
pt.y = y;
return pt;
}
static bool
android_inside_rect_p (struct android_rectangle *rects, int nrects, int x,
int y)
{
int i;
for (i = 0; i < nrects; ++i)
{
if (x >= rects[i].x && y >= rects[i].y
&& x < rects[i].x + rects[i].width
&& y < rects[i].y + rects[i].height)
return true;
}
return false;
}
static void
android_clear_point (struct frame *f, struct android_gc *gc,
int x, int y)
{
struct android_gc_values xgcv;
android_get_gc_values (gc, ANDROID_GC_BACKGROUND | ANDROID_GC_FOREGROUND,
&xgcv);
android_set_foreground (gc, xgcv.background);
android_draw_point (FRAME_ANDROID_DRAWABLE (f), gc, x, y);
android_set_foreground (gc, xgcv.foreground);
}
static void
android_draw_relief_rect (struct frame *f, int left_x, int top_y, int right_x,
int bottom_y, int hwidth, int vwidth, bool raised_p,
bool top_p, bool bot_p, bool left_p, bool right_p,
struct android_rectangle *clip_rect)
{
struct android_gc *gc, *white_gc, *black_gc, *normal_gc;
android_drawable drawable;
/* This code is more complicated than it has to be, because of two
minor hacks to make the boxes look nicer: (i) if width > 1, draw
the outermost line using the black relief. (ii) Omit the four
corner pixels. */
white_gc = f->output_data.android->white_relief.gc;
black_gc = f->output_data.android->black_relief.gc;
normal_gc = f->output_data.android->normal_gc;
drawable = FRAME_ANDROID_DRAWABLE (f);
android_set_clip_rectangles (white_gc, 0, 0, clip_rect, 1);
android_set_clip_rectangles (black_gc, 0, 0, clip_rect, 1);
if (raised_p)
gc = white_gc;
else
gc = black_gc;
/* Draw lines. */
if (top_p)
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x, top_y,
right_x - left_x + 1, hwidth);
if (left_p)
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x, top_y,
vwidth, bottom_y - top_y + 1);
if (raised_p)
gc = black_gc;
else
gc = white_gc;
if (bot_p)
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, left_x,
bottom_y - hwidth + 1,
right_x - left_x + 1, hwidth);
if (right_p)
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc,
right_x - vwidth + 1,
top_y, vwidth, bottom_y - top_y + 1);
/* Draw corners. */
if (bot_p && left_p)
android_fill_triangle (f, raised_p ? white_gc : black_gc,
android_make_point (left_x, bottom_y - hwidth),
android_make_point (left_x + vwidth,
bottom_y - hwidth),
android_make_point (left_x, bottom_y));
if (top_p && right_p)
android_fill_triangle (f, raised_p ? white_gc : black_gc,
android_make_point (right_x - vwidth, top_y),
android_make_point (right_x, top_y),
android_make_point (right_x - vwidth,
top_y + hwidth));
/* Draw outer line. */
if (top_p && left_p && bot_p && right_p
&& hwidth > 1 && vwidth > 1)
android_draw_rectangle (FRAME_ANDROID_DRAWABLE (f),
black_gc, left_x, top_y,
right_x - left_x, bottom_y - top_y);
else
{
if (top_p && hwidth > 1)
android_draw_line (drawable, black_gc, left_x, top_y,
right_x + 1, top_y);
if (bot_p && hwidth > 1)
android_draw_line (drawable, black_gc, left_x, bottom_y,
right_x + 1, bottom_y);
if (left_p && vwidth > 1)
android_draw_line (drawable, black_gc, left_x, top_y,
left_x, bottom_y + 1);
if (right_p && vwidth > 1)
android_draw_line (drawable, black_gc, right_x, top_y,
right_x, bottom_y + 1);
}
/* Erase corners. */
if (hwidth > 1 && vwidth > 1)
{
if (left_p && top_p && android_inside_rect_p (clip_rect, 1,
left_x, top_y))
android_clear_point (f, normal_gc, left_x, top_y);
if (left_p && bot_p && android_inside_rect_p (clip_rect, 1,
left_x, bottom_y))
android_clear_point (f, normal_gc, left_x, bottom_y);
if (right_p && top_p && android_inside_rect_p (clip_rect, 1,
right_x, top_y))
android_clear_point (f, normal_gc, right_x, top_y);
if (right_p && bot_p && android_inside_rect_p (clip_rect, 1,
right_x, bottom_y))
android_clear_point (f, normal_gc, right_x, bottom_y);
}
android_reset_clip_rectangles (f, white_gc);
android_reset_clip_rectangles (f, black_gc);
}
static void
android_draw_box_rect (struct glyph_string *s,
int left_x, int top_y, int right_x, int bottom_y,
int hwidth, int vwidth, bool left_p, bool right_p,
struct android_rectangle *clip_rect)
{
struct android_gc_values xgcv;
android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
android_set_foreground (s->gc, s->face->box_color);
android_set_clip_rectangles (s->gc, 0, 0, clip_rect, 1);
/* Top. */
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x,
top_y, right_x - left_x + 1, hwidth);
/* Left. */
if (left_p)
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x,
top_y, vwidth, bottom_y - top_y + 1);
/* Bottom. */
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, left_x,
bottom_y - hwidth + 1, right_x - left_x + 1,
hwidth);
/* Right. */
if (right_p)
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
right_x - vwidth + 1, top_y, vwidth,
bottom_y - top_y + 1);
android_set_foreground (s->gc, xgcv.foreground);
android_reset_clip_rectangles (s->f, s->gc);
}
#define HIGHLIGHT_COLOR_DARK_BOOST_LIMIT 48000
static bool
android_alloc_lighter_color (struct frame *f, unsigned long *pixel,
double factor, int delta)
{
Emacs_Color color, new;
long bright;
bool success_p;
/* Get RGB color values. */
color.pixel = *pixel;
android_query_colors (f, &color, 1);
/* Change RGB values by specified FACTOR. Avoid overflow! */
eassert (factor >= 0);
new.red = min (0xffff, factor * color.red);
new.green = min (0xffff, factor * color.green);
new.blue = min (0xffff, factor * color.blue);
/* Calculate brightness of COLOR. */
bright = (2 * color.red + 3 * color.green + color.blue) / 6;
/* We only boost colors that are darker than
HIGHLIGHT_COLOR_DARK_BOOST_LIMIT. */
if (bright < HIGHLIGHT_COLOR_DARK_BOOST_LIMIT)
/* Make an additive adjustment to NEW, because it's dark enough so
that scaling by FACTOR alone isn't enough. */
{
/* How far below the limit this color is (0 - 1, 1 being darker). */
double dimness = 1 - (double) bright / HIGHLIGHT_COLOR_DARK_BOOST_LIMIT;
/* The additive adjustment. */
int min_delta = delta * dimness * factor / 2;
if (factor < 1)
{
new.red = max (0, new.red - min_delta);
new.green = max (0, new.green - min_delta);
new.blue = max (0, new.blue - min_delta);
}
else
{
new.red = min (0xffff, min_delta + new.red);
new.green = min (0xffff, min_delta + new.green);
new.blue = min (0xffff, min_delta + new.blue);
}
}
/* Try to allocate the color. */
success_p = android_alloc_nearest_color (f, &new);
if (success_p)
{
if (new.pixel == *pixel)
{
/* If we end up with the same color as before, try adding
delta to the RGB values. */
new.red = min (0xffff, delta + color.red);
new.green = min (0xffff, delta + color.green);
new.blue = min (0xffff, delta + color.blue);
success_p = android_alloc_nearest_color (f, &new);
}
else
success_p = true;
*pixel = new.pixel;
}
return success_p;
}
/* Set up the foreground color for drawing relief lines of glyph
string S. RELIEF is a pointer to a struct relief containing the GC
with which lines will be drawn. Use a color that is FACTOR or
DELTA lighter or darker than the relief's background which is found
in S->f->output_data.android->relief_background. If such a color
cannot be allocated, use DEFAULT_PIXEL, instead. */
static void
android_setup_relief_color (struct frame *f, struct relief *relief,
double factor, int delta,
unsigned long default_pixel)
{
struct android_gc_values xgcv;
struct android_output *di = f->output_data.android;
unsigned long mask = ANDROID_GC_FOREGROUND;
unsigned long pixel;
unsigned long background = di->relief_background;
struct android_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
if (relief->gc && relief->pixel != -1)
relief->pixel = -1;
/* Allocate new color. */
xgcv.foreground = default_pixel;
pixel = background;
if (dpyinfo->n_planes != 1
&& android_alloc_lighter_color (f, &pixel, factor, delta))
xgcv.foreground = relief->pixel = pixel;
if (relief->gc == 0)
relief->gc = android_create_gc (mask, &xgcv);
else
android_change_gc (relief->gc, mask, &xgcv);
}
/* Set up colors for the relief lines around glyph string S. */
static void
android_setup_relief_colors (struct glyph_string *s)
{
struct android_output *di;
unsigned long color;
di = s->f->output_data.android;
if (s->face->use_box_color_for_shadows_p)
color = s->face->box_color;
else if (s->first_glyph->type == IMAGE_GLYPH
&& s->img->pixmap
&& !IMAGE_BACKGROUND_TRANSPARENT (s->img, s->f, 0))
color = IMAGE_BACKGROUND (s->img, s->f, 0);
else
{
struct android_gc_values xgcv;
/* Get the background color of the face. */
android_get_gc_values (s->gc, ANDROID_GC_BACKGROUND, &xgcv);
color = xgcv.background;
}
if (di->white_relief.gc == 0
|| color != di->relief_background)
{
di->relief_background = color;
android_setup_relief_color (s->f, &di->white_relief, 1.2, 0x8000,
WHITE_PIX_DEFAULT (s->f));
android_setup_relief_color (s->f, &di->black_relief, 0.6, 0x4000,
BLACK_PIX_DEFAULT (s->f));
}
}
static void
android_draw_glyph_string_box (struct glyph_string *s)
{
int hwidth, vwidth, left_x, right_x, top_y, bottom_y, last_x;
bool raised_p, left_p, right_p;
struct glyph *last_glyph;
struct android_rectangle clip_rect;
last_x = ((s->row->full_width_p && !s->w->pseudo_window_p)
? WINDOW_RIGHT_EDGE_X (s->w)
: window_box_right (s->w, s->area));
/* The glyph that may have a right box line. For static
compositions and images, the right-box flag is on the first glyph
of the glyph string; for other types it's on the last glyph. */
if (s->cmp || s->img)
last_glyph = s->first_glyph;
else if (s->first_glyph->type == COMPOSITE_GLYPH
&& s->first_glyph->u.cmp.automatic)
{
/* For automatic compositions, we need to look up the last glyph
in the composition. */
struct glyph *end = s->row->glyphs[s->area] + s->row->used[s->area];
struct glyph *g = s->first_glyph;
for (last_glyph = g++;
g < end && g->u.cmp.automatic && g->u.cmp.id == s->cmp_id
&& g->slice.cmp.to < s->cmp_to;
last_glyph = g++)
;
}
else
last_glyph = s->first_glyph + s->nchars - 1;
vwidth = eabs (s->face->box_vertical_line_width);
hwidth = eabs (s->face->box_horizontal_line_width);
raised_p = s->face->box == FACE_RAISED_BOX;
left_x = s->x;
right_x = (s->row->full_width_p && s->extends_to_end_of_line_p
? last_x - 1
: min (last_x, s->x + s->background_width) - 1);
top_y = s->y;
bottom_y = top_y + s->height - 1;
left_p = (s->first_glyph->left_box_line_p
|| (s->hl == DRAW_MOUSE_FACE
&& (s->prev == NULL
|| s->prev->hl != s->hl)));
right_p = (last_glyph->right_box_line_p
|| (s->hl == DRAW_MOUSE_FACE
&& (s->next == NULL
|| s->next->hl != s->hl)));
get_glyph_string_clip_rect (s, &clip_rect);
if (s->face->box == FACE_SIMPLE_BOX)
android_draw_box_rect (s, left_x, top_y, right_x, bottom_y, hwidth,
vwidth, left_p, right_p, &clip_rect);
else
{
android_setup_relief_colors (s);
android_draw_relief_rect (s->f, left_x, top_y, right_x, bottom_y, hwidth,
vwidth, raised_p, true, true, left_p, right_p,
&clip_rect);
}
}
static void
android_draw_glyph_string_bg_rect (struct glyph_string *s, int x, int y,
int w, int h)
{
if (s->stippled_p)
{
/* Fill background with a stipple pattern. */
android_set_fill_style (s->gc, ANDROID_FILL_OPAQUE_STIPPLED);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x,
y, w, h);
android_set_fill_style (s->gc, ANDROID_FILL_SOLID);
}
else
android_clear_glyph_string_rect (s, x, y, w, h);
}
static void
android_draw_image_relief (struct glyph_string *s)
{
int x1, y1, thick;
bool raised_p, top_p, bot_p, left_p, right_p;
int extra_x, extra_y;
struct android_rectangle r;
int x = s->x;
int y = s->ybase - image_ascent (s->img, s->face, &s->slice);
/* If first glyph of S has a left box line, start drawing it to the
right of that line. */
if (s->face->box != FACE_NO_BOX
&& s->first_glyph->left_box_line_p
&& s->slice.x == 0)
x += max (s->face->box_vertical_line_width, 0);
/* If there is a margin around the image, adjust x- and y-position
by that margin. */
if (s->slice.x == 0)
x += s->img->hmargin;
if (s->slice.y == 0)
y += s->img->vmargin;
if (s->hl == DRAW_IMAGE_SUNKEN
|| s->hl == DRAW_IMAGE_RAISED)
{
if (s->face->id == TAB_BAR_FACE_ID)
thick = (tab_bar_button_relief < 0
? DEFAULT_TAB_BAR_BUTTON_RELIEF
: min (tab_bar_button_relief, 1000000));
else
thick = (tool_bar_button_relief < 0
? DEFAULT_TOOL_BAR_BUTTON_RELIEF
: min (tool_bar_button_relief, 1000000));
raised_p = s->hl == DRAW_IMAGE_RAISED;
}
else
{
thick = eabs (s->img->relief);
raised_p = s->img->relief > 0;
}
x1 = x + s->slice.width - 1;
y1 = y + s->slice.height - 1;
extra_x = extra_y = 0;
if (s->face->id == TAB_BAR_FACE_ID)
{
if (CONSP (Vtab_bar_button_margin)
&& FIXNUMP (XCAR (Vtab_bar_button_margin))
&& FIXNUMP (XCDR (Vtab_bar_button_margin)))
{
extra_x = XFIXNUM (XCAR (Vtab_bar_button_margin)) - thick;
extra_y = XFIXNUM (XCDR (Vtab_bar_button_margin)) - thick;
}
else if (FIXNUMP (Vtab_bar_button_margin))
extra_x = extra_y = XFIXNUM (Vtab_bar_button_margin) - thick;
}
if (s->face->id == TOOL_BAR_FACE_ID)
{
if (CONSP (Vtool_bar_button_margin)
&& FIXNUMP (XCAR (Vtool_bar_button_margin))
&& FIXNUMP (XCDR (Vtool_bar_button_margin)))
{
extra_x = XFIXNUM (XCAR (Vtool_bar_button_margin));
extra_y = XFIXNUM (XCDR (Vtool_bar_button_margin));
}
else if (FIXNUMP (Vtool_bar_button_margin))
extra_x = extra_y = XFIXNUM (Vtool_bar_button_margin);
}
top_p = bot_p = left_p = right_p = false;
if (s->slice.x == 0)
x -= thick + extra_x, left_p = true;
if (s->slice.y == 0)
y -= thick + extra_y, top_p = true;
if (s->slice.x + s->slice.width == s->img->width)
x1 += thick + extra_x, right_p = true;
if (s->slice.y + s->slice.height == s->img->height)
y1 += thick + extra_y, bot_p = true;
android_setup_relief_colors (s);
get_glyph_string_clip_rect (s, &r);
android_draw_relief_rect (s->f, x, y, x1, y1, thick, thick, raised_p,
top_p, bot_p, left_p, right_p, &r);
}
static void
android_draw_image_foreground (struct glyph_string *s)
{
int x = s->x;
int y = s->ybase - image_ascent (s->img, s->face, &s->slice);
/* If first glyph of S has a left box line, start drawing it to the
right of that line. */
if (s->face->box != FACE_NO_BOX
&& s->first_glyph->left_box_line_p
&& s->slice.x == 0)
x += max (s->face->box_vertical_line_width, 0);
/* If there is a margin around the image, adjust x- and y-position
by that margin. */
if (s->slice.x == 0)
x += s->img->hmargin;
if (s->slice.y == 0)
y += s->img->vmargin;
if (s->img->pixmap)
{
unsigned long mask = (ANDROID_GC_CLIP_MASK
| ANDROID_GC_CLIP_X_ORIGIN
| ANDROID_GC_CLIP_Y_ORIGIN
| ANDROID_GC_FUNCTION);
struct android_gc_values xgcv;
struct android_rectangle clip_rect, image_rect, r;
xgcv.clip_mask = s->img->mask;
xgcv.clip_x_origin = x - s->slice.x;
xgcv.clip_y_origin = y - s->slice.y;
xgcv.function = ANDROID_GC_COPY;
android_change_gc (s->gc, mask, &xgcv);
get_glyph_string_clip_rect (s, &clip_rect);
image_rect.x = x;
image_rect.y = y;
image_rect.width = s->slice.width;
image_rect.height = s->slice.height;
if (gui_intersect_rectangles (&clip_rect, &image_rect, &r))
android_copy_area (s->img->pixmap,
FRAME_ANDROID_DRAWABLE (s->f),
s->gc, s->slice.x + r.x - x,
s->slice.y + r.y - y,
r.width, r.height, r.x, r.y);
/* When the image has a mask, we can expect that at least part
of a mouse highlight or a block cursor will be visible. If
the image doesn't have a mask, make a block cursor visible by
drawing a rectangle around the image. I believe it's looking
better if we do nothing here for mouse-face. */
if (s->hl == DRAW_CURSOR && !s->img->mask)
{
int relief = eabs (s->img->relief);
android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
x - relief, y - relief,
s->slice.width + relief*2 - 1,
s->slice.height + relief*2 - 1);
}
android_set_clip_mask (s->gc, ANDROID_NONE);
}
else
/* Draw a rectangle if image could not be loaded. */
android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc, x, y,
s->slice.width - 1, s->slice.height - 1);
}
static void
android_draw_image_glyph_string (struct glyph_string *s)
{
int box_line_hwidth = max (s->face->box_vertical_line_width, 0);
int box_line_vwidth = max (s->face->box_horizontal_line_width, 0);
int height;
height = s->height;
if (s->slice.y == 0)
height -= box_line_vwidth;
if (s->slice.y + s->slice.height >= s->img->height)
height -= box_line_vwidth;
/* Fill background with face under the image. Do it only if row is
taller than image or if image has a clip mask to reduce
flickering. */
s->stippled_p = s->face->stipple != 0;
if (height > s->slice.height
|| s->img->hmargin
|| s->img->vmargin
|| s->img->mask
|| s->img->pixmap == 0
|| s->width != s->background_width)
{
if (s->stippled_p)
s->row->stipple_p = true;
int x = s->x;
int y = s->y;
int width = s->background_width;
if (s->first_glyph->left_box_line_p
&& s->slice.x == 0)
{
x += box_line_hwidth;
width -= box_line_hwidth;
}
if (s->slice.y == 0)
y += box_line_vwidth;
android_draw_glyph_string_bg_rect (s, x, y, width, height);
s->background_filled_p = true;
}
/* Draw the foreground. */
android_draw_image_foreground (s);
android_set_glyph_string_clipping (s);
/* If we must draw a relief around the image, do it. */
if (s->img->relief
|| s->hl == DRAW_IMAGE_RAISED
|| s->hl == DRAW_IMAGE_SUNKEN)
android_draw_image_relief (s);
}
static void
android_draw_stretch_glyph_string (struct glyph_string *s)
{
eassert (s->first_glyph->type == STRETCH_GLYPH);
if (s->hl == DRAW_CURSOR && !x_stretch_cursor_p)
{
/* If `x-stretch-cursor' is nil, don't draw a block cursor as
wide as the stretch glyph. */
int width, background_width = s->background_width;
int x = s->x;
if (!s->row->reversed_p)
{
int left_x = window_box_left_offset (s->w, TEXT_AREA);
if (x < left_x)
{
background_width -= left_x - x;
x = left_x;
}
}
else
{
/* In R2L rows, draw the cursor on the right edge of the
stretch glyph. */
int right_x = window_box_right (s->w, TEXT_AREA);
if (x + background_width > right_x)
background_width -= x - right_x;
x += background_width;
}
width = min (FRAME_COLUMN_WIDTH (s->f), background_width);
if (s->row->reversed_p)
x -= width;
/* Draw cursor. */
android_draw_glyph_string_bg_rect (s, x, s->y, width, s->height);
/* Clear rest using the GC of the original non-cursor face. */
if (width < background_width)
{
int y = s->y;
int w = background_width - width, h = s->height;
struct android_rectangle r;
struct android_gc *gc;
if (!s->row->reversed_p)
x += width;
else
x = s->x;
if (s->row->mouse_face_p
&& cursor_in_mouse_face_p (s->w))
{
android_set_mouse_face_gc (s);
gc = s->gc;
}
else
gc = s->face->gc;
get_glyph_string_clip_rect (s, &r);
android_set_clip_rectangles (gc, 0, 0, &r, 1);
if (s->face->stipple)
{
/* Fill background with a stipple pattern. */
android_set_fill_style (gc, ANDROID_FILL_OPAQUE_STIPPLED);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
gc, x, y, w, h);
android_set_fill_style (gc, ANDROID_FILL_SOLID);
s->row->stipple_p = true;
}
else
{
struct android_gc_values xgcv;
android_get_gc_values (gc, (ANDROID_GC_FOREGROUND
| ANDROID_GC_BACKGROUND),
&xgcv);
android_set_foreground (gc, xgcv.background);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
gc, x, y, w, h);
android_set_foreground (gc, xgcv.foreground);
}
android_reset_clip_rectangles (s->f, gc);
}
}
else if (!s->background_filled_p)
{
int background_width = s->background_width;
int x = s->x, text_left_x = window_box_left (s->w, TEXT_AREA);
/* Don't draw into left fringe or scrollbar area except for
header line and mode line. */
if (s->area == TEXT_AREA
&& x < text_left_x && !s->row->mode_line_p)
{
background_width -= text_left_x - x;
x = text_left_x;
}
if (!s->row->stipple_p)
s->row->stipple_p = s->stippled_p;
if (background_width > 0)
android_draw_glyph_string_bg_rect (s, x, s->y,
background_width,
s->height);
}
s->background_filled_p = true;
}
static void
android_get_scale_factor (int *scale_x, int *scale_y)
{
/* This is 96 everywhere else, but 160 on Android. */
int base_res = 160;
*scale_x = *scale_y = 1;
eassert (x_display_list);
if (x_display_list->resx > base_res)
*scale_x = floor (x_display_list->resx / base_res);
if (x_display_list->resy > base_res)
*scale_y = floor (x_display_list->resy / base_res);
}
static void
android_draw_underwave (struct glyph_string *s, int decoration_width)
{
int scale_x, scale_y;
android_get_scale_factor (&scale_x, &scale_y);
int wave_height = 3 * scale_y, wave_length = 2 * scale_x;
int dx, dy, x0, y0, width, x1, y1, x2, y2, xmax;
bool odd;
struct android_rectangle wave_clip, string_clip, final_clip;
dx = wave_length;
dy = wave_height - 1;
x0 = s->x;
y0 = s->ybase + wave_height / 2;
width = decoration_width;
xmax = x0 + width;
/* Find and set clipping rectangle */
wave_clip.x = x0;
wave_clip.y = y0;
wave_clip.width = width;
wave_clip.height = wave_height;
get_glyph_string_clip_rect (s, &string_clip);
if (!gui_intersect_rectangles (&wave_clip, &string_clip, &final_clip))
return;
android_set_clip_rectangles (s->gc, 0, 0, &final_clip, 1);
/* Draw the waves */
x1 = x0 - (x0 % dx);
x2 = x1 + dx;
odd = (x1 / dx) & 1;
y1 = y2 = y0;
if (odd)
y1 += dy;
else
y2 += dy;
if (INT_MAX - dx < xmax)
emacs_abort ();
while (x1 <= xmax)
{
android_draw_line (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
x1, y1, x2, y2);
x1 = x2, y1 = y2;
x2 += dx, y2 = y0 + odd*dy;
odd = !odd;
}
/* Restore previous clipping rectangle(s) */
android_set_clip_rectangles (s->gc, 0, 0, s->clip, s->num_clips);
}
static void
android_draw_glyph_string_foreground (struct glyph_string *s)
{
int i, x;
/* If first glyph of S has a left box line, start drawing the text
of S to the right of that box line. */
if (s->face->box != FACE_NO_BOX
&& s->first_glyph->left_box_line_p)
x = s->x + max (s->face->box_vertical_line_width, 0);
else
x = s->x;
/* Draw characters of S as rectangles if S's font could not be
loaded. */
if (s->font_not_found_p)
{
for (i = 0; i < s->nchars; ++i)
{
struct glyph *g = s->first_glyph + i;
android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
s->gc, x, s->y,
g->pixel_width - 1,
s->height - 1);
x += g->pixel_width;
}
}
else
{
struct font *font = s->font;
int boff = font->baseline_offset;
int y;
if (font->vertical_centering)
boff = VCENTER_BASELINE_OFFSET (font, s->f) - boff;
y = s->ybase - boff;
if (s->for_overlaps
|| (s->background_filled_p && s->hl != DRAW_CURSOR))
font->driver->draw (s, 0, s->nchars, x, y, false);
else
font->driver->draw (s, 0, s->nchars, x, y, true);
if (s->face->overstrike)
font->driver->draw (s, 0, s->nchars, x + 1, y, false);
}
}
static void
android_draw_composite_glyph_string_foreground (struct glyph_string *s)
{
int i, j, x;
struct font *font = s->font;
/* If first glyph of S has a left box line, start drawing the text
of S to the right of that box line. */
if (s->face && s->face->box != FACE_NO_BOX
&& s->first_glyph->left_box_line_p)
x = s->x + max (s->face->box_vertical_line_width, 0);
else
x = s->x;
/* S is a glyph string for a composition. S->cmp_from is the index
of the first character drawn for glyphs of this composition.
S->cmp_from == 0 means we are drawing the very first character of
this composition. */
/* Draw a rectangle for the composition if the font for the very
first character of the composition could not be loaded. */
if (s->font_not_found_p)
{
if (s->cmp_from == 0)
android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
s->gc, x, s->y,
s->width - 1, s->height - 1);
}
else if (! s->first_glyph->u.cmp.automatic)
{
int y = s->ybase;
for (i = 0, j = s->cmp_from; i < s->nchars; i++, j++)
/* TAB in a composition means display glyphs with
padding space on the left or right. */
if (COMPOSITION_GLYPH (s->cmp, j) != '\t')
{
int xx = x + s->cmp->offsets[j * 2];
int yy = y - s->cmp->offsets[j * 2 + 1];
font->driver->draw (s, j, j + 1, xx, yy, false);
if (s->face->overstrike)
font->driver->draw (s, j, j + 1, xx + 1, yy, false);
}
}
else
{
Lisp_Object gstring = composition_gstring_from_id (s->cmp_id);
Lisp_Object glyph;
int y = s->ybase;
int width = 0;
for (i = j = s->cmp_from; i < s->cmp_to; i++)
{
glyph = LGSTRING_GLYPH (gstring, i);
if (NILP (LGLYPH_ADJUSTMENT (glyph)))
width += LGLYPH_WIDTH (glyph);
else
{
int xoff, yoff, wadjust;
if (j < i)
{
font->driver->draw (s, j, i, x, y, false);
if (s->face->overstrike)
font->driver->draw (s, j, i, x + 1, y, false);
x += width;
}
xoff = LGLYPH_XOFF (glyph);
yoff = LGLYPH_YOFF (glyph);
wadjust = LGLYPH_WADJUST (glyph);
font->driver->draw (s, i, i + 1, x + xoff, y + yoff, false);
if (s->face->overstrike)
font->driver->draw (s, i, i + 1, x + xoff + 1, y + yoff,
false);
x += wadjust;
j = i + 1;
width = 0;
}
}
if (j < i)
{
font->driver->draw (s, j, i, x, y, false);
if (s->face->overstrike)
font->driver->draw (s, j, i, x + 1, y, false);
}
}
}
static void
android_draw_glyphless_glyph_string_foreground (struct glyph_string *s)
{
struct glyph *glyph = s->first_glyph;
unsigned char2b[8];
int x, i, j;
/* If first glyph of S has a left box line, start drawing the text
of S to the right of that box line. */
if (s->face && s->face->box != FACE_NO_BOX
&& s->first_glyph->left_box_line_p)
x = s->x + max (s->face->box_vertical_line_width, 0);
else
x = s->x;
s->char2b = char2b;
for (i = 0; i < s->nchars; i++, glyph++)
{
#ifdef GCC_LINT
enum { PACIFY_GCC_BUG_81401 = 1 };
#else
enum { PACIFY_GCC_BUG_81401 = 0 };
#endif
char buf[7 + PACIFY_GCC_BUG_81401];
char *str = NULL;
int len = glyph->u.glyphless.len;
if (glyph->u.glyphless.method == GLYPHLESS_DISPLAY_ACRONYM)
{
if (len > 0
&& CHAR_TABLE_P (Vglyphless_char_display)
&& (CHAR_TABLE_EXTRA_SLOTS (XCHAR_TABLE (Vglyphless_char_display))
>= 1))
{
Lisp_Object acronym
= (! glyph->u.glyphless.for_no_font
? CHAR_TABLE_REF (Vglyphless_char_display,
glyph->u.glyphless.ch)
: XCHAR_TABLE (Vglyphless_char_display)->extras[0]);
if (CONSP (acronym))
acronym = XCAR (acronym);
if (STRINGP (acronym))
str = SSDATA (acronym);
}
}
else if (glyph->u.glyphless.method == GLYPHLESS_DISPLAY_HEX_CODE)
{
unsigned int ch = glyph->u.glyphless.ch;
eassume (ch <= MAX_CHAR);
sprintf (buf, "%0*X", ch < 0x10000 ? 4 : 6, ch);
str = buf;
}
if (str)
{
int upper_len = (len + 1) / 2;
/* It is assured that all LEN characters in STR is ASCII. */
for (j = 0; j < len; j++)
char2b[j] = s->font->driver->encode_char (s->font, str[j]) & 0xFFFF;
s->font->driver->draw (s, 0, upper_len,
x + glyph->slice.glyphless.upper_xoff,
s->ybase + glyph->slice.glyphless.upper_yoff,
false);
s->font->driver->draw (s, upper_len, len,
x + glyph->slice.glyphless.lower_xoff,
s->ybase + glyph->slice.glyphless.lower_yoff,
false);
}
if (glyph->u.glyphless.method != GLYPHLESS_DISPLAY_THIN_SPACE)
android_draw_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
x, s->ybase - glyph->ascent,
glyph->pixel_width - 1,
glyph->ascent + glyph->descent - 1);
x += glyph->pixel_width;
}
/* Defend against hypothetical bad code elsewhere that uses
s->char2b after this function returns. */
s->char2b = NULL;
}
/* Draw a dashed underline of thickness THICKNESS and width WIDTH onto F
at a vertical offset of OFFSET from the position of the glyph string
S, with each segment SEGMENT pixels in length. */
static void
android_draw_dash (struct frame *f, struct glyph_string *s, int width,
int segment, int offset, int thickness)
{
struct android_gc *gc;
struct android_gc_values gcv;
int y_center;
/* Configure the GC, the dash pattern and a suitable offset. */
gc = s->gc;
gcv.line_style = ANDROID_LINE_ON_OFF_DASH;
gcv.line_width = thickness;
android_change_gc (s->gc, (ANDROID_GC_LINE_STYLE
| ANDROID_GC_LINE_WIDTH), &gcv);
android_set_dashes (s->gc, s->x, &segment, 1);
/* Offset the origin of the line by half the line width. */
y_center = s->ybase + offset + thickness / 2;
android_draw_line (FRAME_ANDROID_WINDOW (f), gc,
s->x, y_center, s->x + width, y_center);
/* Restore the initial line style. */
gcv.line_style = ANDROID_LINE_SOLID;
gcv.line_width = 1;
android_change_gc (s->gc, (ANDROID_GC_LINE_STYLE
| ANDROID_GC_LINE_WIDTH), &gcv);
}
/* Draw an underline of STYLE onto F at an offset of POSITION from the
baseline of the glyph string S, DECORATION_WIDTH in length, and
THICKNESS in height. */
static void
android_fill_underline (struct frame *f, struct glyph_string *s,
enum face_underline_type style, int position,
int decoration_width, int thickness)
{
int segment;
segment = thickness * 3;
switch (style)
{
/* FACE_UNDERLINE_DOUBLE_LINE is treated identically to SINGLE, as
the second line will be filled by another invocation of this
function. */
case FACE_UNDERLINE_SINGLE:
case FACE_UNDERLINE_DOUBLE_LINE:
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
s->gc, s->x, s->ybase + position,
decoration_width, thickness);
break;
case FACE_UNDERLINE_DOTS:
segment = thickness;
FALLTHROUGH;
case FACE_UNDERLINE_DASHES:
android_draw_dash (f, s, decoration_width, segment, position,
thickness);
break;
case FACE_NO_UNDERLINE:
case FACE_UNDERLINE_WAVE:
default:
emacs_abort ();
}
}
static void
android_draw_glyph_string (struct glyph_string *s)
{
bool relief_drawn_p = false;
/* If S draws into the background of its successors, draw the
background of the successors first so that S can draw into it.
This makes S->next use XDrawString instead of XDrawImageString. */
if (s->next && s->right_overhang && !s->for_overlaps)
{
int width;
struct glyph_string *next;
for (width = 0, next = s->next;
next && width < s->right_overhang;
width += next->width, next = next->next)
if (next->first_glyph->type != IMAGE_GLYPH)
{
android_set_glyph_string_gc (next);
android_set_glyph_string_clipping (next);
if (next->first_glyph->type == STRETCH_GLYPH)
android_draw_stretch_glyph_string (next);
else
android_draw_glyph_string_background (next, true);
next->num_clips = 0;
}
}
/* Set up S->gc, set clipping and draw S. */
android_set_glyph_string_gc (s);
/* Draw relief (if any) in advance for char/composition so that the
glyph string can be drawn over it. */
if (!s->for_overlaps
&& s->face->box != FACE_NO_BOX
&& (s->first_glyph->type == CHAR_GLYPH
|| s->first_glyph->type == COMPOSITE_GLYPH))
{
android_set_glyph_string_clipping (s);
android_draw_glyph_string_background (s, true);
android_draw_glyph_string_box (s);
android_set_glyph_string_clipping (s);
relief_drawn_p = true;
}
else if (!s->clip_head /* draw_glyphs didn't specify a clip mask. */
&& !s->clip_tail
&& ((s->prev && s->prev->hl != s->hl && s->left_overhang)
|| (s->next && s->next->hl != s->hl && s->right_overhang)))
/* We must clip just this glyph. left_overhang part has already
drawn when s->prev was drawn, and right_overhang part will be
drawn later when s->next is drawn. */
android_set_glyph_string_clipping_exactly (s, s);
else
android_set_glyph_string_clipping (s);
switch (s->first_glyph->type)
{
case IMAGE_GLYPH:
android_draw_image_glyph_string (s);
break;
case XWIDGET_GLYPH:
emacs_abort ();
break;
case STRETCH_GLYPH:
android_draw_stretch_glyph_string (s);
break;
case CHAR_GLYPH:
if (s->for_overlaps)
s->background_filled_p = true;
else
android_draw_glyph_string_background (s, false);
android_draw_glyph_string_foreground (s);
break;
case COMPOSITE_GLYPH:
if (s->for_overlaps || (s->cmp_from > 0
&& ! s->first_glyph->u.cmp.automatic))
s->background_filled_p = true;
else
android_draw_glyph_string_background (s, true);
android_draw_composite_glyph_string_foreground (s);
break;
case GLYPHLESS_GLYPH:
if (s->for_overlaps)
s->background_filled_p = true;
else
android_draw_glyph_string_background (s, true);
android_draw_glyphless_glyph_string_foreground (s);
break;
default:
emacs_abort ();
}
if (!s->for_overlaps)
{
int area_x, area_y, area_width, area_height;
int area_max_x, decoration_width;
/* Prevent the underline from overwriting surrounding areas
and the fringe. */
window_box (s->w, s->area, &area_x, &area_y,
&area_width, &area_height);
area_max_x = area_x + area_width - 1;
decoration_width = s->width;
if (!s->row->mode_line_p
&& !s->row->tab_line_p
&& area_max_x < (s->x + decoration_width - 1))
decoration_width -= (s->x + decoration_width - 1) - area_max_x;
/* Draw relief if not yet drawn. */
if (!relief_drawn_p && s->face->box != FACE_NO_BOX)
android_draw_glyph_string_box (s);
/* Draw underline. */
if (s->face->underline)
{
if (s->face->underline == FACE_UNDERLINE_WAVE)
{
if (s->face->underline_defaulted_p)
android_draw_underwave (s, decoration_width);
else
{
struct android_gc_values xgcv;
android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
android_set_foreground (s->gc, s->face->underline_color);
android_draw_underwave (s, decoration_width);
android_set_foreground (s->gc, xgcv.foreground);
}
}
else if (s->face->underline >= FACE_UNDERLINE_SINGLE)
{
unsigned long thickness, position;
if (s->prev
&& (s->prev->face->underline != FACE_UNDERLINE_WAVE
&& s->prev->face->underline >= FACE_UNDERLINE_SINGLE)
&& (s->prev->face->underline_at_descent_line_p
== s->face->underline_at_descent_line_p)
&& (s->prev->face->underline_pixels_above_descent_line
== s->face->underline_pixels_above_descent_line))
{
/* We use the same underline style as the previous one. */
thickness = s->prev->underline_thickness;
position = s->prev->underline_position;
}
else
{
struct font *font = font_for_underline_metrics (s);
unsigned long minimum_offset;
bool underline_at_descent_line;
bool use_underline_position_properties;
Lisp_Object val = (WINDOW_BUFFER_LOCAL_VALUE
(Qunderline_minimum_offset, s->w));
if (FIXNUMP (val))
minimum_offset = max (0, XFIXNUM (val));
else
minimum_offset = 1;
val = (WINDOW_BUFFER_LOCAL_VALUE
(Qx_underline_at_descent_line, s->w));
underline_at_descent_line
= (!(NILP (val) || BASE_EQ (val, Qunbound))
|| s->face->underline_at_descent_line_p);
val = (WINDOW_BUFFER_LOCAL_VALUE
(Qx_use_underline_position_properties, s->w));
use_underline_position_properties
= !(NILP (val) || BASE_EQ (val, Qunbound));
/* Get the underline thickness. Default is 1 pixel. */
if (font && font->underline_thickness > 0)
thickness = font->underline_thickness;
else
thickness = 1;
if (underline_at_descent_line)
position = ((s->height - thickness)
- (s->ybase - s->y)
- s->face->underline_pixels_above_descent_line);
else
{
/* Get the underline position. This is the
recommended vertical offset in pixels from
the baseline to the top of the underline.
This is a signed value according to the
specs, and its default is
ROUND ((maximum descent) / 2), with
ROUND(x) = floor (x + 0.5) */
if (use_underline_position_properties
&& font && font->underline_position >= 0)
position = font->underline_position;
else if (font)
position = (font->descent + 1) / 2;
else
position = minimum_offset;
}
/* Ignore minimum_offset if the amount of pixels was
explicitly specified. */
if (!s->face->underline_pixels_above_descent_line)
position = max (position, minimum_offset);
}
/* Check the sanity of thickness and position. We should
avoid drawing underline out of the current line area. */
if (s->y + s->height <= s->ybase + position)
position = (s->height - 1) - (s->ybase - s->y);
if (s->y + s->height < s->ybase + position + thickness)
thickness = (s->y + s->height) - (s->ybase + position);
s->underline_thickness = thickness;
s->underline_position = position;
{
struct android_gc_values xgcv;
if (!s->face->underline_defaulted_p)
{
android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
android_set_foreground (s->gc, s->face->underline_color);
}
android_fill_underline (s->f, s, s->face->underline,
position, decoration_width,
thickness);
/* Place a second underline above the first if this was
requested in the face specification. */
if (s->face->underline == FACE_UNDERLINE_DOUBLE_LINE)
{
/* Compute the position of the second underline. */
position = position - thickness - 1;
android_fill_underline (s->f, s, s->face->underline,
position, decoration_width,
thickness);
}
if (!s->face->underline_defaulted_p)
android_set_foreground (s->gc, xgcv.foreground);
}
}
}
/* Draw overline. */
if (s->face->overline_p)
{
unsigned long dy = 0, h = 1;
if (s->face->overline_color_defaulted_p)
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f),
s->gc, s->x, s->y + dy,
decoration_width, h);
else
{
struct android_gc_values xgcv;
android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
android_set_foreground (s->gc, s->face->overline_color);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
s->x, s->y + dy, decoration_width, h);
android_set_foreground (s->gc, xgcv.foreground);
}
}
/* Draw strike-through. */
if (s->face->strike_through_p)
{
/* Y-coordinate and height of the glyph string's first
glyph. We cannot use s->y and s->height because those
could be larger if there are taller display elements
(e.g., characters displayed with a larger font) in the
same glyph row. */
int glyph_y = s->ybase - s->first_glyph->ascent;
int glyph_height = s->first_glyph->ascent + s->first_glyph->descent;
/* Strike-through width and offset from the glyph string's
top edge. */
unsigned long h = 1;
unsigned long dy = (glyph_height - h) / 2;
if (s->face->strike_through_color_defaulted_p)
android_fill_rectangle (FRAME_ANDROID_WINDOW (s->f),
s->gc, s->x, glyph_y + dy,
s->width, h);
else
{
struct android_gc_values xgcv;
android_get_gc_values (s->gc, ANDROID_GC_FOREGROUND, &xgcv);
android_set_foreground (s->gc, s->face->strike_through_color);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (s->f), s->gc,
s->x, glyph_y + dy, decoration_width,
h);
android_set_foreground (s->gc, xgcv.foreground);
}
}
if (s->prev)
{
struct glyph_string *prev;
for (prev = s->prev; prev; prev = prev->prev)
if (prev->hl != s->hl
&& prev->x + prev->width + prev->right_overhang > s->x)
{
/* As prev was drawn while clipped to its own area, we
must draw the right_overhang part using s->hl now. */
enum draw_glyphs_face save = prev->hl;
prev->hl = s->hl;
android_set_glyph_string_gc (prev);
android_set_glyph_string_clipping_exactly (s, prev);
if (prev->first_glyph->type == CHAR_GLYPH)
android_draw_glyph_string_foreground (prev);
else
android_draw_composite_glyph_string_foreground (prev);
android_reset_clip_rectangles (prev->f, prev->gc);
prev->hl = save;
prev->num_clips = 0;
}
}
if (s->next)
{
struct glyph_string *next;
for (next = s->next; next; next = next->next)
if (next->hl != s->hl
&& next->x - next->left_overhang < s->x + s->width)
{
/* As next will be drawn while clipped to its own area,
we must draw the left_overhang part using s->hl now. */
enum draw_glyphs_face save = next->hl;
next->hl = s->hl;
android_set_glyph_string_gc (next);
android_set_glyph_string_clipping_exactly (s, next);
if (next->first_glyph->type == CHAR_GLYPH)
android_draw_glyph_string_foreground (next);
else
android_draw_composite_glyph_string_foreground (next);
android_reset_clip_rectangles (next->f, next->gc);
next->hl = save;
next->num_clips = 0;
next->clip_head = s->next;
}
}
}
/* Reset clipping. */
android_reset_clip_rectangles (s->f, s->gc);
s->num_clips = 0;
/* Set the stippled flag that tells redisplay whether or not a
stipple was actually draw. */
if (s->first_glyph->type != STRETCH_GLYPH
&& s->first_glyph->type != IMAGE_GLYPH
&& !s->row->stipple_p)
s->row->stipple_p = s->stippled_p;
}
static void
android_define_frame_cursor (struct frame *f, Emacs_Cursor cursor)
{
if (!f->pointer_invisible
&& !FRAME_ANDROID_OUTPUT (f)->hourglass
&& f->output_data.android->current_cursor != cursor)
android_define_cursor (FRAME_ANDROID_WINDOW (f), cursor);
f->output_data.android->current_cursor = cursor;
}
static void
android_clear_frame_area (struct frame *f, int x, int y,
int width, int height)
{
android_clear_area (FRAME_ANDROID_DRAWABLE (f),
x, y, width, height);
}
void
android_clear_under_internal_border (struct frame *f)
{
if (FRAME_INTERNAL_BORDER_WIDTH (f) > 0)
{
int border = FRAME_INTERNAL_BORDER_WIDTH (f);
int width = FRAME_PIXEL_WIDTH (f);
int height = FRAME_PIXEL_HEIGHT (f);
int margin = FRAME_TOP_MARGIN_HEIGHT (f);
int bottom_margin = FRAME_BOTTOM_MARGIN_HEIGHT (f);
int face_id = (FRAME_PARENT_FRAME (f)
? (!NILP (Vface_remapping_alist)
? lookup_basic_face (NULL, f,
CHILD_FRAME_BORDER_FACE_ID)
: CHILD_FRAME_BORDER_FACE_ID)
: (!NILP (Vface_remapping_alist)
? lookup_basic_face (NULL, f,
INTERNAL_BORDER_FACE_ID)
: INTERNAL_BORDER_FACE_ID));
struct face *face = FACE_FROM_ID_OR_NULL (f, face_id);
if (face)
{
unsigned long color = face->background;
struct android_gc *gc = f->output_data.android->normal_gc;
android_set_foreground (gc, color);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0, margin,
width, border);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0, 0,
border, height);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, width - border,
0, border, height);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, 0,
height - bottom_margin - border,
width, border);
android_set_foreground (gc, FRAME_FOREGROUND_PIXEL (f));
}
else
{
android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0, 0,
border, height);
android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0,
margin, width, border);
android_clear_area (FRAME_ANDROID_DRAWABLE (f), width - border,
0, border, height);
android_clear_area (FRAME_ANDROID_DRAWABLE (f), 0,
height - bottom_margin - border,
width, border);
}
}
}
static void
android_draw_hollow_cursor (struct window *w, struct glyph_row *row)
{
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct android_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
int x, y, wd, h;
struct android_gc_values xgcv;
struct glyph *cursor_glyph;
struct android_gc *gc;
/* Get the glyph the cursor is on. If we can't tell because
the current matrix is invalid or such, give up. */
cursor_glyph = get_phys_cursor_glyph (w);
if (cursor_glyph == NULL)
return;
/* Compute frame-relative coordinates for phys cursor. */
get_phys_cursor_geometry (w, row, cursor_glyph, &x, &y, &h);
wd = w->phys_cursor_width - 1;
/* The foreground of cursor_gc is typically the same as the normal
background color, which can cause the cursor box to be invisible. */
xgcv.foreground = f->output_data.android->cursor_pixel;
if (dpyinfo->scratch_cursor_gc)
android_change_gc (dpyinfo->scratch_cursor_gc,
ANDROID_GC_FOREGROUND, &xgcv);
else
dpyinfo->scratch_cursor_gc
= android_create_gc (ANDROID_GC_FOREGROUND, &xgcv);
gc = dpyinfo->scratch_cursor_gc;
/* When on R2L character, show cursor at the right edge of the
glyph, unless the cursor box is as wide as the glyph or wider
(the latter happens when x-stretch-cursor is non-nil). */
if ((cursor_glyph->resolved_level & 1) != 0
&& cursor_glyph->pixel_width > wd)
{
x += cursor_glyph->pixel_width - wd;
if (wd > 0)
wd -= 1;
}
/* Set clipping, draw the rectangle, and reset clipping again. */
android_clip_to_row (w, row, TEXT_AREA, gc, NULL);
android_draw_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x, y, wd, h - 1);
android_reset_clip_rectangles (f, gc);
}
static void
android_draw_bar_cursor (struct window *w, struct glyph_row *row, int width,
enum text_cursor_kinds kind)
{
struct frame *f = XFRAME (w->frame);
struct glyph *cursor_glyph;
int cursor_start_y;
/* If cursor is out of bounds, don't draw garbage. This can happen
in mini-buffer windows when switching between echo area glyphs
and mini-buffer. */
cursor_glyph = get_phys_cursor_glyph (w);
if (cursor_glyph == NULL)
return;
/* Experimental avoidance of cursor on xwidget. */
if (cursor_glyph->type == XWIDGET_GLYPH)
return;
/* If on an image, draw like a normal cursor. That's usually better
visible than drawing a bar, esp. if the image is large so that
the bar might not be in the window. */
if (cursor_glyph->type == IMAGE_GLYPH)
{
struct glyph_row *r;
r = MATRIX_ROW (w->current_matrix, w->phys_cursor.vpos);
draw_phys_cursor_glyph (w, r, DRAW_CURSOR);
}
else
{
struct android_gc *gc = FRAME_DISPLAY_INFO (f)->scratch_cursor_gc;
unsigned long mask = ANDROID_GC_FOREGROUND | ANDROID_GC_BACKGROUND;
struct face *face = FACE_FROM_ID (f, cursor_glyph->face_id);
struct android_gc_values xgcv;
/* If the glyph's background equals the color we normally draw
the bars cursor in, the bar cursor in its normal color is
invisible. Use the glyph's foreground color instead in this
case, on the assumption that the glyph's colors are chosen so
that the glyph is legible. */
if (face->background == f->output_data.android->cursor_pixel)
xgcv.background = xgcv.foreground = face->foreground;
else
xgcv.background = xgcv.foreground = f->output_data.android->cursor_pixel;
if (gc)
android_change_gc (gc, mask, &xgcv);
else
{
gc = android_create_gc (mask, &xgcv);
FRAME_DISPLAY_INFO (f)->scratch_cursor_gc = gc;
}
android_clip_to_row (w, row, TEXT_AREA, gc, NULL);
if (kind == BAR_CURSOR)
{
int x = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, w->phys_cursor.x);
if (width < 0)
width = FRAME_CURSOR_WIDTH (f);
width = min (cursor_glyph->pixel_width, width);
w->phys_cursor_width = width;
/* If the character under cursor is R2L, draw the bar cursor
on the right of its glyph, rather than on the left. */
if ((cursor_glyph->resolved_level & 1) != 0)
x += cursor_glyph->pixel_width - width;
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x,
WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y),
width, row->height);
}
else /* HBAR_CURSOR */
{
int dummy_x, dummy_y, dummy_h;
int x = WINDOW_TEXT_TO_FRAME_PIXEL_X (w, w->phys_cursor.x);
if (width < 0)
width = row->height;
width = min (row->height, width);
get_phys_cursor_geometry (w, row, cursor_glyph, &dummy_x,
&dummy_y, &dummy_h);
cursor_start_y = WINDOW_TO_FRAME_PIXEL_Y (w, w->phys_cursor.y
+ row->height - width);
if ((cursor_glyph->resolved_level & 1) != 0
&& cursor_glyph->pixel_width > w->phys_cursor_width - 1)
x += cursor_glyph->pixel_width - w->phys_cursor_width + 1;
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f), gc, x,
cursor_start_y,
w->phys_cursor_width - 1, width);
}
android_reset_clip_rectangles (f, gc);
}
}
static void
android_draw_window_cursor (struct window *w, struct glyph_row *glyph_row,
int x, int y, enum text_cursor_kinds cursor_type,
int cursor_width, bool on_p, bool active_p)
{
struct frame *f;
f = WINDOW_XFRAME (w);
if (on_p)
{
w->phys_cursor_type = cursor_type;
w->phys_cursor_on_p = true;
if (glyph_row->exact_window_width_line_p
&& (glyph_row->reversed_p
? (w->phys_cursor.hpos < 0)
: (w->phys_cursor.hpos >= glyph_row->used[TEXT_AREA])))
{
glyph_row->cursor_in_fringe_p = true;
draw_fringe_bitmap (w, glyph_row, glyph_row->reversed_p);
}
else
{
switch (cursor_type)
{
case HOLLOW_BOX_CURSOR:
android_draw_hollow_cursor (w, glyph_row);
break;
case FILLED_BOX_CURSOR:
draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR);
break;
case BAR_CURSOR:
android_draw_bar_cursor (w, glyph_row, cursor_width, BAR_CURSOR);
break;
case HBAR_CURSOR:
android_draw_bar_cursor (w, glyph_row, cursor_width, HBAR_CURSOR);
break;
case NO_CURSOR:
w->phys_cursor_width = 0;
break;
default:
emacs_abort ();
}
}
/* Now proceed to tell the input method the current position of
the cursor, if required. */
if (FRAME_OUTPUT_DATA (f)->need_cursor_updates
&& w == XWINDOW (f->selected_window))
android_set_preeditarea (w, x, y);
}
}
static void
android_draw_vertical_window_border (struct window *w, int x, int y0, int y1)
{
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct face *face;
face = FACE_FROM_ID_OR_NULL (f, VERTICAL_BORDER_FACE_ID);
if (face)
android_set_foreground (f->output_data.android->normal_gc,
face->foreground);
android_draw_line (FRAME_ANDROID_DRAWABLE (f),
f->output_data.android->normal_gc,
x, y0, x, y1);
}
static void
android_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1)
{
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct face *face = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FACE_ID);
struct face *face_first
= FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FIRST_PIXEL_FACE_ID);
struct face *face_last
= FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_LAST_PIXEL_FACE_ID);
unsigned long color = face ? face->foreground : FRAME_FOREGROUND_PIXEL (f);
unsigned long color_first = (face_first
? face_first->foreground
: FRAME_FOREGROUND_PIXEL (f));
unsigned long color_last = (face_last
? face_last->foreground
: FRAME_FOREGROUND_PIXEL (f));
if ((y1 - y0 > x1 - x0) && (x1 - x0 >= 3))
/* A vertical divider, at least three pixels wide: Draw first and
last pixels differently. */
{
android_set_foreground (f->output_data.android->normal_gc,
color_first);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
f->output_data.android->normal_gc,
x0, y0, 1, y1 - y0);
android_set_foreground (f->output_data.android->normal_gc,
color);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
f->output_data.android->normal_gc,
x0 + 1, y0, x1 - x0 - 2, y1 - y0);
android_set_foreground (f->output_data.android->normal_gc,
color_last);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
f->output_data.android->normal_gc,
x1 - 1, y0, 1, y1 - y0);
}
else if ((x1 - x0 > y1 - y0) && (y1 - y0 >= 3))
/* A horizontal divider, at least three pixels high: Draw first
and last pixels differently. */
{
android_set_foreground (f->output_data.android->normal_gc,
color_first);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
f->output_data.android->normal_gc,
x0, y0, x1 - x0, 1);
android_set_foreground (f->output_data.android->normal_gc, color);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
f->output_data.android->normal_gc,
x0, y0 + 1, x1 - x0, y1 - y0 - 2);
android_set_foreground (f->output_data.android->normal_gc,
color_last);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
f->output_data.android->normal_gc,
x0, y1 - 1, x1 - x0, 1);
}
else
{
/* In any other case do not draw the first and last pixels
differently. */
android_set_foreground (f->output_data.android->normal_gc, color);
android_fill_rectangle (FRAME_ANDROID_DRAWABLE (f),
f->output_data.android->normal_gc,
x0, y0, x1 - x0, y1 - y0);
}
}
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
#endif
/* Input method related functions. Some of these are called from Java
within the UI thread. */
/* A counter used to decide when an editing request completes. */
static unsigned long edit_counter;
/* The last counter known to have completed. */
static unsigned long last_edit_counter;
/* Semaphore posted every time the counter increases. */
static sem_t edit_sem;
/* Try to synchronize with the UI thread, waiting a certain amount of
time for outstanding editing requests to complete.
Every time one of the text retrieval functions is called and an
editing request is made, Emacs gives the main thread approximately
100 ms to process it, in order to mostly keep the input method in
sync with the buffer contents. */
static void
android_sync_edit (void)
{
struct timespec start, end, rem;
unsigned long counter;
counter = __atomic_load_n (&last_edit_counter,
__ATOMIC_SEQ_CST);
if (counter == edit_counter)
return;
start = current_timespec ();
end = timespec_add (start, make_timespec (0, 100000000));
while (true)
{
rem = timespec_sub (end, current_timespec ());
/* Timeout. */
if (timespec_sign (rem) < 0)
break;
if (__atomic_load_n (&last_edit_counter,
__ATOMIC_SEQ_CST)
== edit_counter)
break;
sem_timedwait (&edit_sem, &end);
}
}
/* Return a copy of the specified Java string and its length in
*LENGTH. Use the JNI environment ENV. Value is NULL if copying
the string fails. */
static unsigned short *
android_copy_java_string (JNIEnv *env, jstring string, size_t *length)
{
jsize size, i;
const jchar *java;
unsigned short *buffer;
size = (*env)->GetStringLength (env, string);
buffer = malloc (size * sizeof *buffer);
if (!buffer)
return NULL;
java = (*env)->GetStringChars (env, string, NULL);
if (!java)
{
free (buffer);
return NULL;
}
for (i = 0; i < size; ++i)
buffer[i] = java[i];
*length = size;
(*env)->ReleaseStringChars (env, string, java);
return buffer;
}
JNIEXPORT void JNICALL
NATIVE_NAME (beginBatchEdit) (JNIEnv *env, jobject object, jlong window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_START_BATCH_EDIT;
event.ime.start = 0;
event.ime.end = 0;
event.ime.length = 0;
event.ime.position = 0;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (endBatchEdit) (JNIEnv *env, jobject object, jlong window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_END_BATCH_EDIT;
event.ime.start = 0;
event.ime.end = 0;
event.ime.length = 0;
event.ime.position = 0;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (commitCompletion) (JNIEnv *env, jobject object, jlong window,
jstring completion_text, jint position)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
unsigned short *text;
size_t length;
/* First, obtain a copy of the Java string. */
text = android_copy_java_string (env, completion_text, &length);
if (!text)
return;
/* Next, populate the event. Events will always eventually be
delivered on Android, so handle_one_android_event can be relied
on to free text. */
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_COMMIT_TEXT;
event.ime.start = 0;
event.ime.end = 0;
event.ime.length = min (length, PTRDIFF_MAX);
event.ime.position = position;
event.ime.text = text;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (commitText) (JNIEnv *env, jobject object, jlong window,
jstring commit_text, jint position)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
unsigned short *text;
size_t length;
/* First, obtain a copy of the Java string. */
text = android_copy_java_string (env, commit_text, &length);
if (!text)
return;
/* Next, populate the event. Events will always eventually be
delivered on Android, so handle_one_android_event can be relied
on to free text. */
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_COMMIT_TEXT;
event.ime.start = 0;
event.ime.end = 0;
event.ime.length = min (length, PTRDIFF_MAX);
event.ime.position = position;
event.ime.text = text;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (deleteSurroundingText) (JNIEnv *env, jobject object,
jlong window, jint left_length,
jint right_length)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_DELETE_SURROUNDING_TEXT;
event.ime.start = left_length;
event.ime.end = right_length;
event.ime.length = 0;
event.ime.position = 0;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (finishComposingText) (JNIEnv *env, jobject object,
jlong window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT;
event.ime.start = 0;
event.ime.end = 0;
event.ime.length = 0;
event.ime.position = 0;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (replaceText) (JNIEnv *env, jobject object, jlong window,
jint start, jint end, jobject text,
int new_cursor_position, jobject attribute)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
size_t length;
/* First, obtain a copy of the Java string. */
text = android_copy_java_string (env, text, &length);
if (!text)
return;
/* Next, populate the event with the information in this function's
arguments. */
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_REPLACE_TEXT;
event.ime.start = start + 1;
event.ime.end = end + 1;
event.ime.length = length;
event.ime.position = new_cursor_position;
event.ime.text = text;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
/* Structure describing the context used for a text query. */
struct android_conversion_query_context
{
/* The conversion request. */
struct textconv_callback_struct query;
/* The window the request is being made on. */
android_window window;
/* Whether or not the request was successful. */
bool success;
};
/* Obtain the text from the frame whose window is that specified in
DATA using the text conversion query specified there.
Set ((struct android_conversion_query_context *) DATA)->success on
success. */
static void
android_perform_conversion_query (void *data)
{
struct android_conversion_query_context *context;
struct frame *f;
context = data;
/* Find the frame associated with the window. */
f = android_window_to_frame (NULL, context->window);
if (!f)
return;
textconv_query (f, &context->query, 0);
/* context->query.text will have been set even if textconv_query
returns 1. */
context->success = true;
}
/* Convert a string in BUFFER, containing N characters in Emacs's
internal multibyte encoding, to a Java string utilizing the
specified JNI environment ENV.
If N is equal to BYTES, then BUFFER holds unibyte or plain-ASCII
characters. Otherwise, BUFFER holds multibyte characters.
Make sure N and BYTES are absolutely correct, or you are asking for
trouble.
Value is a jstring upon success, NULL otherwise. Any exceptions
generated are not cleared. */
static jstring
android_text_to_string (JNIEnv *env, char *buffer, ptrdiff_t n,
ptrdiff_t bytes)
{
jchar *utf16;
size_t size, index;
jstring string;
int encoded;
if (n == bytes)
{
/* This buffer holds no multibyte characters. */
if (ckd_mul (&size, n, sizeof *utf16))
return NULL;
utf16 = malloc (size);
index = 0;
if (!utf16)
return NULL;
while (n--)
{
utf16[index] = buffer[index];
index++;
}
string = (*env)->NewString (env, utf16, bytes);
free (utf16);
return string;
}
/* Allocate enough to hold N characters. */
if (ckd_mul (&size, n, sizeof *utf16))
return NULL;
utf16 = malloc (size);
index = 0;
if (!utf16)
return NULL;
while (n--)
{
eassert (CHAR_HEAD_P (*buffer));
encoded = STRING_CHAR ((unsigned char *) buffer);
/* Now establish how to save ENCODED into the string.
Emacs operates on multibyte characters, not UTF-16 characters
with surrogate pairs as Android does.
However, character positions in Java are represented as
character (rather than codepoint) indices into UTF-16
strings, meaning that text positions reported to Android can
become decoupled from their actual values if the text
returned incorporates characters that must be encoded as
surrogate pairs.
The hack used by Emacs is to simply replace each multibyte
character that doesn't fit in a jchar with the NULL
character. */
if (encoded >= 65536)
encoded = 0;
utf16[index++] = encoded;
buffer += BYTES_BY_CHAR_HEAD (*buffer);
}
/* Create the string. */
string = (*env)->NewString (env, utf16, index);
free (utf16);
return string;
}
JNIEXPORT jstring JNICALL
NATIVE_NAME (getTextAfterCursor) (JNIEnv *env, jobject object, jlong window,
jint length, jint flags)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
struct android_conversion_query_context context;
jstring string;
/* First, set up the conversion query. */
context.query.position = EMACS_INT_MAX;
context.query.direction = TEXTCONV_FORWARD_CHAR;
context.query.factor = min (length, 65535);
context.query.operation = TEXTCONV_RETRIEVAL;
/* Next, set the rest of the context. */
context.window = window;
context.success = false;
/* Now try to perform the query. */
android_sync_edit ();
if (android_run_in_emacs_thread (android_perform_conversion_query,
&context))
return NULL;
if (!context.success)
return NULL;
/* context->query.text now contains the text in Emacs's internal
UTF-8 based encoding.
Convert it to Java's UTF-16 encoding, which is the same as
UTF-16, except that NULL bytes are encoded as surrogate pairs.
This assumes that `free' can free data allocated with xmalloc. */
string = android_text_to_string (env, context.query.text.text,
context.query.text.length,
context.query.text.bytes);
free (context.query.text.text);
return string;
}
JNIEXPORT jstring JNICALL
NATIVE_NAME (getTextBeforeCursor) (JNIEnv *env, jobject object, jlong window,
jint length, jint flags)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
struct android_conversion_query_context context;
jstring string;
/* First, set up the conversion query. */
context.query.position = TYPE_MINIMUM (EMACS_INT);
context.query.direction = TEXTCONV_BACKWARD_CHAR;
context.query.factor = min (length, 65535);
context.query.operation = TEXTCONV_RETRIEVAL;
/* Next, set the rest of the context. */
context.window = window;
context.success = false;
/* Now try to perform the query. */
android_sync_edit ();
if (android_run_in_emacs_thread (android_perform_conversion_query,
&context))
return NULL;
if (!context.success)
return NULL;
/* context->query.text now contains the text in Emacs's internal
UTF-8 based encoding.
Convert it to Java's UTF-16 encoding, which is the same as
UTF-16, except that NULL bytes are encoded as surrogate pairs.
This assumes that `free' can free data allocated with xmalloc. */
string = android_text_to_string (env, context.query.text.text,
context.query.text.length,
context.query.text.bytes);
free (context.query.text.text);
return string;
}
JNIEXPORT void JNICALL
NATIVE_NAME (setComposingText) (JNIEnv *env, jobject object, jlong window,
jstring composing_text,
jint new_cursor_position)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
unsigned short *text;
size_t length;
/* First, obtain a copy of the Java string. */
text = android_copy_java_string (env, composing_text, &length);
if (!text)
return;
/* Next, populate the event. Events will always eventually be
delivered on Android, so handle_one_android_event can be relied
on to free text. */
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_SET_COMPOSING_TEXT;
event.ime.start = 0;
event.ime.end = 0;
event.ime.length = min (length, PTRDIFF_MAX);
event.ime.position = new_cursor_position;
event.ime.text = text;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (setComposingRegion) (JNIEnv *env, jobject object, jlong window,
jint start, jint end)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_SET_COMPOSING_REGION;
event.ime.start = start + 1;
event.ime.end = end + 1;
event.ime.length = 0;
event.ime.position = 0;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (setSelection) (JNIEnv *env, jobject object, jlong window,
jint start, jint end)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
/* While IMEs want access to the entire selection, Emacs only
supports setting the point. */
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_SET_POINT;
event.ime.start = start + 1;
event.ime.end = end + 1;
event.ime.length = 0;
event.ime.position = start;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
/* Structure describing the context for `getSelection'. */
struct android_get_selection_context
{
/* The window in question. */
android_window window;
/* The position of the window's point when it was last
redisplayed, and its last mark if active. */
ptrdiff_t point, mark;
};
/* Function run on the main thread by `getSelection'.
Place the character position of point in PT. */
static void
android_get_selection (void *data)
{
struct android_get_selection_context *context;
struct frame *f;
struct window *w;
struct buffer *b;
context = data;
/* Look up the associated frame and its selected window. */
f = android_window_to_frame (NULL, context->window);
if (!f)
context->point = -1;
else
{
w = XWINDOW (f->selected_window);
/* Return W's point as it is now. Then, set
W->ephemeral_last_point to match the current point. */
context->point = window_point (w);
w->ephemeral_last_point = context->point;
/* Default context->mark to w->last_point too. */
context->mark = context->point;
/* If the mark is active, then set it properly. Also, adjust
w->last_mark to match. */
b = XBUFFER (w->contents);
if (!NILP (BVAR (b, mark_active)))
{
context->mark = marker_position (BVAR (b, mark));
w->last_mark = context->mark;
}
}
}
JNIEXPORT jintArray JNICALL
NATIVE_NAME (getSelection) (JNIEnv *env, jobject object, jlong window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
struct android_get_selection_context context;
jintArray array;
jint contents[2];
context.window = window;
android_sync_edit ();
if (android_run_in_emacs_thread (android_get_selection,
&context))
return NULL;
if (context.point == -1)
return NULL;
/* Wraparound actually makes more sense than truncation; at least
editing will sort of work. Convert the positions to start from
index 0, as that is what Android expects. */
contents[0] = (unsigned int) min (context.point,
context.mark) - 1;
contents[1] = (unsigned int) max (context.point,
context.mark) - 1;
/* Now create the array. */
array = (*env)->NewIntArray (env, 2);
if (!array)
return NULL;
/* Set its contents. */
(*env)->SetIntArrayRegion (env, array, 0, 2, contents);
return array;
}
JNIEXPORT void JNICALL
NATIVE_NAME (performEditorAction) (JNIEnv *env, jobject object,
jlong window, int action)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
/* It's a good idea to call `android_sync_edit' before sending the
key event. Otherwise, if RET causes the current window to be
changed, any text previously committed might end up in the newly
selected window. */
android_sync_edit ();
/* Undocumented behavior: performEditorAction is apparently expected
to finish composing any text. */
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT;
event.ime.start = 0;
event.ime.end = 0;
/* This value of `length' means that the input method should receive
an update containing the new conversion region. */
event.ime.length = 1;
event.ime.position = 0;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
/* Finally, send the return key press. `counter' is set; this means
that a text conversion barrier will be generated once the event
is read, which will cause subsequent edits to wait until the
edits associated with this key press complete. */
event.xkey.type = ANDROID_KEY_PRESS;
event.xkey.serial = ++event_serial;
event.xkey.window = window;
event.xkey.time = 0;
event.xkey.state = 0;
event.xkey.keycode = 66;
event.xkey.unicode_char = 0;
event.xkey.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (performContextMenuAction) (JNIEnv *env, jobject object,
jlong window, int action)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
int key;
/* Note that ACTION is determined in EmacsInputConnection, and as
such they are not actual resource IDs. */
switch (action)
{
/* The subsequent three keycodes are addressed by
android_get_keysym_name rather than in keyboard.c. */
case 0: /* android.R.id.selectAll */
key = 65536 + 1;
break;
case 1: /* android.R.id.startSelectingText */
key = 65536 + 2;
break;
case 2: /* android.R.id.stopSelectingText */
key = 65536 + 3;
break;
default:
return;
case 3: /* android.R.id.cut */
key = 277;
break;
case 4: /* android.R.id.copy */
key = 278;
break;
case 5: /* android.R.id.paste */
key = 279;
break;
}
event.xkey.type = ANDROID_KEY_PRESS;
event.xkey.serial = ++event_serial;
event.xkey.window = window;
event.xkey.time = 0;
event.xkey.state = 0;
event.xkey.keycode = key;
event.xkey.unicode_char = 0;
event.xkey.counter = ++edit_counter;
android_write_event (&event);
}
/* Text extraction. */
struct android_get_extracted_text_context
{
/* The parameters of the request. */
int hint_max_chars;
/* Token for the request. */
int token;
/* Flags associated with the request. */
int flags;
/* The returned text, or NULL. */
char *text;
/* The size of that text in characters and bytes. */
ptrdiff_t length, bytes;
/* Offsets into that text. */
ptrdiff_t start, start_offset, end_offset;
/* The window. */
android_window window;
/* Whether or not the mark is active. */
bool mark_active;
};
/* Return the extracted text in the extracted text context specified
by DATA. Save its flags and token into its frame's state. */
static void
android_get_extracted_text (void *data)
{
struct android_get_extracted_text_context *request;
struct frame *f;
request = data;
/* Find the frame associated with the window. */
f = android_window_to_frame (NULL, request->window);
if (!f)
return;
/* Now get the extracted text. */
request->text
= get_extracted_text (f, min (request->hint_max_chars, 600),
&request->start, &request->start_offset,
&request->end_offset, &request->length,
&request->bytes, &request->mark_active);
/* See if request->flags & GET_EXTRACTED_TEXT_MONITOR. If so, then
the input method has asked to monitor changes to the extracted
text until the next IM context reset. */
FRAME_ANDROID_OUTPUT (f)->extracted_text_flags = request->flags;
FRAME_ANDROID_OUTPUT (f)->extracted_text_token = request->token;
FRAME_ANDROID_OUTPUT (f)->extracted_text_hint = request->hint_max_chars;
}
/* Structure describing the `ExtractedTextRequest' class.
Valid only on the UI thread. */
struct android_extracted_text_request_class
{
bool initialized;
jfieldID hint_max_chars;
jfieldID token;
};
/* Structure describing the `ExtractedText' class.
Valid only on the UI thread. */
struct android_extracted_text_class
{
jclass class;
jmethodID constructor;
jfieldID flags;
jfieldID partial_start_offset;
jfieldID partial_end_offset;
jfieldID selection_start;
jfieldID selection_end;
jfieldID start_offset;
jfieldID text;
};
/* Fields and methods associated with the `ExtractedTextRequest'
class. */
static struct android_extracted_text_request_class request_class;
/* Fields and methods associated with the `ExtractedText' class. */
static struct android_extracted_text_class text_class;
/* Return an ExtractedText object corresponding to the extracted text
TEXT. START is a character position describing the offset of the
first character in TEXT. START_OFFSET is the offset of the lesser
of point or mark relative to START, and END_OFFSET is that of the
greater of point or mark relative to START. MARK_ACTIVE specifies
whether or not the mark is currently active.
Assume that request_class and text_class have already been
initialized.
Value is NULL if an error occurs; the exception is not cleared,
else a local reference to the ExtractedText object. */
static jobject
android_build_extracted_text (jstring text, ptrdiff_t start,
ptrdiff_t start_offset,
ptrdiff_t end_offset, bool mark_active)
{
JNIEnv *env;
jobject object;
env = android_java_env;
/* Return NULL if the class has not yet been obtained. */
if (!text_class.class)
return NULL;
/* Create an ExtractedText object containing this information. */
object = (*env)->NewObject (env, text_class.class,
text_class.constructor);
if (!object)
return NULL;
(*env)->SetIntField (env, object, text_class.flags,
/* ExtractedText.FLAG_SELECTING */
mark_active ? 2 : 0);
(*env)->SetIntField (env, object, text_class.partial_start_offset, -1);
(*env)->SetIntField (env, object, text_class.partial_end_offset, -1);
(*env)->SetIntField (env, object, text_class.selection_start,
min (start_offset, TYPE_MAXIMUM (jint)));
(*env)->SetIntField (env, object, text_class.selection_end,
min (end_offset, TYPE_MAXIMUM (jint)));
/* Subtract 1 from start: point indices in Emacs start from 1, but
Android expects 0. */
(*env)->SetIntField (env, object, text_class.start_offset,
min (start - 1, TYPE_MAXIMUM (jint)));
(*env)->SetObjectField (env, object, text_class.text, text);
return object;
}
JNIEXPORT jobject JNICALL
NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object,
jlong window, jobject request,
jint flags)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
struct android_get_extracted_text_context context;
jstring string;
jclass class;
jobject object;
/* Initialize both classes if necessary. */
if (!request_class.initialized)
{
class
= (*env)->FindClass (env, ("android/view/inputmethod"
"/ExtractedTextRequest"));
eassert (class);
request_class.hint_max_chars
= (*env)->GetFieldID (env, class, "hintMaxChars", "I");
eassert (request_class.hint_max_chars);
request_class.token
= (*env)->GetFieldID (env, class, "token", "I");
eassert (request_class.token);
request_class.initialized = true;
}
if (!text_class.class)
{
text_class.class
= (*env)->FindClass (env, ("android/view/inputmethod"
"/ExtractedText"));
eassert (text_class.class);
class
= text_class.class
= (*env)->NewGlobalRef (env, text_class.class);
eassert (text_class.class);
text_class.flags
= (*env)->GetFieldID (env, class, "flags", "I");
text_class.partial_start_offset
= (*env)->GetFieldID (env, class, "partialStartOffset", "I");
text_class.partial_end_offset
= (*env)->GetFieldID (env, class, "partialEndOffset", "I");
text_class.selection_start
= (*env)->GetFieldID (env, class, "selectionStart", "I");
text_class.selection_end
= (*env)->GetFieldID (env, class, "selectionEnd", "I");
text_class.start_offset
= (*env)->GetFieldID (env, class, "startOffset", "I");
text_class.text
= (*env)->GetFieldID (env, class, "text", "Ljava/lang/CharSequence;");
text_class.constructor
= (*env)->GetMethodID (env, class, "<init>", "()V");
}
context.hint_max_chars
= (*env)->GetIntField (env, request, request_class.hint_max_chars);
context.token
= (*env)->GetIntField (env, request, request_class.token);
context.flags = flags;
context.text = NULL;
context.window = window;
android_sync_edit ();
if (android_run_in_emacs_thread (android_get_extracted_text,
&context))
return NULL;
if (!context.text)
return NULL;
/* Encode the returned text. */
string = android_text_to_string (env, context.text, context.length,
context.bytes);
free (context.text);
if (!string)
return NULL;
/* Create an ExtractedText object containing this information. */
object = (*env)->NewObject (env, text_class.class,
text_class.constructor);
if (!object)
return NULL;
(*env)->SetIntField (env, object, text_class.flags,
/* ExtractedText.FLAG_SELECTING */
context.mark_active ? 2 : 0);
(*env)->SetIntField (env, object, text_class.partial_start_offset, -1);
(*env)->SetIntField (env, object, text_class.partial_end_offset, -1);
(*env)->SetIntField (env, object, text_class.selection_start,
min (context.start_offset, TYPE_MAXIMUM (jint)));
(*env)->SetIntField (env, object, text_class.selection_end,
min (context.end_offset, TYPE_MAXIMUM (jint)));
/* Subtract 1 from start: point indices in Emacs start from 1, but
Android expects 0. */
(*env)->SetIntField (env, object, text_class.start_offset,
min (context.start - 1, TYPE_MAXIMUM (jint)));
(*env)->SetObjectField (env, object, text_class.text, string);
return object;
}
JNIEXPORT jstring JNICALL
NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object,
jlong window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
struct android_get_extracted_text_context context;
jstring string;
context.hint_max_chars = -1;
context.token = 0;
context.text = NULL;
context.window = window;
android_sync_edit ();
if (android_run_in_emacs_thread (android_get_extracted_text,
&context))
return NULL;
if (!context.text)
return NULL;
/* Encode the returned text. */
string = android_text_to_string (env, context.text, context.length,
context.bytes);
free (context.text);
return string;
}
JNIEXPORT void JNICALL
NATIVE_NAME (requestSelectionUpdate) (JNIEnv *env, jobject object,
jlong window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_REQUEST_SELECTION_UPDATE;
event.ime.start = 0;
event.ime.end = 0;
event.ime.length = 0;
event.ime.position = 0;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
JNIEXPORT void JNICALL
NATIVE_NAME (requestCursorUpdates) (JNIEnv *env, jobject object,
jlong window, jint mode)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_REQUEST_CURSOR_UPDATES;
event.ime.start = 0;
event.ime.end = 0;
event.ime.length = mode;
event.ime.position = 0;
event.ime.text = NULL;
/* Since this does not affect the state of the buffer text, there is
no need to apply synchronization to this event. */
event.ime.counter = 0;
android_write_event (&event);
}
/* Notice that a new input method connection has been initialized and
clear cursor update requests, extracted text requests, and the
composing region. */
JNIEXPORT void JNICALL
NATIVE_NAME (clearInputFlags) (JNIEnv *env, jobject object,
jlong window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
union android_event event;
event.ime.type = ANDROID_INPUT_METHOD;
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_FINISH_COMPOSING_TEXT;
event.ime.start = 0;
event.ime.end = 0;
/* This value of `length' means that updates to the cursor position
and extracted text should not be reported anymore. */
event.ime.length = 2;
event.ime.position = 0;
event.ime.text = NULL;
event.ime.counter = ++edit_counter;
android_write_event (&event);
}
/* Context for a call to `getSurroundingText'. */
struct android_get_surrounding_text_context
{
/* Number of characters before the region to return. */
int before_length;
/* Number of characters after the region to return. */
int after_length;
/* The returned text, or NULL. */
char *text;
/* The size of that text in characters and bytes. */
ptrdiff_t length, bytes;
/* Offsets into that text. */
ptrdiff_t offset, start, end;
/* The start and end indices of the conversion region.
-1 if it does not exist. */
ptrdiff_t conversion_start, conversion_end;
/* The window. */
android_window window;
};
/* Return the surrounding text in the surrounding text context
specified by DATA. */
static void
android_get_surrounding_text (void *data)
{
struct android_get_surrounding_text_context *request;
struct frame *f;
ptrdiff_t temp;
request = data;
/* Find the frame associated with the window. */
f = android_window_to_frame (NULL, request->window);
if (!f)
return;
/* Now get the surrounding text. */
request->text
= get_surrounding_text (f, request->before_length,
request->after_length, &request->length,
&request->bytes, &request->offset,
&request->start, &request->end);
/* Sort request->start and request->end for compatibility with some
bad input methods. */
if (request->end < request->start)
{
temp = request->start;
request->start = request->end;
request->end = temp;
}
/* Retrieve the conversion region. */
request->conversion_start = -1;
request->conversion_end = -1;
if (MARKERP (f->conversion.compose_region_start))
{
request->conversion_start
= marker_position (f->conversion.compose_region_start) - 1;
request->conversion_end
= marker_position (f->conversion.compose_region_end) - 1;
}
}
/* Return a local reference to a `SurroundingText' object describing
WINDOW's surrounding text. ENV should be a valid JNI environment
for the current thread.
BEFORE_LENGTH and AFTER_LENGTH specify the number of characters
around point and mark to return.
Return the conversion region (or -1) in *CONVERSION_START and
*CONVERSION_END if non-NULL.
Value is the object upon success, else NULL. */
static jobject
android_get_surrounding_text_internal (JNIEnv *env, jlong window,
jint before_length,
jint after_length,
ptrdiff_t *conversion_start,
ptrdiff_t *conversion_end)
{
struct android_get_surrounding_text_context context;
jstring string;
jobject object;
static jclass class;
static jmethodID constructor;
/* Initialize CLASS if it has not yet been initialized. */
if (!class)
{
class
= (*env)->FindClass (env, ("android/view/inputmethod"
"/SurroundingText"));
#if __ANDROID_API__ < 31
/* If CLASS cannot be found, the version of Android currently
running is too old. */
if (!class)
{
(*env)->ExceptionClear (env);
return NULL;
}
#else /* __ANDROID_API__ >= 31 */
eassert (class);
#endif /* __ANDROID_API__ < 31 */
class = (*env)->NewGlobalRef (env, class);
if (!class)
/* Clear class to prevent a local reference from remaining in
`class'. */
return (class = NULL);
/* Now look for its constructor. */
constructor = (*env)->GetMethodID (env, class, "<init>",
"(Ljava/lang/CharSequence;III)V");
eassert (constructor);
}
context.before_length = before_length;
context.after_length = after_length;
context.window = window;
context.text = NULL;
android_sync_edit ();
if (android_run_in_emacs_thread (android_get_surrounding_text,
&context))
return NULL;
if (!context.text)
return NULL;
/* Encode the returned text. */
string = android_text_to_string (env, context.text, context.length,
context.bytes);
free (context.text);
if (!string)
return NULL;
/* Create an SurroundingText object containing this information. */
object = (*env)->NewObject (env, class, constructor, string,
(jint) min (context.start,
TYPE_MAXIMUM (jint)),
(jint) min (context.end,
TYPE_MAXIMUM (jint)),
/* Adjust point offsets to fit into
Android's 0-based indexing. */
(jint) min (context.offset - 1,
TYPE_MAXIMUM (jint)));
if (!object)
return NULL;
/* Now return the conversion region if that was requested. */
if (conversion_start)
{
*conversion_start = context.conversion_start;
*conversion_end = context.conversion_start;
}
return object;
}
JNIEXPORT jobject JNICALL
NATIVE_NAME (getSurroundingText) (JNIEnv *env, jobject object,
jlong window, jint before_length,
jint after_length, jint flags)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
return android_get_surrounding_text_internal (env, window, before_length,
after_length, NULL, NULL);
}
JNIEXPORT jobject JNICALL
NATIVE_NAME (takeSnapshot) (JNIEnv *env, jobject object, jlong window)
{
JNI_STACK_ALIGNMENT_PROLOGUE;
jobject text;
ptrdiff_t start, end;
static jclass class;
static jmethodID constructor;
/* First, obtain the surrounding text and conversion region. */
text = android_get_surrounding_text_internal (env, window, 600, 600,
&start, &end);
/* If that fails, return NULL. */
if (!text)
return NULL;
/* Next, initialize the TextSnapshot class. */
if (!class)
{
class
= (*env)->FindClass (env, ("android/view/inputmethod"
"/TextSnapshot"));
#if __ANDROID_API__ < 33
/* If CLASS cannot be found, the version of Android currently
running is too old. */
if (!class)
{
(*env)->ExceptionClear (env);
return NULL;
}
#else /* __ANDROID_API__ >= 33 */
eassert (class);
#endif /* __ANDROID_API__ < 33 */
class = (*env)->NewGlobalRef (env, class);
if (!class)
/* Clear class to prevent a local reference from remaining in
`class'. */
return (class = NULL);
constructor = (*env)->GetMethodID (env, class, "<init>",
"(Landroid/view/inputmethod"
"/SurroundingText;III)V");
eassert (constructor);
}
/* Try to create a TextSnapshot object. */
eassert (start <= end);
object = (*env)->NewObject (env, class, constructor, text,
(jint) min (start, TYPE_MAXIMUM (jint)),
(jint) min (end, TYPE_MAXIMUM (jint)),
(jint) 0);
return object;
}
#ifdef __clang__
#pragma clang diagnostic pop
#else /* GCC */
#pragma GCC diagnostic pop
#endif /* __clang__ */
/* Tell the input method where the composing region and selection of
F's selected window is located. W should be F's selected window;
if it is NULL, then F->selected_window is used in its place. */
static void
android_update_selection (struct frame *f, struct window *w)
{
ptrdiff_t start, end, point, mark, start_offset, end_offset;
ptrdiff_t length, bytes;
struct buffer *b;
int hint, token;
char *text;
jobject extracted;
jstring string;
bool mark_active;
ptrdiff_t field_start, field_end;
/* Offset these values by the start offset of the field. */
get_conversion_field (f, &field_start, &field_end);
if (MARKERP (f->conversion.compose_region_start))
{
eassert (MARKERP (f->conversion.compose_region_end));
/* Indexing in android starts from 0 instead of 1. */
start = marker_position (f->conversion.compose_region_start);
end = marker_position (f->conversion.compose_region_end);
/* Offset and detect underflow. */
start = max (start, field_start) - field_start;
end = min (end, field_end) - field_start;
if (end < 0 || start < 0)
end = start = -1;
}
else
start = -1, end = -1;
/* Now constrain START and END to the maximum size of a Java
integer. */
start = min (start, TYPE_MAXIMUM (jint));
end = min (end, TYPE_MAXIMUM (jint));
if (!w)
w = XWINDOW (f->selected_window);
/* Figure out where the point and mark are. If the mark is not
active, then point is set to equal mark. */
b = XBUFFER (w->contents);
point = min (min (max (w->ephemeral_last_point,
field_start),
field_end) - field_start,
TYPE_MAXIMUM (jint));
mark = ((!NILP (BVAR (b, mark_active))
&& w->last_mark != -1)
? min (min (max (w->last_mark, field_start),
field_end) - field_start,
TYPE_MAXIMUM (jint))
: point);
/* Send the update. Android doesn't employ a concept of "point" and
"mark"; instead, it only has a selection, where the start of the
selection is less than or equal to the end, and the region is
"active" when those two values differ. The indices will have been
converted from 1-based Emacs indices to 0-based Android ones. */
android_update_ic (FRAME_ANDROID_WINDOW (f), min (point, mark),
max (point, mark), start, end);
/* Update the extracted text as well, if the input method has asked
for updates. 1 is InputConnection.GET_EXTRACTED_TEXT_MONITOR. */
if (FRAME_ANDROID_OUTPUT (f)->extracted_text_flags & 1)
{
hint = FRAME_ANDROID_OUTPUT (f)->extracted_text_hint;
token = FRAME_ANDROID_OUTPUT (f)->extracted_text_token;
text = get_extracted_text (f, min (hint, 600), &start,
&start_offset, &end_offset,
&length, &bytes, &mark_active);
if (text)
{
/* Make a string out of the extracted text. */
string = android_text_to_string (android_java_env,
text, length, bytes);
xfree (text);
android_exception_check ();
/* Make extracted text out of that string. */
extracted = android_build_extracted_text (string, start,
start_offset,
end_offset,
mark_active);
android_exception_check_1 (string);
ANDROID_DELETE_LOCAL_REF (string);
if (extracted)
{
/* extracted is now an associated ExtractedText object.
Perform the update. */
android_update_extracted_text (FRAME_ANDROID_WINDOW (f),
extracted, token);
ANDROID_DELETE_LOCAL_REF (extracted);
}
}
}
}
/* Return whether or not EVENT is an input method event destined for
the frame (struct frame *) ARG. */
static bool
android_event_is_for_frame (union android_event *event, void *arg)
{
struct frame *f;
f = arg;
return (event->type == ANDROID_INPUT_METHOD
&& event->ime.window == FRAME_ANDROID_WINDOW (f));
}
/* Notice that the input method connection to F should be reset as a
result of a change to its contents. */
static void
android_reset_conversion (struct frame *f)
{
enum android_ic_mode mode;
struct window *w;
struct buffer *buffer;
Lisp_Object style;
union android_event event;
/* Reset the input method.
Select an appropriate ``input mode'' based on whether or not the
minibuffer window is selected, which in turn affects if ``RET''
inserts a newline or sends an editor action Emacs transforms into
a key event (refer to `performEditorAction'.) */
w = XWINDOW (f->selected_window);
buffer = XBUFFER (WINDOW_BUFFER (w));
style = (EQ (find_symbol_value (Qoverriding_text_conversion_style),
Qlambda)
? BVAR (buffer, text_conversion_style)
: find_symbol_value (Qoverriding_text_conversion_style));
if (NILP (style) || conversion_disabled_p ())
mode = ANDROID_IC_MODE_NULL;
else if (EQ (style, Qpassword))
mode = ANDROID_IC_MODE_PASSWORD;
else if (EQ (style, Qaction) || EQ (f->selected_window,
f->minibuffer_window))
mode = ANDROID_IC_MODE_ACTION;
else
mode = ANDROID_IC_MODE_TEXT;
/* Remove any existing input method events that apply to FRAME from
the event queue.
There's a small window between this and the call to
android_reset_ic between which more events can be generated. */
while (android_check_if_event (&event, android_event_is_for_frame, f))
{
switch (event.ime.operation)
{
case ANDROID_IME_COMMIT_TEXT:
case ANDROID_IME_FINISH_COMPOSING_TEXT:
case ANDROID_IME_SET_COMPOSING_TEXT:
xfree (event.ime.text);
break;
default:
break;
}
}
android_reset_ic (FRAME_ANDROID_WINDOW (f), mode);
/* Clear extracted text flags. Since the IM has been reinitialized,
it should no longer be displaying extracted text. */
FRAME_ANDROID_OUTPUT (f)->extracted_text_flags = 0;
/* Move its selection to the specified position. */
android_update_selection (f, NULL);
}
/* Notice that point has moved in the F's selected window's selected
buffer. W is the window, and BUFFER is that buffer. */
static void
android_set_point (struct frame *f, struct window *w,
struct buffer *buffer)
{
android_update_selection (f, w);
}
/* Notice that the composition region on F's old selected window has
changed. */
static void
android_compose_region_changed (struct frame *f)
{
android_update_selection (f, XWINDOW (f->old_selected_window));
}
/* Notice that the text conversion has completed. */
static void
android_notify_conversion (unsigned long counter)
{
int sval;
if (last_edit_counter < counter)
__atomic_store_n (&last_edit_counter, counter,
__ATOMIC_SEQ_CST);
sem_getvalue (&edit_sem, &sval);
if (sval <= 0)
sem_post (&edit_sem);
}
/* Android text conversion interface. */
static struct textconv_interface text_conversion_interface =
{
android_reset_conversion,
android_set_point,
android_compose_region_changed,
android_notify_conversion,
};
#endif /* !ANDROID_STUBIFY */
static struct redisplay_interface android_redisplay_interface =
{
#ifndef ANDROID_STUBIFY
android_frame_parm_handlers,
gui_produce_glyphs,
gui_write_glyphs,
gui_insert_glyphs,
gui_clear_end_of_line,
android_scroll_run,
android_after_update_window_line,
NULL, /* update_window_begin */
NULL, /* update_window_end */
android_flip_and_flush,
gui_clear_window_mouse_face,
gui_get_glyph_overhangs,
gui_fix_overlapping_area,
android_draw_fringe_bitmap,
NULL, /* define_fringe_bitmap */
NULL, /* destroy_fringe_bitmap */
android_compute_glyph_string_overhangs,
android_draw_glyph_string,
android_define_frame_cursor,
android_clear_frame_area,
android_clear_under_internal_border,
android_draw_window_cursor,
android_draw_vertical_window_border,
android_draw_window_divider,
NULL,
android_show_hourglass,
android_hide_hourglass,
android_default_font_parameter,
#endif
};
void
frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y)
{
/* This cannot be implemented on Android, and as such is left
blank. */
}
char *
get_keysym_name (int keysym)
{
static char buffer[64];
#ifndef ANDROID_STUBIFY
android_get_keysym_name (keysym, buffer, 64);
#else
emacs_abort ();
#endif
return buffer;
}
/* Create a struct terminal, initialize it with the Android specific
functions and make DISPLAY->TERMINAL point to it. */
static struct terminal *
android_create_terminal (struct android_display_info *dpyinfo)
{
struct terminal *terminal;
terminal = create_terminal (output_android,
&android_redisplay_interface);
terminal->display_info.android = dpyinfo;
dpyinfo->terminal = terminal;
/* kboard is initialized in android_term_init. */
#ifndef ANDROID_STUBIFY
terminal->clear_frame_hook = android_clear_frame;
terminal->ring_bell_hook = android_ring_bell;
terminal->toggle_invisible_pointer_hook
= android_toggle_invisible_pointer;
terminal->update_begin_hook = android_update_begin;
terminal->update_end_hook = android_update_end;
terminal->read_socket_hook = android_read_socket;
terminal->frame_up_to_date_hook = android_frame_up_to_date;
terminal->buffer_flipping_unblocked_hook
= android_buffer_flipping_unblocked_hook;
terminal->defined_color_hook = android_defined_color;
terminal->query_frame_background_color
= android_query_frame_background_color;
terminal->query_colors = android_query_colors;
terminal->mouse_position_hook = android_mouse_position;
terminal->get_focus_frame = android_get_focus_frame;
terminal->focus_frame_hook = android_focus_frame;
terminal->frame_rehighlight_hook = android_frame_rehighlight_hook;
terminal->frame_raise_lower_hook = android_frame_raise_lower;
terminal->frame_visible_invisible_hook
= android_make_frame_visible_invisible;
terminal->fullscreen_hook = android_fullscreen_hook;
terminal->iconify_frame_hook = android_iconify_frame;
terminal->set_window_size_hook = android_set_window_size;
terminal->set_frame_offset_hook = android_set_offset;
terminal->set_frame_alpha_hook = android_set_alpha;
terminal->set_new_font_hook = android_new_font;
terminal->set_bitmap_icon_hook = android_bitmap_icon;
terminal->implicit_set_name_hook = android_implicitly_set_name;
terminal->menu_show_hook = android_menu_show;
terminal->popup_dialog_hook = android_popup_dialog;
terminal->change_tab_bar_height_hook = android_change_tab_bar_height;
terminal->change_tool_bar_height_hook = android_change_tool_bar_height;
terminal->set_scroll_bar_default_width_hook
= android_set_scroll_bar_default_width;
terminal->set_scroll_bar_default_height_hook
= android_set_scroll_bar_default_height;
terminal->free_pixmap = android_free_pixmap_hook;
terminal->delete_frame_hook = android_delete_frame;
terminal->delete_terminal_hook = android_delete_terminal;
#else
emacs_abort ();
#endif
return terminal;
}
/* Initialize the Android terminal interface. The display connection
has already been set up by the system at this point. */
void
android_term_init (void)
{
struct terminal *terminal;
struct android_display_info *dpyinfo;
Lisp_Object color_file, color_map;
dpyinfo = xzalloc (sizeof *dpyinfo);
terminal = android_create_terminal (dpyinfo);
terminal->kboard = allocate_kboard (Qandroid);
terminal->kboard->reference_count++;
dpyinfo->n_planes = 24;
dpyinfo->n_image_planes = 24;
/* This function should only be called once at startup. */
eassert (!x_display_list);
x_display_list = dpyinfo;
dpyinfo->name_list_element
= Fcons (build_pure_c_string ("android"), Qnil);
color_file = Fexpand_file_name (build_string ("rgb.txt"),
Vdata_directory);
color_map = Fx_load_color_file (color_file);
if (NILP (color_map))
fatal ("Could not read %s.\n", SDATA (color_file));
dpyinfo->color_map = color_map;
#ifndef ANDROID_STUBIFY
dpyinfo->resx = android_pixel_density_x;
dpyinfo->resy = android_pixel_density_y;
dpyinfo->font_resolution = android_scaled_pixel_density;
#endif /* ANDROID_STUBIFY */
/* https://lists.gnu.org/r/emacs-devel/2015-11/msg00194.html */
dpyinfo->smallest_font_height = 1;
dpyinfo->smallest_char_width = 1;
terminal->name = xstrdup ("android");
{
Lisp_Object system_name = Fsystem_name ();
static char const title[] = "GNU Emacs";
if (STRINGP (system_name))
{
static char const at[] = " at ";
ptrdiff_t nbytes = sizeof (title) + sizeof (at);
if (ckd_add (&nbytes, nbytes, SBYTES (system_name)))
memory_full (SIZE_MAX);
dpyinfo->x_id_name = xmalloc (nbytes);
sprintf (dpyinfo->x_id_name, "%s%s%s", title, at,
SDATA (system_name));
}
else
{
dpyinfo->x_id_name = xmalloc (sizeof (title));
strcpy (dpyinfo->x_id_name, title);
}
}
/* The display "connection" is now set up, and it must never go
away. */
terminal->reference_count = 30000;
/* Set the baud rate to the same value it gets set to on X. */
baud_rate = 19200;
#ifndef ANDROID_STUBIFY
sem_init (&edit_sem, false, 0);
register_textconv_interface (&text_conversion_interface);
#endif
}
/* Set Vandroid_build_fingerprint to a reasonable value, and also
Vandroid_build_manufacturer. */
static void
android_set_build_fingerprint (void)
{
#ifdef ANDROID_STUBIFY
Vandroid_build_fingerprint = Qnil;
#else /* !ANDROID_STUBIFY */
jclass class;
jfieldID field;
jobject string;
const char *data;
/* Set class to NULL so freeing an uninitialized local ref can be
avoided. */
class = NULL;
/* Likewise for string. */
string = NULL;
if (!android_init_gui)
goto fail;
else
{
/* Obtain Build.FINGERPRINT. Clear exceptions after each query;
JNI can't find Build.FINGERPRINT on some systems. */
class = (*android_java_env)->FindClass (android_java_env,
"android/os/Build");
(*android_java_env)->ExceptionClear (android_java_env);
if (!class)
goto fail;
field = (*android_java_env)->GetStaticFieldID (android_java_env,
class,
"FINGERPRINT",
"Ljava/lang/String;");
(*android_java_env)->ExceptionClear (android_java_env);
if (!field)
goto fail;
string
= (*android_java_env)->GetStaticObjectField (android_java_env,
class, field);
(*android_java_env)->ExceptionClear (android_java_env);
if (!string)
goto fail;
data = (*android_java_env)->GetStringUTFChars (android_java_env,
string, NULL);
(*android_java_env)->ExceptionClear (android_java_env);
if (!data)
goto fail;
Vandroid_build_fingerprint = build_string_from_utf8 (data);
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
string, data);
/* Now retrieve Build.MANUFACTURER. */
ANDROID_DELETE_LOCAL_REF (string);
string = NULL;
field = (*android_java_env)->GetStaticFieldID (android_java_env,
class,
"MANUFACTURER",
"Ljava/lang/String;");
(*android_java_env)->ExceptionClear (android_java_env);
if (!field)
goto fail;
string
= (*android_java_env)->GetStaticObjectField (android_java_env,
class, field);
(*android_java_env)->ExceptionClear (android_java_env);
if (!string)
goto fail;
data = (*android_java_env)->GetStringUTFChars (android_java_env,
string, NULL);
(*android_java_env)->ExceptionClear (android_java_env);
if (!data)
goto fail;
Vandroid_build_manufacturer = build_string_from_utf8 (data);
(*android_java_env)->ReleaseStringUTFChars (android_java_env,
string, data);
}
if (string)
ANDROID_DELETE_LOCAL_REF (string);
ANDROID_DELETE_LOCAL_REF (class);
return;
fail:
if (class)
ANDROID_DELETE_LOCAL_REF (class);
Vandroid_build_fingerprint = Qnil;
Vandroid_build_manufacturer = Qnil;
#endif /* ANDROID_STUBIFY */
}
void
syms_of_androidterm (void)
{
Fprovide (Qandroid, Qnil);
DEFVAR_LISP ("android-wait-for-event-timeout",
Vandroid_wait_for_event_timeout,
doc: /* How long to wait for Android events.
Emacs will wait up to this many seconds to receive events after
making changes which affect the state of the graphical interface.
Under some situations this can take an indefinite amount of time,
so it is important to limit the wait.
If set to a non-float value, there will be no wait at all. */);
Vandroid_wait_for_event_timeout = make_float (0.1);
DEFVAR_INT ("android-quit-keycode", android_quit_keycode,
doc: /* Keycode that signals quit when typed twice in rapid succession.
This is the key code of a key whose repeated activation should prompt
Emacs to quit, enabling quitting on systems where a keyboard capable of
typing C-g is unavailable, when set to a key that does exist on the
device. Its value must be a keycode defined by the operating system,
and defaults to 25 (KEYCODE_VOLUME_DOWN), though one of the following
values might be desired on those devices where this default is also
unavailable, or if another key must otherwise serve this function
instead:
- 4 (KEYCODE_BACK)
- 24 (KEYCODE_VOLUME_UP) */);
android_quit_keycode = 25;
DEFVAR_BOOL ("x-use-underline-position-properties",
x_use_underline_position_properties,
doc: /* SKIP: real doc in xterm.c. */);
x_use_underline_position_properties = true;
DEFSYM (Qx_use_underline_position_properties,
"x-use-underline-position-properties");
DEFVAR_BOOL ("x-underline-at-descent-line",
x_underline_at_descent_line,
doc: /* SKIP: real doc in xterm.c. */);
x_underline_at_descent_line = false;
DEFVAR_LISP ("android-build-fingerprint", Vandroid_build_fingerprint,
doc: /* String identifying the device's OS version.
This is a string that uniquely identifies the version of Android
Emacs is running on. */);
Vandroid_build_fingerprint = Qnil;
DEFVAR_LISP ("android-build-manufacturer", Vandroid_build_manufacturer,
doc: /* Name of the developer of the running version of Android. */);
Vandroid_build_manufacturer = Qnil;
DEFVAR_INT ("android-display-planes", android_display_planes,
doc: /* Depth and visual class of the display.
This variable controls the visual class and depth of the display, which
cannot be detected on Android. The default value of 24, and values from
there to 8 represent a TrueColor display providing 24 planes, values
between 8 and 1 StaticGray displays providing that many planes, and 1 or
lower monochrome displays with a single plane. Modifications to this
variable must be completed before the window system is initialized, in,
for instance, `early-init.el', or they will be of no effect. */);
android_display_planes = 24;
DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym,
doc: /* SKIP: real doc in xterm.c. */);
Vx_ctrl_keysym = Qnil;
DEFVAR_LISP ("x-alt-keysym", Vx_alt_keysym,
doc: /* SKIP: real doc in xterm.c. */);
Vx_alt_keysym = Qnil;
DEFVAR_LISP ("x-hyper-keysym", Vx_hyper_keysym,
doc: /* SKIP: real doc in xterm.c. */);
Vx_hyper_keysym = Qnil;
DEFVAR_LISP ("x-meta-keysym", Vx_meta_keysym,
doc: /* SKIP: real doc in xterm.c. */);
Vx_meta_keysym = Qnil;
DEFVAR_LISP ("x-super-keysym", Vx_super_keysym,
doc: /* SKIP: real doc in xterm.c. */);
Vx_super_keysym = Qnil;
/* Only defined so loadup.el loads scroll-bar.el. */
DEFVAR_LISP ("x-toolkit-scroll-bars", Vx_toolkit_scroll_bars,
doc: /* SKIP: real doc in xterm.c. */);
Vx_toolkit_scroll_bars = Qnil;
/* Avoid dumping Vandroid_build_fingerprint. */
pdumper_do_now_and_after_load (android_set_build_fingerprint);
DEFSYM (Qx_underline_at_descent_line, "x-underline-at-descent-line");
/* Symbols defined for DND events. */
DEFSYM (Quri, "uri");
DEFSYM (Qtext, "text");
/* Symbols defined for modifier value reassignment. */
DEFSYM (Qmodifier_value, "modifier-value");
DEFSYM (Qctrl, "ctrl");
Fput (Qctrl, Qmodifier_value, make_fixnum (ctrl_modifier));
DEFSYM (Qalt, "alt");
Fput (Qalt, Qmodifier_value, make_fixnum (alt_modifier));
DEFSYM (Qmeta, "meta");
Fput (Qmeta, Qmodifier_value, make_fixnum (meta_modifier));
DEFSYM (Qsuper, "super");
Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier));
}
void
mark_androidterm (void)
{
if (x_display_list)
mark_object (x_display_list->color_map);
}