From de25aaa11a8ef264c6f76841daa7e2a721c60937 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 14 Dec 2023 13:24:42 +0800 Subject: [PATCH] Respect Language & Input preferences under Android * doc/emacs/android.texi (Android Environment): * doc/emacs/cmdargs.texi (General Variables): Mention the manner in which the default language environment is selected on Android. * lisp/startup.el (normal-top-level): If android and initial-window-system, call android-locale-for-system-language for the default locale name. * lisp/term/android-win.el (android-locale-for-system-language): New function. * src/androidfns.c (syms_of_androidfns_for_pdumper): New function. (syms_of_androidfns) : New variable. Call syms_of_androidfns_for_pdumper both now and after loading the dump image. --- doc/emacs/android.texi | 24 +++-- doc/emacs/cmdargs.texi | 5 + lisp/startup.el | 18 +++- lisp/term/android-win.el | 55 +++++++++++ src/androidfns.c | 203 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+), 9 deletions(-) diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index fe73bc09d67..3cdeec6ba9e 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -409,15 +409,23 @@ Startup}) connect the Android system to another computer, and run: $ adb shell "settings put global settings_enable_monitor_phantom_procs false" @end example +@cindex system language settings, Android + The ``Languages & Input'' preferences which apply to the operating +system do not influence the C locale set for programs, but is taken +into account by Emacs during startup: a locale name is generated from +the selected language and regional variant and a language environment +(@pxref{Language Environment}) is selected on that basis, which does +not overwrite @code{LANG} or other locale-related environment +variables. The coding system for language environments set in this +fashion is @code{utf-8-unix} without exception. + @cindex C locale settings, Android - Emacs does not respect the locale configured for user applications -in the system, for the selection of locales available there does not -match that supplied by the C library. When Emacs starts on Android -5.0 or newer, the @code{LANG} environment variable is set to -@code{en_US.utf8}, which induces subprocesses linked against the -Android C library to print output sensibly. Earlier versions of -Android do not implement locales at all, on account of which the -variable is set to @code{C} instead. + Instead, the @code{LANG} environment variable (@pxref{General +Variables}) is set to @code{en_US.utf8} when Emacs starts on Android +5.0 or newer, which induces subprocesses linked against the Android C +library to print output sensibly. Earlier versions of Android do not +implement locales at all, on account of which the variable is set to +@code{C} instead. @cindex running emacs in the background, android @cindex emacs killed, android diff --git a/doc/emacs/cmdargs.texi b/doc/emacs/cmdargs.texi index 38e683bd7f5..7cdc29ea30b 100644 --- a/doc/emacs/cmdargs.texi +++ b/doc/emacs/cmdargs.texi @@ -640,6 +640,11 @@ set this in the ``Regional Settings'' Control Panel on some versions of MS-Windows, and in the ``Language and Region'' System Preference on macOS. +When running a GUI session on Android, @env{LANG} is set to a fixed +value, but the language and locale environment is derived from the +system's ``Languages & Input'' preferences. @xref{Android +Environment}. + The value of the @env{LC_CTYPE} category is matched against entries in @code{locale-language-names}, @code{locale-charset-language-names}, and diff --git a/lisp/startup.el b/lisp/startup.el index e40c316a8e8..09ec24c6c67 100644 --- a/lisp/startup.el +++ b/lisp/startup.el @@ -641,7 +641,23 @@ It is the default value of the variable `top-level'." (setq eol-mnemonic-dos "(DOS)" eol-mnemonic-mac "(Mac)"))) - (set-locale-environment nil) + (if (and (featurep 'android) + (eq system-type 'android) + initial-window-system) + ;; If Android windowing is enabled, derive a proper locale + ;; from the system's language preferences. On Android, LANG + ;; and LC_* must be set to one of the two locales the C + ;; library supports, but, by contrast with other systems, the + ;; C library locale does not reflect the configured system + ;; language. + ;; + ;; For this reason, the locale from which Emacs derives a + ;; default language environment is computed from such + ;; preferences, rather than environment variables that the C + ;; library refers to. + (set-locale-environment + (funcall 'android-locale-for-system-language)) + (set-locale-environment nil)) ;; Decode all default-directory's (probably, only *scratch* exists ;; at this point). default-directory of *scratch* is the basis ;; for many other file-name variables and directory lists, so it diff --git a/lisp/term/android-win.el b/lisp/term/android-win.el index 3e759a37a71..b2cc7b5d040 100644 --- a/lisp/term/android-win.el +++ b/lisp/term/android-win.el @@ -424,6 +424,61 @@ denied. ") 'follow-link t) (newline)))) + +;;; Locale preferences. + +(defvar android-os-language) + +(defun android-locale-for-system-language () + "Return a locale representing the system language. +This locale reflects the system's language preferences in its +language name and country variant fields, and always specifies +the UTF-8 coding system." + ;; android-os-language is a list comprising four elements LANGUAGE, + ;; COUNTRY, SCRIPT, and VARIANT. + ;; + ;; LANGUAGE and COUNTRY are ISO language and country codes identical + ;; to those stored within POSIX locales. + ;; + ;; SCRIPT is an ISO 15924 script tag, representing the script used + ;; if available, or if required to disambiguate between distinct + ;; writing systems for the same combination of language and country. + ;; + ;; VARIANT is an arbitrary string representing the variant of the + ;; LANGUAGE or SCRIPT represented. + ;; + ;; Each of these fields might be empty, but the locale is invalid if + ;; LANGUAGE is empty, which if true "en_US.UTF-8" is returned as a + ;; placeholder. + (let ((language (or (nth 0 android-os-language) "")) + (country (or (nth 1 android-os-language) "")) + (script (or (nth 2 android-os-language) "")) + (variant (or (nth 3 android-os-language) "")) + locale-base locale-modifier) + (if (string-empty-p language) + (setq locale-base "en_US.UTF-8") + (if (string-empty-p country) + (setq locale-base (concat language ".UTF-8")) + (setq locale-base (concat language "_" country + ".UTF-8")))) + ;; No straightforward relation between Java script and variant + ;; combinations exist: Java permits both a script and a variant to + ;; be supplied at once, whereas POSIX's closest analog "modifiers" + ;; permit only either an alternative script or a variant to be + ;; supplied. + ;; + ;; Emacs disregards variants besides "EURO" and scripts besides + ;; "Cyrl", for these two never coexist in existing locales, and + ;; their POSIX equivalents are the sole modifiers recognized by + ;; Emacs. + (if (string-equal script "Cyrl") + (setq locale-modifier "@cyrillic") + (if (string-equal variant "EURO") + (setq locale-modifier "@euro") + (setq locale-modifier ""))) + ;; Return the concatenation of both these values. + (concat locale-base locale-modifier))) + (provide 'android-win) ;; android-win.el ends here. diff --git a/src/androidfns.c b/src/androidfns.c index 31a4924e34d..60ace4fd453 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -27,6 +27,7 @@ along with GNU Emacs. If not, see . */ #include "keyboard.h" #include "buffer.h" #include "androidgui.h" +#include "pdumper.h" #ifndef ANDROID_STUBIFY @@ -3170,6 +3171,186 @@ android_set_preeditarea (struct window *w, int x, int y) +#ifndef ANDROID_STUBIFY + +static void +syms_of_androidfns_for_pdumper (void) +{ + jclass locale; + jmethodID method; + jobject object; + jstring string; + Lisp_Object language, country, script, variant; + const char *data; + + /* Find the Locale class. */ + + locale = (*android_java_env)->FindClass (android_java_env, + "java/util/Locale"); + if (!locale) + emacs_abort (); + + /* And the method from which the default locale can be + extracted. */ + + method = (*android_java_env)->GetStaticMethodID (android_java_env, + locale, + "getDefault", + "()Ljava/util/Locale;"); + if (!method) + emacs_abort (); + + /* Retrieve the default locale. */ + + object = (*android_java_env)->CallStaticObjectMethod (android_java_env, + locale, method); + android_exception_check_1 (locale); + + if (!object) + emacs_abort (); + + /* Retrieve its language field. Each of these methods is liable to + return the empty string, though if language is empty, the locale + is malformed. */ + + method = (*android_java_env)->GetMethodID (android_java_env, locale, + "getLanguage", + "()Ljava/lang/String;"); + if (!method) + emacs_abort (); + + string = (*android_java_env)->CallObjectMethod (android_java_env, object, + method); + android_exception_check_2 (object, locale); + + if (!string) + language = empty_unibyte_string; + else + { + data = (*android_java_env)->GetStringUTFChars (android_java_env, + string, NULL); + android_exception_check_3 (object, locale, string); + + if (!data) + language = empty_unibyte_string; + else + { + language = build_unibyte_string (data); + (*android_java_env)->ReleaseStringUTFChars (android_java_env, + string, data); + } + } + + /* Delete the reference to this string. */ + ANDROID_DELETE_LOCAL_REF (string); + + /* Proceed to retrieve the country code. */ + + method = (*android_java_env)->GetMethodID (android_java_env, locale, + "getCountry", + "()Ljava/lang/String;"); + if (!method) + emacs_abort (); + + string = (*android_java_env)->CallObjectMethod (android_java_env, object, + method); + android_exception_check_2 (object, locale); + + if (!string) + country = empty_unibyte_string; + else + { + data = (*android_java_env)->GetStringUTFChars (android_java_env, + string, NULL); + android_exception_check_3 (object, locale, string); + + if (!data) + country = empty_unibyte_string; + else + { + country = build_unibyte_string (data); + (*android_java_env)->ReleaseStringUTFChars (android_java_env, + string, data); + } + } + + ANDROID_DELETE_LOCAL_REF (string); + + /* Proceed to retrieve the script. */ + + method = (*android_java_env)->GetMethodID (android_java_env, locale, + "getScript", + "()Ljava/lang/String;"); + if (!method) + emacs_abort (); + + string = (*android_java_env)->CallObjectMethod (android_java_env, object, + method); + android_exception_check_2 (object, locale); + + if (!string) + script = empty_unibyte_string; + else + { + data = (*android_java_env)->GetStringUTFChars (android_java_env, + string, NULL); + android_exception_check_3 (object, locale, string); + + if (!data) + script = empty_unibyte_string; + else + { + script = build_unibyte_string (data); + (*android_java_env)->ReleaseStringUTFChars (android_java_env, + string, data); + } + } + + ANDROID_DELETE_LOCAL_REF (string); + + /* And variant. */ + + method = (*android_java_env)->GetMethodID (android_java_env, locale, + "getVariant", + "()Ljava/lang/String;"); + if (!method) + emacs_abort (); + + string = (*android_java_env)->CallObjectMethod (android_java_env, object, + method); + android_exception_check_2 (object, locale); + + if (!string) + variant = empty_unibyte_string; + else + { + data = (*android_java_env)->GetStringUTFChars (android_java_env, + string, NULL); + android_exception_check_3 (object, locale, string); + + if (!data) + variant = empty_unibyte_string; + else + { + variant = build_unibyte_string (data); + (*android_java_env)->ReleaseStringUTFChars (android_java_env, + string, data); + } + } + + /* Delete the reference to this string. */ + ANDROID_DELETE_LOCAL_REF (string); + + /* And other remaining local references. */ + ANDROID_DELETE_LOCAL_REF (object); + ANDROID_DELETE_LOCAL_REF (locale); + + /* Set Vandroid_os_language. */ + Vandroid_os_language = list4 (language, country, script, variant); +} + +#endif /* ANDROID_STUBIFY */ + void syms_of_androidfns (void) { @@ -3313,6 +3494,26 @@ element that is activated for a given number of milliseconds upon the bell being rung. */); android_keyboard_bell_duration = 50; + DEFVAR_LISP ("android-os-language", Vandroid_os_language, + doc: /* List representing the system language configured. +This list incorporates four elements LANGUAGE, COUNTRY, SCRIPT +and VARIANT, of which: + +LANGUAGE and COUNTRY are ISO language and country codes identical to +those stored within POSIX locales. + +SCRIPT is an ISO 15924 script tag, representing the script used +if available, or if required to disambiguate between distinct +writing systems for the same combination of language and country. + +VARIANT is an arbitrary string representing the variant of the +LANGUAGE or SCRIPT represented. + +Each of these fields might be empty or nil, but the locale is invalid +if LANGUAGE is empty. Users of this variable should consider the +language US English in this scenario. */); + Vandroid_os_language = Qnil; + /* Functions defined. */ defsubr (&Sx_create_frame); defsubr (&Sxw_color_defined_p); @@ -3363,5 +3564,7 @@ bell being rung. */); staticpro (&tip_dx); tip_dy = Qnil; staticpro (&tip_dy); + + pdumper_do_now_and_after_load (syms_of_androidfns_for_pdumper); #endif /* !ANDROID_STUBIFY */ } -- 2.39.2