Communicate frame titles to the window manager on Android

* java/org/gnu/emacs/EmacsActivity.java (detachWindow)
(attachWindow): Call updateWmName.
(updateWmName): New function; transfer wm name from the window
attached to the task's description.

* java/org/gnu/emacs/EmacsWindow.java (EmacsWindow)
<wmName>: New field.
(setWmName): New function.

* src/android.c (android_init_emacs_window): Link to new
function.
(android_set_wm_name): New function.

* src/android.h (struct android_emacs_service): Delete unused
entries.

* src/androidfns.c (android_set_name_internal, android_set_name)
(android_implicitly_set_name, android_explicitly_set_name)
(android_set_title): Port from X.

* src/androidterm.c (android_term_init): Compute default frame
title.

* src/androidterm.h (struct android_display_info) <x_id_name>:
New field.
This commit is contained in:
Po Lu 2024-05-13 14:40:15 +08:00
parent f560e75933
commit 9443f8145e
7 changed files with 192 additions and 5 deletions

View file

@ -27,6 +27,7 @@
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.app.ActivityManager.TaskDescription;
import android.content.ContentResolver;
import android.content.Context;
@ -166,6 +167,10 @@ children and RESETWHENCHILDLESS is set (implying it is a
layout.removeView (window.view);
window = null;
/* Reset the WM name. */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
updateWmName ();
invalidateFocus (0);
}
}
@ -205,6 +210,11 @@ children and RESETWHENCHILDLESS is set (implying it is a
invalidateFocus (1);
}
});
/* Synchronize the window's window manager name with this activity's
task in the recents list. */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
updateWmName ();
}
@Override
@ -522,6 +532,29 @@ children and RESETWHENCHILDLESS is set (implying it is a
}
}
/* Update the name of this activity's task description from the
current window, or reset the same if no window is attached. */
@SuppressWarnings ("deprecation")
public final void
updateWmName ()
{
String wmName;
TaskDescription description;
if (window == null || window.wmName == null)
wmName = "Emacs";
else
wmName = window.wmName;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
description = new TaskDescription (wmName);
else
description = (new TaskDescription.Builder ()
.setLabel (wmName).build ());
setTaskDescription (description);
}
@Override
public final void
onAttachedToWindow ()

View file

@ -169,6 +169,11 @@ private static class Coordinate
and whether this window has previously been attached to a task. */
public boolean preserve, previouslyAttached;
/* The window manager name of this window, which supplies the name of
activities in which it is displayed as a toplevel window, or
NULL. */
public String wmName;
public
EmacsWindow (final EmacsWindow parent, int x, int y,
int width, int height, boolean overrideRedirect)
@ -1562,6 +1567,36 @@ else if (EmacsWindow.this.isMapped)
return dontFocusOnMap;
}
public void
setWmName (final String wmName)
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
return;
EmacsService.SERVICE.runOnUiThread (new Runnable () {
@Override
public void
run ()
{
EmacsActivity activity;
Object tem;
EmacsWindow.this.wmName = wmName;
/* If an activity is already attached, replace its task
description. */
tem = getAttachedConsumer ();
if (tem != null && tem instanceof EmacsActivity)
{
activity = (EmacsActivity) tem;
activity.updateWmName ();
}
}
});
}
public int[]
translateCoordinates (int x, int y)
{
@ -1631,7 +1666,7 @@ else if (EmacsWindow.this.isMapped)
fullscreen = isFullscreen;
tem = getAttachedConsumer ();
if (tem != null)
if (tem != null && tem instanceof EmacsActivity)
{
activity = (EmacsActivity) tem;
activity.syncFullscreenWith (EmacsWindow.this);

View file

@ -115,6 +115,7 @@ struct android_emacs_window
jmethodID recreate_activity;
jmethodID clear_window;
jmethodID clear_area;
jmethodID set_wm_name;
};
struct android_emacs_cursor
@ -1842,6 +1843,7 @@ android_init_emacs_window (void)
FIND_METHOD (recreate_activity, "recreateActivity", "()V");
FIND_METHOD (clear_window, "clearWindow", "()V");
FIND_METHOD (clear_area, "clearArea", "(IIII)V");
FIND_METHOD (set_wm_name, "setWmName", "(Ljava/lang/String;)V");
#undef FIND_METHOD
}
@ -5603,6 +5605,27 @@ android_set_dont_accept_focus (android_window handle,
android_exception_check ();
}
/* Set the WM name of HANDLE to STRING, a Java string. This name
provides the task description of activities that receive HANDLE. */
void
android_set_wm_name (android_window handle, jstring name)
{
jmethodID method;
jobject window;
window = android_resolve_handle (handle);
method = window_class.set_wm_name;
if (android_get_current_api_level () < 21)
return;
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env, window,
window_class.class, method,
name);
android_exception_check ();
}
void
android_get_keysym_name (int keysym, char *name_return, size_t size)
{

View file

@ -118,6 +118,7 @@ extern bool android_detect_keyboard (void);
extern void android_set_dont_focus_on_map (android_window, bool);
extern void android_set_dont_accept_focus (android_window, bool);
extern void android_set_wm_name (android_window, jstring);
extern int android_verify_jni_string (const char *);
extern jstring android_build_string (Lisp_Object, ...);
@ -275,8 +276,6 @@ struct android_emacs_service
jmethodID draw_rectangle;
jmethodID draw_line;
jmethodID draw_point;
jmethodID clear_window;
jmethodID clear_area;
jmethodID ring_bell;
jmethodID query_tree;
jmethodID get_screen_width;

View file

@ -211,18 +211,90 @@ android_set_parent_frame (struct frame *f, Lisp_Object new_value,
FRAME_TERMINAL (f)->fullscreen_hook (f);
}
/* Set the WM name to NAME for frame F. Also set the icon name.
If the frame already has an icon name, use that, otherwise set the
icon name to NAME. */
static void
android_set_name_internal (struct frame *f, Lisp_Object name)
{
jstring java_name;
if (FRAME_ANDROID_WINDOW (f))
{
java_name = android_build_string (name, NULL);
android_set_wm_name (FRAME_ANDROID_WINDOW (f), java_name);
ANDROID_DELETE_LOCAL_REF (java_name);
}
}
/* Change the name of frame F to NAME. If NAME is nil, set F's name to
x_id_name.
If EXPLICIT is true, that indicates that lisp code is setting the
name; if NAME is a string, set F's name to NAME and set
F->explicit_name; if NAME is Qnil, then clear F->explicit_name.
If EXPLICIT is false, that indicates that Emacs redisplay code is
suggesting a new name, which lisp code should override; if
F->explicit_name is set, ignore the new name; otherwise, set it. */
static void
android_set_name (struct frame *f, Lisp_Object name, bool explicit)
{
/* Make sure that requests from lisp code override requests from
Emacs redisplay code. */
if (explicit)
{
/* If we're switching from explicit to implicit, we had better
update the mode lines and thereby update the title. */
if (f->explicit_name && NILP (name))
update_mode_lines = 37;
f->explicit_name = ! NILP (name);
}
else if (f->explicit_name)
return;
/* If NAME is nil, set the name to the x_id_name. */
if (NILP (name))
{
/* Check for no change needed in this very common case
before we do any consing. */
if (!strcmp (FRAME_DISPLAY_INFO (f)->x_id_name,
SSDATA (f->name)))
return;
name = build_string (FRAME_DISPLAY_INFO (f)->x_id_name);
}
else
CHECK_STRING (name);
/* Don't change the name if it's already NAME. */
if (! NILP (Fstring_equal (name, f->name)))
return;
fset_name (f, name);
/* For setting the frame title, the title parameter should override
the name parameter. */
if (! NILP (f->title))
name = f->title;
android_set_name_internal (f, name);
}
void
android_implicitly_set_name (struct frame *f, Lisp_Object arg,
Lisp_Object oldval)
{
android_set_name (f, arg, false);
}
void
android_explicitly_set_name (struct frame *f, Lisp_Object arg,
Lisp_Object oldval)
{
android_set_name (f, arg, true);
}
/* Set the number of lines used for the tool bar of frame F to VALUE.
@ -2988,6 +3060,8 @@ android_set_title (struct frame *f, Lisp_Object name,
name = f->name;
else
CHECK_STRING (name);
android_set_name_internal (f, name);
}
static void

View file

@ -6649,6 +6649,26 @@ android_term_init (void)
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;

View file

@ -90,6 +90,9 @@ struct android_display_info
/* Minimum font height over all fonts in font_table. */
int smallest_font_height;
/* Default name for all frames on this display. */
char *x_id_name;
/* The number of fonts opened for this display. */
int n_fonts;