1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-05 22:20:24 -08:00

Support `toolkit-theme-set-functions' on Android and execute hooks safely

* java/org/gnu/emacs/EmacsNative.java (setEmacsParams): New arg
UIMODE.
(sendConfigurationChanged): New args DETAIL and UI_MODE.

* java/org/gnu/emacs/EmacsNoninteractive.java (main1): Provide
an undefined UI mode.

* java/org/gnu/emacs/EmacsService.java (EmacsService): New field
uiMode.
(onCreate): Initialize this field at start-up and provide the
same to setEmacsParams.
(onConfigurationChanged): If the UI mode has been altered,
generate a configuration changed event to match.

* src/android.c (android_ui_mode): New variable.
(setEmacsParams): New argument UI_MODE.  Initialize the same
from this variable.

* src/androidgui.h (enum android_configuration_changed): New
enum.
(struct android_configuration_changed_event): New field
`DETAIL'.  Convert fields providing specifics into a union of
display density information and a UI mode integer.

* src/androidterm.c (handle_one_android_event): Handle both
manners of configuration change events.
(android_term_init): Initialize Vtoolkit_theme from UI mode
provided at start-up.

* src/frame.c (syms_of_frame): Always define Vtoolkit_theme.
Define Qtoolkit_theme_set_functions.

* src/gtkutil.c (xg_update_dark_mode_for_all_displays):

* src/w32term.c (w32_read_socket): Generate special toolkit
theme events, rather than executing hooks directly within the
read_socket callback.

* src/keyboard.c (kbd_buffer_get_event)
<TOOLKIT_THEME_CHANGED_EVENT>: Run Qtoolkit_theme_set_functions
and set Vtoolkit_theme from event->ie.arg.

* src/termhooks.h (enum event_kind): New event
TOOLKIT_THEME_CHANGED_EVENT.
This commit is contained in:
Po Lu 2025-11-24 10:16:21 +08:00
parent 435c3948a4
commit 7550791287
13 changed files with 132 additions and 52 deletions

View file

@ -3573,10 +3573,10 @@ automatically toggling between them.
--- ---
*** 'toolkit-theme-set-functions' called when the toolkit theme is set for Emacs. *** 'toolkit-theme-set-functions' called when the toolkit theme is set for Emacs.
When the theme is set on PGTK or MS-Windows builds, When the theme is set on PGTK, Android, or MS-Windows systems,
'toolkit-theme-set-functions' is called. The result is stored in the 'toolkit-theme-set-functions' is called. The result is stored in the
variable 'toolkit-theme' as either symbol 'dark' or 'light', but may be variable 'toolkit-theme' as either symbol 'dark' or 'light', but may be
expanded to future toolkit-specific symbols in the future. extended to encompass other toolkit-specific symbols in the future.
* Changes in Emacs 31.1 on Non-Free Operating Systems * Changes in Emacs 31.1 on Non-Free Operating Systems

View file

@ -61,6 +61,9 @@ public final class EmacsNative
scaledDensity is the DPI value used to translate point sizes to scaledDensity is the DPI value used to translate point sizes to
pixel sizes when loading fonts. pixel sizes when loading fonts.
uiMode holds the bits of the system's UI mode specification
defining the active system theme.
classPath must be the classpath of this app_process process, or classPath must be the classpath of this app_process process, or
NULL. NULL.
@ -74,6 +77,7 @@ public final class EmacsNative
float pixelDensityX, float pixelDensityX,
float pixelDensityY, float pixelDensityY,
float scaledDensity, float scaledDensity,
int uiMode,
String classPath, String classPath,
EmacsService emacsService, EmacsService emacsService,
int apiLevel); int apiLevel);
@ -197,8 +201,9 @@ public final class EmacsNative
public static native void sendNotificationAction (String tag, String action); public static native void sendNotificationAction (String tag, String action);
/* Send an ANDROID_CONFIGURATION_CHANGED event. */ /* Send an ANDROID_CONFIGURATION_CHANGED event. */
public static native void sendConfigurationChanged (float dpiX, float dpiY, public static native void sendConfigurationChanged (int detail, float dpiX,
float dpiScaled); float dpiY, float dpiScaled,
int ui_mode);
/* Return the file name associated with the specified file /* Return the file name associated with the specified file
descriptor, or NULL if there is none. */ descriptor, or NULL if there is none. */

View file

@ -67,7 +67,7 @@ public final class EmacsNoninteractive
cacheDir = context.getCacheDir ().getCanonicalPath (); cacheDir = context.getCacheDir ().getCanonicalPath ();
EmacsNative.setEmacsParams (assets, filesDir, EmacsNative.setEmacsParams (assets, filesDir,
libDir, cacheDir, 0.0f, libDir, cacheDir, 0.0f,
0.0f, 0.0f, null, null, 0.0f, 0.0f, 0x0, null, null,
Build.VERSION.SDK_INT); Build.VERSION.SDK_INT);
/* Now find the dump file that Emacs should use, if it has already /* Now find the dump file that Emacs should use, if it has already

View file

@ -150,6 +150,10 @@ public final class EmacsService extends Service
consulted for font scaling. */ consulted for font scaling. */
private double dpiX, dpiY, dpiScaled; private double dpiX, dpiY, dpiScaled;
/* The display's previously observed UI mode as it relates to the
system theme. */
private int uiMode;
static static
{ {
servicingQuery = new AtomicInteger (); servicingQuery = new AtomicInteger ();
@ -240,6 +244,7 @@ public final class EmacsService extends Service
float tempScaledDensity; float tempScaledDensity;
Resources resources; Resources resources;
DisplayMetrics metrics; DisplayMetrics metrics;
Configuration configuration;
super.onCreate (); super.onCreate ();
@ -254,6 +259,8 @@ public final class EmacsService extends Service
tempScaledDensity = ((getScaledDensity (metrics) tempScaledDensity = ((getScaledDensity (metrics)
/ metrics.density) / metrics.density)
* pixelDensityX); * pixelDensityX);
configuration = resources.getConfiguration ();
uiMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
resolver = getContentResolver (); resolver = getContentResolver ();
mainThread = Thread.currentThread (); mainThread = Thread.currentThread ();
@ -311,7 +318,8 @@ public final class EmacsService extends Service
EmacsNative.setEmacsParams (manager, filesDir, libDir, EmacsNative.setEmacsParams (manager, filesDir, libDir,
cacheDir, pixelDensityX, cacheDir, pixelDensityX,
pixelDensityY, scaledDensity, pixelDensityY, scaledDensity,
classPath, EmacsService.this, uiMode, classPath,
EmacsService.this,
Build.VERSION.SDK_INT); Build.VERSION.SDK_INT);
} }
}, extraStartupArguments); }, extraStartupArguments);
@ -375,8 +383,14 @@ public final class EmacsService extends Service
dpiX = pixelDensityX; dpiX = pixelDensityX;
dpiY = pixelDensityY; dpiY = pixelDensityY;
dpiScaled = scaledDensity; dpiScaled = scaledDensity;
EmacsNative.sendConfigurationChanged (pixelDensityX, pixelDensityY, EmacsNative.sendConfigurationChanged (0, pixelDensityX, pixelDensityY,
scaledDensity); scaledDensity, 0);
}
if ((newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != uiMode)
{
uiMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
EmacsNative.sendConfigurationChanged (1, 0.0f, 0.0f, 0.0f, uiMode);
} }
super.onConfigurationChanged (newConfig); super.onConfigurationChanged (newConfig);

View file

@ -163,6 +163,9 @@ double android_pixel_density_x, android_pixel_density_y;
font sizes. */ font sizes. */
double android_scaled_pixel_density; double android_scaled_pixel_density;
/* The display's current UI mode. */
int android_ui_mode;
/* The Android application data directory. */ /* The Android application data directory. */
static char *android_files_dir; static char *android_files_dir;
@ -1476,6 +1479,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
jfloat pixel_density_x, jfloat pixel_density_x,
jfloat pixel_density_y, jfloat pixel_density_y,
jfloat scaled_density, jfloat scaled_density,
jint ui_mode,
jobject class_path, jobject class_path,
jobject emacs_service_object, jobject emacs_service_object,
jint api_level) jint api_level)
@ -1501,6 +1505,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
android_pixel_density_x = pixel_density_x; android_pixel_density_x = pixel_density_x;
android_pixel_density_y = pixel_density_y; android_pixel_density_y = pixel_density_y;
android_scaled_pixel_density = scaled_density; android_scaled_pixel_density = scaled_density;
android_ui_mode = ui_mode;
__android_log_print (ANDROID_LOG_INFO, __func__, __android_log_print (ANDROID_LOG_INFO, __func__,
"Initializing "PACKAGE_STRING"...\nPlease report bugs to " "Initializing "PACKAGE_STRING"...\nPlease report bugs to "
@ -2821,8 +2826,9 @@ NATIVE_NAME (sendNotificationAction) (JNIEnv *env, jobject object,
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
NATIVE_NAME (sendConfigurationChanged) (JNIEnv *env, jobject object, NATIVE_NAME (sendConfigurationChanged) (JNIEnv *env, jobject object,
jfloat dpi_x, jfloat dpi_y, int detail, jfloat dpi_x,
jfloat dpi_scaled) jfloat dpi_y, jfloat dpi_scaled,
int ui_mode)
{ {
JNI_STACK_ALIGNMENT_PROLOGUE; JNI_STACK_ALIGNMENT_PROLOGUE;
@ -2831,9 +2837,24 @@ NATIVE_NAME (sendConfigurationChanged) (JNIEnv *env, jobject object,
event.config.type = ANDROID_CONFIGURATION_CHANGED; event.config.type = ANDROID_CONFIGURATION_CHANGED;
event.config.serial = ++event_serial; event.config.serial = ++event_serial;
event.config.window = ANDROID_NONE; event.config.window = ANDROID_NONE;
event.config.dpi_x = dpi_x; event.config.detail = detail;
event.config.dpi_y = dpi_y;
event.config.dpi_scaled = dpi_scaled; switch (detail)
{
case ANDROID_PIXEL_DENSITY_CHANGED:
event.config.u.pixel_density.dpi_x = dpi_x;
event.config.u.pixel_density.dpi_y = dpi_y;
event.config.u.pixel_density.dpi_scaled = dpi_scaled;
break;
case ANDROID_UI_MODE_CHANGED:
event.config.u.ui_mode = ui_mode;
break;
default:
emacs_abort ();
}
android_write_event (&event); android_write_event (&event);
return event_serial; return event_serial;
} }

View file

@ -102,6 +102,7 @@ extern ssize_t android_readlinkat (int, const char *restrict, char *restrict,
extern double android_pixel_density_x, android_pixel_density_y; extern double android_pixel_density_x, android_pixel_density_y;
extern double android_scaled_pixel_density; extern double android_scaled_pixel_density;
extern int android_ui_mode;
static_assert (sizeof (android_handle) == sizeof (jobject)); static_assert (sizeof (android_handle) == sizeof (jobject));
#define android_resolve_handle(handle) ((jobject) (handle)) #define android_resolve_handle(handle) ((jobject) (handle))

View file

@ -596,6 +596,17 @@ struct android_notification_event
size_t length; size_t length;
}; };
enum android_configuration_change_type
{
ANDROID_PIXEL_DENSITY_CHANGED,
ANDROID_UI_MODE_CHANGED,
};
#define UI_MODE_NIGHT_MASK 0x00000030
#define UI_MODE_NIGHT_NO 0x00000010
#define UI_MODE_NIGHT_YES 0x00000020
#define UI_MODE_NIGHT_UNDEFINED 0x00000000
struct android_configuration_changed_event struct android_configuration_changed_event
{ {
/* Type of the event. */ /* Type of the event. */
@ -607,13 +618,23 @@ struct android_configuration_changed_event
/* The window that gave rise to the event (None). */ /* The window that gave rise to the event (None). */
android_window window; android_window window;
/* What type of change this event represents. */
enum android_configuration_change_type detail;
union {
struct {
/* The density of the display along the horizontal and vertical /* The density of the display along the horizontal and vertical
axes. */ axes. */
double dpi_x, dpi_y; double dpi_x, dpi_y;
/* The density to take into account when converting between point and /* The density to take into account when converting between point
pixel dimensions. */ and pixel dimensions. */
double dpi_scaled; double dpi_scaled;
} pixel_density;
/* A change in the reported user interface UI mode. */
int ui_mode;
} u;
}; };
union android_event union android_event

View file

@ -1825,11 +1825,15 @@ handle_one_android_event (struct android_display_info *dpyinfo,
goto OTHER; goto OTHER;
case ANDROID_CONFIGURATION_CHANGED: case ANDROID_CONFIGURATION_CHANGED:
if (event->config.detail == ANDROID_PIXEL_DENSITY_CHANGED)
{
/* Update the display configuration from the event. */ /* Update the display configuration from the event. */
dpyinfo->resx = event->config.dpi_x; dpyinfo->resx = event->config.u.pixel_density.dpi_x;
dpyinfo->resy = event->config.dpi_y; dpyinfo->resy = event->config.u.pixel_density.dpi_y;
dpyinfo->font_resolution = event->config.dpi_scaled; dpyinfo->font_resolution
#ifdef notdef = event->config.u.pixel_density.dpi_scaled;
#if notdef
__android_log_print (ANDROID_LOG_VERBOSE, __func__, __android_log_print (ANDROID_LOG_VERBOSE, __func__,
"New display configuration: " "New display configuration: "
"resx = %.2f resy = %.2f font_resolution = %.2f", "resx = %.2f resy = %.2f font_resolution = %.2f",
@ -1838,6 +1842,15 @@ handle_one_android_event (struct android_display_info *dpyinfo,
inev.ie.kind = CONFIG_CHANGED_EVENT; inev.ie.kind = CONFIG_CHANGED_EVENT;
inev.ie.frame_or_window = XCAR (dpyinfo->name_list_element); inev.ie.frame_or_window = XCAR (dpyinfo->name_list_element);
inev.ie.arg = Qfont_render; inev.ie.arg = Qfont_render;
}
else if (event->config.detail == ANDROID_UI_MODE_CHANGED)
{
android_ui_mode = event->config.u.ui_mode;
inev.ie.kind = TOOLKIT_THEME_CHANGED_EVENT;
inev.ie.arg = (android_ui_mode == UI_MODE_NIGHT_YES
? Qdark : Qlight);
}
goto OTHER; goto OTHER;
default: default:
@ -6753,6 +6766,8 @@ android_term_init (void)
dpyinfo->resx = android_pixel_density_x; dpyinfo->resx = android_pixel_density_x;
dpyinfo->resy = android_pixel_density_y; dpyinfo->resy = android_pixel_density_y;
dpyinfo->font_resolution = android_scaled_pixel_density; dpyinfo->font_resolution = android_scaled_pixel_density;
Vtoolkit_theme = (android_ui_mode == UI_MODE_NIGHT_YES
? Qdark : Qlight);
#endif /* !ANDROID_STUBIFY */ #endif /* !ANDROID_STUBIFY */
/* https://lists.gnu.org/r/emacs-devel/2015-11/msg00194.html */ /* https://lists.gnu.org/r/emacs-devel/2015-11/msg00194.html */

View file

@ -7619,16 +7619,15 @@ The default is \\+`inhibit' in NS builds and nil everywhere else. */);
#endif /* !HAVE_EXT_TOOL_BAR || USE_GTK */ #endif /* !HAVE_EXT_TOOL_BAR || USE_GTK */
#endif /* HAVE_WINDOW_SYSTEM */ #endif /* HAVE_WINDOW_SYSTEM */
#if (defined(HAVE_PGTK) && defined(HAVE_GSETTINGS)) || defined (WINDOWSNT)
DEFVAR_LISP ("toolkit-theme", Vtoolkit_theme, DEFVAR_LISP ("toolkit-theme", Vtoolkit_theme,
doc: /* The current toolkit theme. doc: /* The current toolkit theme.
Either the symbol `light' or the symbol `dark', reflecting the system's Either the symbol `light' or the symbol `dark', reflecting the system's
current theme preference. This variable is updated automatically when current theme preference. This variable is updated automatically when
the system theme changes. the system theme changes.
This variable is only available on PGTK and MS-Windows builds. */); This variable is only set on PGTK, Android, and MS-Windows builds. */);
Vtoolkit_theme = Qnil; Vtoolkit_theme = Qnil;
DEFSYM (Qlight, "light"); DEFSYM (Qlight, "light");
DEFSYM (Qdark, "dark"); DEFSYM (Qdark, "dark");
#endif DEFSYM (Qtoolkit_theme_set_functions, "toolkit-theme-set-functions");
} }

View file

@ -1476,14 +1476,12 @@ xg_update_dark_mode_for_all_displays (bool dark_mode_p)
xg_set_gtk_theme_dark_mode (dark_mode_p, settings); xg_set_gtk_theme_dark_mode (dark_mode_p, settings);
} }
Vtoolkit_theme = dark_mode_p ? Qdark : Qlight;
Lisp_Object hook = intern ("toolkit-theme-set-functions");
if (!NILP (Fboundp (hook)))
{ {
Lisp_Object args[2]; struct input_event inev;
args[0] = hook; EVENT_INIT (inev);
args[1] = Vtoolkit_theme; inev.kind = TOOLKIT_THEME_CHANGED_EVENT;
Frun_hook_with_args (2, args); inev.arg = msg.msg.wParam ? Qdark : Qlight;
kbd_buffer_store_event (&inev);
} }
} }

View file

@ -4242,6 +4242,15 @@ kbd_buffer_get_event (KBOARD **kbp,
} }
#endif /* HAVE_ANDROID */ #endif /* HAVE_ANDROID */
case TOOLKIT_THEME_CHANGED_EVENT:
kbd_fetch_ptr = next_kbd_event (event);
input_pending = readable_events (0);
Vtoolkit_theme = event->ie.arg;
CALLN (Frun_hook_with_args, Qtoolkit_theme_set_functions,
event->ie.arg);
break;
#ifdef HAVE_EXT_MENU_BAR #ifdef HAVE_EXT_MENU_BAR
case MENU_BAR_ACTIVATE_EVENT: case MENU_BAR_ACTIVATE_EVENT:
{ {

View file

@ -341,6 +341,10 @@ enum event_kind
which the monitors changed. */ which the monitors changed. */
, MONITORS_CHANGED_EVENT , MONITORS_CHANGED_EVENT
/* In a TOOLKIT_THEME_CHANGED_EVENT, .arg is the value to which the
toolkit theme was altered. */
, TOOLKIT_THEME_CHANGED_EVENT
#ifdef HAVE_HAIKU #ifdef HAVE_HAIKU
/* In a NOTIFICATION_CLICKED_EVENT, .arg is an integer identifying /* In a NOTIFICATION_CLICKED_EVENT, .arg is an integer identifying
the notification that was clicked. */ the notification that was clicked. */

View file

@ -6085,15 +6085,8 @@ w32_read_socket (struct terminal *terminal,
case WM_EMACS_SET_TOOLKIT_THEME: case WM_EMACS_SET_TOOLKIT_THEME:
{ {
Vtoolkit_theme = msg.msg.wParam ? Qdark : Qlight; inev.kind = TOOLKIT_THEME_CHANGED_EVENT;
Lisp_Object hook = intern ("toolkit-theme-set-functions"); inev.arg = msg.msg.wParam ? Qdark : Qlight;
if (!NILP (Fboundp (hook)))
{
Lisp_Object args[2];
args[0] = hook;
args[1] = Vtoolkit_theme;
Frun_hook_with_args (2, args);
}
} }
break; break;