/* Functions for handling font and other changes dynamically. Copyright (C) 2009-2026 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 . */ #include #include #include #include #include #include "lisp.h" #ifndef HAVE_PGTK #include "xterm.h" #else #include "gtkutil.h" #endif #include "xsettings.h" #include "frame.h" #include "keyboard.h" #include "blockinput.h" #include "termhooks.h" #include "pdumper.h" #ifndef HAVE_PGTK #include #else typedef unsigned short CARD16; typedef unsigned int CARD32; #endif #ifdef HAVE_GSETTINGS #include #include #endif #ifdef HAVE_GCONF #include #endif #ifdef USE_CAIRO #include #include "ftfont.h" #elif defined HAVE_XFT #include #endif #if defined USE_CAIRO && defined CAIRO_HAS_FT_FONT #include #endif static char *current_mono_font; static char *current_font; static Display_Info *first_dpyinfo; static Lisp_Object current_tool_bar_style; #if defined HAVE_GSETTINGS || defined HAVE_GCONF || !defined HAVE_PGTK /* Store a config changed event in to the event queue. */ static void store_config_changed_event (Lisp_Object arg, Lisp_Object display_name) { struct input_event event; EVENT_INIT (event); event.kind = CONFIG_CHANGED_EVENT; event.frame_or_window = display_name; event.arg = arg; kbd_buffer_store_event (&event); } /* Return true if DPYINFO is still valid. */ static bool dpyinfo_valid (Display_Info *dpyinfo) { bool found = false; if (dpyinfo != NULL) { Display_Info *d; for (d = x_display_list; !found && d; d = d->next) #ifndef HAVE_PGTK found = d == dpyinfo && d->display == dpyinfo->display; #else found = d == dpyinfo && d->gdpy == dpyinfo->gdpy; #endif } return found; } #endif /* Store a monospace font change event if the monospaced font changed. */ #if (defined USE_CAIRO || defined HAVE_XFT) && (defined HAVE_GSETTINGS || defined HAVE_GCONF) static void store_monospaced_changed (const char *newfont) { if (current_mono_font != NULL && strcmp (newfont, current_mono_font) == 0) return; /* No change. */ dupstring (¤t_mono_font, newfont); if (dpyinfo_valid (first_dpyinfo) && use_system_font) { store_config_changed_event (Qmonospace_font_name, XCAR (first_dpyinfo->name_list_element)); } } #endif /* Store a font name change event if the font name changed. */ #if (defined USE_CAIRO || defined HAVE_XFT) && (defined HAVE_GSETTINGS || defined HAVE_GCONF || !defined HAVE_PGTK) static void store_font_name_changed (const char *newfont) { if (current_font != NULL && strcmp (newfont, current_font) == 0) return; /* No change. */ dupstring (¤t_font, newfont); if (dpyinfo_valid (first_dpyinfo)) { store_config_changed_event (Qfont_name, XCAR (first_dpyinfo->name_list_element)); } } #endif /* USE_CAIRO || HAVE_XFT */ #if defined HAVE_GSETTINGS || defined HAVE_GCONF || !defined HAVE_PGTK /* Map TOOL_BAR_STYLE from a string to its corresponding Lisp value. Return Qnil if TOOL_BAR_STYLE is not known. */ static Lisp_Object map_tool_bar_style (const char *tool_bar_style) { Lisp_Object style = Qnil; if (tool_bar_style) { if (strcmp (tool_bar_style, "both") == 0) style = Qboth; else if (strcmp (tool_bar_style, "both-horiz") == 0) style = Qboth_horiz; else if (strcmp (tool_bar_style, "icons") == 0) style = Qimage; else if (strcmp (tool_bar_style, "text") == 0) style = Qtext; } return style; } #endif #if defined HAVE_GSETTINGS || defined HAVE_GCONF || !defined HAVE_PGTK /* Store a tool bar style change event if the tool bar style changed. */ static void store_tool_bar_style_changed (const char *newstyle, Display_Info *dpyinfo) { Lisp_Object style = map_tool_bar_style (newstyle); if (EQ (current_tool_bar_style, style)) return; /* No change. */ current_tool_bar_style = style; if (dpyinfo_valid (dpyinfo)) store_config_changed_event (Qtool_bar_style, XCAR (dpyinfo->name_list_element)); } #endif #ifndef HAVE_PGTK #if defined USE_CAIRO || defined HAVE_XFT #define XSETTINGS_FONT_NAME "Gtk/FontName" #define XSETTINGS_GDK_DPI_NAME "Gdk/UnscaledDPI" #define XSETTINGS_GDK_WSCALE_NAME "Gdk/WindowScalingFactor" #endif #define XSETTINGS_TOOL_BAR_STYLE "Gtk/ToolbarStyle" #endif enum { SEEN_AA = 0x01, SEEN_HINTING = 0x02, SEEN_RGBA = 0x04, SEEN_LCDFILTER = 0x08, SEEN_HINTSTYLE = 0x10, SEEN_DPI = 0x20, SEEN_FONT = 0x40, SEEN_TB_STYLE = 0x80 }; struct xsettings { #if defined USE_CAIRO || defined HAVE_XFT FcBool aa, hinting; int rgba, lcdfilter, hintstyle; double dpi; char *font; #endif char *tb_style; unsigned seen; }; #ifdef HAVE_PGTK /* The cairo font_options as obtained using gsettings. */ static cairo_font_options_t *font_options; #endif #ifdef HAVE_GSETTINGS #define GSETTINGS_SCHEMA "org.gnome.desktop.interface" #define GSETTINGS_TOOL_BAR_STYLE "toolbar-style" #if defined USE_CAIRO || defined HAVE_XFT #define GSETTINGS_MONO_FONT "monospace-font-name" #define GSETTINGS_FONT_NAME "font-name" #endif #ifdef HAVE_PGTK #define GSETTINGS_FONT_ANTIALIASING "font-antialiasing" #define GSETTINGS_FONT_RGBA_ORDER "font-rgba-order" #define GSETTINGS_FONT_HINTING "font-hinting" #define GSETTINGS_COLOR_SCHEME "color-scheme" #endif /* The single GSettings instance, or NULL if not connected to GSettings. */ static GSettings *gsettings_client; #ifdef HAVE_PGTK static bool xg_settings_key_valid_p (GSettings *settings, const char *key) { #ifdef GLIB_VERSION_2_32 GSettingsSchema *schema; bool rc; g_object_get (G_OBJECT (settings), "settings-schema", &schema, NULL); if (!schema) return false; rc = g_settings_schema_has_key (schema, key); g_settings_schema_unref (schema); return rc; #else return false; #endif } #endif /* HAVE_PGTK */ #ifdef HAVE_PGTK /* Store an event for re-rendering of the fonts. */ static void store_font_options_changed (void) { if (dpyinfo_valid (first_dpyinfo)) store_config_changed_event (Qfont_render, XCAR (first_dpyinfo->name_list_element)); } /* Apply changes in the hinting system setting. */ static void apply_gsettings_font_hinting (GSettings *settings) { GVariant *val; const char *hinting; if (!xg_settings_key_valid_p (settings, GSETTINGS_FONT_HINTING)) return; val = g_settings_get_value (settings, GSETTINGS_FONT_HINTING); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) { hinting = g_variant_get_string (val, NULL); if (!strcmp (hinting, "full")) cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_FULL); else if (!strcmp (hinting, "medium")) cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_MEDIUM); else if (!strcmp (hinting, "slight")) cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_SLIGHT); else if (!strcmp (hinting, "none")) cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE); } g_variant_unref (val); } } /* Apply changes in the antialiasing system setting. */ static void apply_gsettings_font_antialias (GSettings *settings) { GVariant *val; const char *antialias; if (!xg_settings_key_valid_p (settings, GSETTINGS_FONT_ANTIALIASING)) return; val = g_settings_get_value (settings, GSETTINGS_FONT_ANTIALIASING); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) { antialias = g_variant_get_string (val, NULL); if (!strcmp (antialias, "none")) cairo_font_options_set_antialias (font_options, CAIRO_ANTIALIAS_NONE); else if (!strcmp (antialias, "grayscale")) cairo_font_options_set_antialias (font_options, CAIRO_ANTIALIAS_GRAY); else if (!strcmp (antialias, "rgba")) cairo_font_options_set_antialias (font_options, CAIRO_ANTIALIAS_SUBPIXEL); } g_variant_unref (val); } } /* Apply the settings for the rgb element ordering. */ static void apply_gsettings_font_rgba_order (GSettings *settings) { GVariant *val; const char *rgba_order; if (!xg_settings_key_valid_p (settings, GSETTINGS_FONT_RGBA_ORDER)) return; val = g_settings_get_value (settings, GSETTINGS_FONT_RGBA_ORDER); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) { rgba_order = g_variant_get_string (val, NULL); if (!strcmp (rgba_order, "rgb")) cairo_font_options_set_subpixel_order (font_options, CAIRO_SUBPIXEL_ORDER_RGB); else if (!strcmp (rgba_order, "bgr")) cairo_font_options_set_subpixel_order (font_options, CAIRO_SUBPIXEL_ORDER_BGR); else if (!strcmp (rgba_order, "vrgb")) cairo_font_options_set_subpixel_order (font_options, CAIRO_SUBPIXEL_ORDER_VRGB); else if (!strcmp (rgba_order, "vbgr")) cairo_font_options_set_subpixel_order (font_options, CAIRO_SUBPIXEL_ORDER_VBGR); } g_variant_unref (val); } } #endif /* HAVE_PGTK */ /* Callback called when something changed in GSettings. */ static void something_changed_gsettingsCB (GSettings *settings, gchar *key, gpointer user_data) { GVariant *val; if (strcmp (key, GSETTINGS_TOOL_BAR_STYLE) == 0) { val = g_settings_get_value (settings, GSETTINGS_TOOL_BAR_STYLE); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) { const gchar *newstyle = g_variant_get_string (val, NULL); store_tool_bar_style_changed (newstyle, first_dpyinfo); } g_variant_unref (val); } } #if defined USE_CAIRO || defined HAVE_XFT else if (strcmp (key, GSETTINGS_MONO_FONT) == 0) { val = g_settings_get_value (settings, GSETTINGS_MONO_FONT); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) { const gchar *newfont = g_variant_get_string (val, NULL); store_monospaced_changed (newfont); } g_variant_unref (val); } } else if (strcmp (key, GSETTINGS_FONT_NAME) == 0) { val = g_settings_get_value (settings, GSETTINGS_FONT_NAME); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) { const gchar *newfont = g_variant_get_string (val, NULL); store_font_name_changed (newfont); } g_variant_unref (val); } } #endif /* USE_CAIRO || HAVE_XFT */ #ifdef HAVE_PGTK else if (!strcmp (key, GSETTINGS_FONT_ANTIALIASING)) { apply_gsettings_font_antialias (settings); store_font_options_changed (); } else if (!strcmp (key, GSETTINGS_FONT_HINTING)) { apply_gsettings_font_hinting (settings); store_font_options_changed (); } else if (!strcmp (key, GSETTINGS_FONT_RGBA_ORDER)) { apply_gsettings_font_rgba_order (settings); store_font_options_changed (); } else if (!strcmp (key, GSETTINGS_COLOR_SCHEME)) { if (xg_settings_key_valid_p (settings, GSETTINGS_COLOR_SCHEME)) { val = g_settings_get_value (settings, GSETTINGS_COLOR_SCHEME); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) { const char *color_scheme = g_variant_get_string (val, NULL); /* Check dark mode preference and update all frames. */ bool dark_mode_p = (strstr (color_scheme, "dark") != NULL); xg_update_dark_mode_for_all_displays (dark_mode_p); } g_variant_unref (val); } } } #endif /* HAVE_PGTK */ } #endif /* HAVE_GSETTINGS */ #ifdef HAVE_GCONF #define GCONF_TOOL_BAR_STYLE "/desktop/gnome/interface/toolbar_style" #if defined USE_CAIRO || defined HAVE_XFT #define GCONF_MONO_FONT "/desktop/gnome/interface/monospace_font_name" #define GCONF_FONT_NAME "/desktop/gnome/interface/font_name" #endif /* The single GConf instance, or NULL if not connected to GConf. */ static GConfClient *gconf_client; /* Callback called when something changed in GConf that we care about. */ static void something_changed_gconfCB (GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data) { GConfValue *v = gconf_entry_get_value (entry); const char *key = gconf_entry_get_key (entry); if (!v || v->type != GCONF_VALUE_STRING || ! key) return; if (strcmp (key, GCONF_TOOL_BAR_STYLE) == 0) { const char *value = gconf_value_get_string (v); store_tool_bar_style_changed (value, first_dpyinfo); } #if defined USE_CAIRO || defined HAVE_XFT else if (strcmp (key, GCONF_MONO_FONT) == 0) { const char *value = gconf_value_get_string (v); store_monospaced_changed (value); } else if (strcmp (key, GCONF_FONT_NAME) == 0) { const char *value = gconf_value_get_string (v); store_font_name_changed (value); } #endif /* USE_CAIRO || HAVE_XFT */ } #endif /* HAVE_GCONF */ #if defined USE_CAIRO || defined HAVE_XFT /* Older fontconfig versions don't have FC_LCD_*. */ #ifndef FC_LCD_NONE #define FC_LCD_NONE 0 #endif #ifndef FC_LCD_DEFAULT #define FC_LCD_DEFAULT 1 #endif #ifndef FC_LCD_FILTER #define FC_LCD_FILTER "lcdfilter" #endif #endif /* USE_CAIRO || HAVE_XFT */ #ifndef HAVE_PGTK /* Find the window that contains the XSETTINGS property values. */ static void get_prop_window (Display_Info *dpyinfo) { Display *dpy = dpyinfo->display; XGrabServer (dpy); dpyinfo->xsettings_window = XGetSelectionOwner (dpy, dpyinfo->Xatom_xsettings_sel); if (dpyinfo->xsettings_window != None) /* Select events so we can detect if window is deleted or if settings are changed. */ XSelectInput (dpy, dpyinfo->xsettings_window, PropertyChangeMask|StructureNotifyMask); XUngrabServer (dpy); } #endif #ifndef HAVE_PGTK #define PAD(nr) (((nr) + 3) & ~3) /* Parse xsettings and extract those that deal with Xft. See https://freedesktop.org/wiki/Specifications/XSettingsRegistry/ and https://specifications.freedesktop.org/xsettings-spec/xsettings-spec-0.5.html. Layout of prop. First is a header: bytes type what ------------------------------------ 1 CARD8 byte-order 3 unused 4 CARD32 SERIAL 4 CARD32 N_SETTINGS Then N_SETTINGS records, with header: bytes type what ------------------------------------ 1 SETTING_TYPE type (0 = integer, 1 = string, 2 RGB color). 1 unused 2 CARD16 n == name-length n STRING8 name p unused, p=pad_to_even_4(n) 4 CARD32 last-change-serial and then the value, For string: bytes type what ------------------------------------ 4 CARD32 n = value-length n STRING8 value p unused, p=pad_to_even_4(n) For integer: bytes type what ------------------------------------ 4 INT32 value For RGB color: bytes type what ------------------------------------ 2 CARD16 red 2 CARD16 blue 2 CARD16 green 2 CARD16 alpha Returns non-zero if some Xft settings was seen, zero otherwise. */ static int parse_settings (unsigned char *prop, unsigned long bytes, struct xsettings *settings) { int int1 = 1; int my_bo = *(char *) &int1 == 1 ? LSBFirst : MSBFirst; int that_bo = prop[0]; CARD32 n_settings; int bytes_parsed = 0; int settings_seen = 0; int i = 0; #if defined USE_CAIRO || defined HAVE_XFT /* Some X environments, e.g. XWayland, communicate DPI changes only through the GDK xsettings values and not the regular Xft one, so recognize both schemes. We want to see both the GDK window scaling factor and the post-scaling DPI so we can compute our desired actual DPI. */ int gdk_unscaled_dpi = 0; int gdk_window_scale = 0; #endif /* First 4 bytes is a serial number, skip that. */ if (bytes < 12) return settings_seen; memcpy (&n_settings, prop+8, 4); if (my_bo != that_bo) n_settings = bswap_32 (n_settings); bytes_parsed = 12; memset (settings, 0, sizeof (*settings)); while (bytes_parsed+4 < bytes && settings_seen < 7 && i < n_settings) { int type = prop[bytes_parsed++]; CARD16 nlen; CARD32 vlen, ival = 0; char name[128]; /* The names we are looking for are not this long. */ char sval[128]; /* The values we are looking for are not this long. */ bool want_this; int to_cpy; sval[0] = '\0'; ++i; ++bytes_parsed; /* Padding */ memcpy (&nlen, prop+bytes_parsed, 2); bytes_parsed += 2; if (my_bo != that_bo) nlen = bswap_16 (nlen); if (bytes_parsed + nlen > bytes) return settings_seen; to_cpy = min (nlen, sizeof name - 1); memcpy (name, prop+bytes_parsed, to_cpy); name[to_cpy] = '\0'; bytes_parsed += nlen; bytes_parsed = PAD (bytes_parsed); bytes_parsed += 4; /* Skip serial for this value */ if (bytes_parsed > bytes) return settings_seen; want_this = strcmp (XSETTINGS_TOOL_BAR_STYLE, name) == 0; #if defined USE_CAIRO || defined HAVE_XFT if ((nlen > 6 && memcmp (name, "Xft/", 4) == 0) || strcmp (XSETTINGS_FONT_NAME, name) == 0 || strcmp (XSETTINGS_GDK_DPI_NAME, name) == 0 || strcmp (XSETTINGS_GDK_WSCALE_NAME, name) == 0) want_this = true; #endif switch (type) { case 0: /* Integer */ if (bytes_parsed + 4 > bytes) return settings_seen; if (want_this) { memcpy (&ival, prop+bytes_parsed, 4); if (my_bo != that_bo) ival = bswap_32 (ival); } bytes_parsed += 4; break; case 1: /* String */ if (bytes_parsed + 4 > bytes) return settings_seen; memcpy (&vlen, prop+bytes_parsed, 4); bytes_parsed += 4; if (my_bo != that_bo) vlen = bswap_32 (vlen); if (want_this) { to_cpy = min (vlen, sizeof sval - 1); memcpy (sval, prop+bytes_parsed, to_cpy); sval[to_cpy] = '\0'; } bytes_parsed += vlen; bytes_parsed = PAD (bytes_parsed); break; case 2: /* RGB value */ /* No need to parse this */ if (bytes_parsed + 8 > bytes) return settings_seen; bytes_parsed += 8; /* 4 values (r, b, g, alpha), 2 bytes each. */ break; default: /* Parse Error */ return settings_seen; } if (want_this) { if (strcmp (name, XSETTINGS_TOOL_BAR_STYLE) == 0) { dupstring (&settings->tb_style, sval); settings->seen |= SEEN_TB_STYLE; } #if defined USE_CAIRO || defined HAVE_XFT else if (strcmp (name, XSETTINGS_FONT_NAME) == 0) { dupstring (&settings->font, sval); settings->seen |= SEEN_FONT; } else if (strcmp (name, "Xft/Antialias") == 0) { settings->seen |= SEEN_AA; settings->aa = ival != 0; } else if (strcmp (name, "Xft/Hinting") == 0) { settings->seen |= SEEN_HINTING; settings->hinting = ival != 0; } # ifdef FC_HINT_STYLE else if (strcmp (name, "Xft/HintStyle") == 0) { settings->seen |= SEEN_HINTSTYLE; if (strcmp (sval, "hintnone") == 0) settings->hintstyle = FC_HINT_NONE; else if (strcmp (sval, "hintslight") == 0) settings->hintstyle = FC_HINT_SLIGHT; else if (strcmp (sval, "hintmedium") == 0) settings->hintstyle = FC_HINT_MEDIUM; else if (strcmp (sval, "hintfull") == 0) settings->hintstyle = FC_HINT_FULL; else settings->seen &= ~SEEN_HINTSTYLE; } # endif else if (strcmp (name, "Xft/RGBA") == 0) { settings->seen |= SEEN_RGBA; if (strcmp (sval, "none") == 0) settings->rgba = FC_RGBA_NONE; else if (strcmp (sval, "rgb") == 0) settings->rgba = FC_RGBA_RGB; else if (strcmp (sval, "bgr") == 0) settings->rgba = FC_RGBA_BGR; else if (strcmp (sval, "vrgb") == 0) settings->rgba = FC_RGBA_VRGB; else if (strcmp (sval, "vbgr") == 0) settings->rgba = FC_RGBA_VBGR; else settings->seen &= ~SEEN_RGBA; } else if (strcmp (name, "Xft/DPI") == 0 && ival != (CARD32) -1) { settings->seen |= SEEN_DPI; settings->dpi = ival / 1024.0; } else if (strcmp (name, XSETTINGS_GDK_DPI_NAME) == 0) gdk_unscaled_dpi = ival; else if (strcmp (name, XSETTINGS_GDK_WSCALE_NAME) == 0) gdk_window_scale = ival; else if (strcmp (name, "Xft/lcdfilter") == 0) { settings->seen |= SEEN_LCDFILTER; if (strcmp (sval, "none") == 0) settings->lcdfilter = FC_LCD_NONE; else if (strcmp (sval, "lcddefault") == 0) settings->lcdfilter = FC_LCD_DEFAULT; else settings->seen &= ~SEEN_LCDFILTER; } #endif /* USE_CAIRO || HAVE_XFT */ else want_this = false; settings_seen += want_this; } } #if defined USE_CAIRO || defined HAVE_XFT if (gdk_unscaled_dpi > 0 && gdk_window_scale > 0) { /* Override any previous DPI settings. GDK ones are intended to be authoritative. See https://mail.gnome.org/archives/commits-list/2013-June/msg06726.html */ settings->seen |= SEEN_DPI; settings->dpi = gdk_window_scale * gdk_unscaled_dpi / 1024.0; } #endif return settings_seen; } #endif #ifndef HAVE_PGTK /* Read settings from the XSettings property window on display for DPYINFO. Store settings read in SETTINGS. Return true if successful. */ static bool read_settings (Display_Info *dpyinfo, struct xsettings *settings) { Atom act_type; int act_form; unsigned long nitems, bytes_after; unsigned char *prop = NULL; Display *dpy = dpyinfo->display; int rc; bool got_settings = false; x_catch_errors (dpy); rc = XGetWindowProperty (dpy, dpyinfo->xsettings_window, dpyinfo->Xatom_xsettings_prop, 0, LONG_MAX, False, AnyPropertyType, &act_type, &act_form, &nitems, &bytes_after, &prop); if (rc == Success && prop != NULL && act_form == 8 && nitems > 0 && act_type == dpyinfo->Xatom_xsettings_prop) got_settings = parse_settings (prop, nitems, settings) != 0; XFree (prop); x_uncatch_errors (); return got_settings; } #endif #ifndef HAVE_PGTK /* Apply Xft settings in SETTINGS to the Xft library. Store a Lisp event that Xft settings changed. */ static void apply_xft_settings (Display_Info *dpyinfo, struct xsettings *settings) { #if defined HAVE_XFT \ || (defined USE_CAIRO && defined CAIRO_HAS_FC_FONT \ && defined CAIRO_HAS_FT_FONT) FcPattern *pat; struct xsettings oldsettings; bool changed = false; #ifndef HAVE_XFT cairo_font_options_t *options; #endif memset (&oldsettings, 0, sizeof (oldsettings)); pat = FcPatternCreate (); #ifdef HAVE_XFT XftDefaultSubstitute (dpyinfo->display, XScreenNumberOfScreen (dpyinfo->screen), pat); #else FcConfigSubstitute (NULL, pat, FcMatchPattern); options = cairo_font_options_create (); ftcrfont_get_default_font_options (dpyinfo, options); cairo_ft_font_options_substitute (options, pat); cairo_font_options_destroy (options); FcDefaultSubstitute (pat); #endif FcPatternGetBool (pat, FC_ANTIALIAS, 0, &oldsettings.aa); FcPatternGetBool (pat, FC_HINTING, 0, &oldsettings.hinting); #ifdef FC_HINT_STYLE FcPatternGetInteger (pat, FC_HINT_STYLE, 0, &oldsettings.hintstyle); #endif FcPatternGetInteger (pat, FC_LCD_FILTER, 0, &oldsettings.lcdfilter); FcPatternGetInteger (pat, FC_RGBA, 0, &oldsettings.rgba); FcPatternGetDouble (pat, FC_DPI, 0, &oldsettings.dpi); if ((settings->seen & SEEN_AA) != 0 && oldsettings.aa != settings->aa) { FcPatternDel (pat, FC_ANTIALIAS); FcPatternAddBool (pat, FC_ANTIALIAS, settings->aa); changed = true; oldsettings.aa = settings->aa; } if ((settings->seen & SEEN_HINTING) != 0 && oldsettings.hinting != settings->hinting) { FcPatternDel (pat, FC_HINTING); FcPatternAddBool (pat, FC_HINTING, settings->hinting); changed = true; oldsettings.hinting = settings->hinting; } if ((settings->seen & SEEN_RGBA) != 0 && oldsettings.rgba != settings->rgba) { FcPatternDel (pat, FC_RGBA); FcPatternAddInteger (pat, FC_RGBA, settings->rgba); oldsettings.rgba = settings->rgba; changed = true; } /* Older fontconfig versions don't have FC_LCD_FILTER. */ if ((settings->seen & SEEN_LCDFILTER) != 0 && oldsettings.lcdfilter != settings->lcdfilter) { FcPatternDel (pat, FC_LCD_FILTER); FcPatternAddInteger (pat, FC_LCD_FILTER, settings->lcdfilter); changed = true; oldsettings.lcdfilter = settings->lcdfilter; } #ifdef FC_HINT_STYLE if ((settings->seen & SEEN_HINTSTYLE) != 0 && oldsettings.hintstyle != settings->hintstyle) { FcPatternDel (pat, FC_HINT_STYLE); FcPatternAddInteger (pat, FC_HINT_STYLE, settings->hintstyle); changed = true; oldsettings.hintstyle = settings->hintstyle; } #endif #ifdef USE_CAIRO /* When Cairo is being used, set oldsettings.dpi to dpyinfo->resx. This is a gross hack, but seeing as Cairo fails to report anything reasonable, just use it to avoid config-changed events being sent at startup. */ oldsettings.dpi = dpyinfo->resx; #endif if ((settings->seen & SEEN_DPI) != 0 && settings->dpi > 0 /* The following conjunct avoids setting `changed' to true when old and new dpi settings do not differ "substantially". Otherwise, the dynamic-setting Elisp code may process all sorts of unrelated settings that override users' font customizations, among others. Compare: https://lists.gnu.org/r/emacs-devel/2016-05/msg00557.html https://lists.gnu.org/r/bug-gnu-emacs/2016-12/msg00820.html As soon as the dynamic-settings code has been tested and verified, this Emacs 25.2 workaround should be removed. */ && ((oldsettings.dpi >= settings->dpi && (oldsettings.dpi - settings->dpi) > 2) || ((settings->dpi > oldsettings.dpi) && (settings->dpi - oldsettings.dpi) > 2))) { FcPatternDel (pat, FC_DPI); FcPatternAddDouble (pat, FC_DPI, settings->dpi); changed = true; oldsettings.dpi = settings->dpi; /* Changing the DPI on this display affects all frames on it. Check FRAME_RES_X and FRAME_RES_Y in frame.h to see how. */ dpyinfo->resy = dpyinfo->resx = settings->dpi; } if (changed) { static char const format[] = "Antialias: %d, Hinting: %d, RGBA: %d, LCDFilter: %d, " "Hintstyle: %d, DPI: %f"; #ifdef HAVE_XFT XftDefaultSet (dpyinfo->display, pat); #else FcPatternDestroy (pat); #endif store_config_changed_event (Qfont_render, XCAR (dpyinfo->name_list_element)); Vxft_settings = make_formatted_string (format, oldsettings.aa, oldsettings.hinting, oldsettings.rgba, oldsettings.lcdfilter, oldsettings.hintstyle, oldsettings.dpi); } else FcPatternDestroy (pat); #endif /* HAVE_XFT || (USE_CAIRO && CAIRO_HAS_FC_FONT && CAIRO_HAS_FT_FONT) */ } #endif #ifndef HAVE_PGTK /* Read XSettings from the display for DPYINFO. If SEND_EVENT_P store a Lisp event settings that changed. */ static void read_and_apply_settings (Display_Info *dpyinfo, bool send_event_p) { struct xsettings settings; if (!read_settings (dpyinfo, &settings)) return; apply_xft_settings (dpyinfo, &settings); if (settings.seen & SEEN_TB_STYLE) { if (send_event_p) store_tool_bar_style_changed (settings.tb_style, dpyinfo); else current_tool_bar_style = map_tool_bar_style (settings.tb_style); xfree (settings.tb_style); } #if defined USE_CAIRO || defined HAVE_XFT if (settings.seen & SEEN_FONT) { if (send_event_p) store_font_name_changed (settings.font); else dupstring (¤t_font, settings.font); xfree (settings.font); } #endif } #endif #ifndef HAVE_PGTK /* Check if EVENT for the display in DPYINFO is XSettings related. Return true if it is, after performing associated side effects. */ bool xft_settings_event (Display_Info *dpyinfo, const XEvent *event) { bool check_window_p = false, apply_settings_p = false; switch (event->type) { case DestroyNotify: if (dpyinfo->xsettings_window == event->xany.window) check_window_p = true; break; case ClientMessage: if (event->xclient.message_type == dpyinfo->Xatom_xsettings_mgr && event->xclient.data.l[1] == dpyinfo->Xatom_xsettings_sel && event->xclient.window == dpyinfo->root_window) check_window_p = true; break; case PropertyNotify: if (event->xproperty.window == dpyinfo->xsettings_window && event->xproperty.state == PropertyNewValue && event->xproperty.atom == dpyinfo->Xatom_xsettings_prop) apply_settings_p = true; break; } if (check_window_p) { dpyinfo->xsettings_window = None; get_prop_window (dpyinfo); if (dpyinfo->xsettings_window != None) apply_settings_p = true; } if (apply_settings_p) read_and_apply_settings (dpyinfo, true); return check_window_p || apply_settings_p; } #endif /* Initialize GSettings and read startup values. */ static void init_gsettings (void) { #ifdef HAVE_GSETTINGS GVariant *val; bool schema_found = false; #if ! GLIB_CHECK_VERSION (2, 36, 0) g_type_init (); #endif #if GLIB_CHECK_VERSION (2, 32, 0) { GSettingsSchema *sc = g_settings_schema_source_lookup (g_settings_schema_source_get_default (), GSETTINGS_SCHEMA, true); schema_found = sc != NULL; if (sc) g_settings_schema_unref (sc); } #else { const gchar *const *schemas = g_settings_list_schemas (); if (schemas == NULL) return; while (! schema_found && *schemas != NULL) schema_found = strcmp (*schemas++, GSETTINGS_SCHEMA) == 0; } #endif if (!schema_found) return; gsettings_client = g_settings_new (GSETTINGS_SCHEMA); if (!gsettings_client) return; g_object_ref_sink (G_OBJECT (gsettings_client)); g_signal_connect (G_OBJECT (gsettings_client), "changed", G_CALLBACK (something_changed_gsettingsCB), NULL); val = g_settings_get_value (gsettings_client, GSETTINGS_TOOL_BAR_STYLE); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) current_tool_bar_style = map_tool_bar_style (g_variant_get_string (val, NULL)); g_variant_unref (val); } #if defined USE_CAIRO || defined HAVE_XFT val = g_settings_get_value (gsettings_client, GSETTINGS_MONO_FONT); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) dupstring (¤t_mono_font, g_variant_get_string (val, NULL)); g_variant_unref (val); } val = g_settings_get_value (gsettings_client, GSETTINGS_FONT_NAME); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) dupstring (¤t_font, g_variant_get_string (val, NULL)); g_variant_unref (val); } /* Only use the gsettings font entries for the Cairo backend running on PGTK. */ #ifdef HAVE_PGTK font_options = cairo_font_options_create (); apply_gsettings_font_antialias (gsettings_client); apply_gsettings_font_hinting (gsettings_client); apply_gsettings_font_rgba_order (gsettings_client); #endif /* HAVE_PGTK */ #endif /* USE_CAIRO || HAVE_XFT */ #endif /* HAVE_GSETTINGS */ } /* Get current system dark mode state. */ #if defined HAVE_PGTK && defined HAVE_GSETTINGS bool xg_get_system_dark_mode (void) { if (gsettings_client && xg_settings_key_valid_p (gsettings_client, GSETTINGS_COLOR_SCHEME)) { GVariant *val = g_settings_get_value (gsettings_client, GSETTINGS_COLOR_SCHEME); if (val) { g_variant_ref_sink (val); if (g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) { const char *color_scheme = g_variant_get_string (val, NULL); bool dark_mode_p = (strstr (color_scheme, "dark") != NULL); g_variant_unref (val); return dark_mode_p; } g_variant_unref (val); } } return false; } #endif /* HAVE_PGTK && HAVE_GSETTINGS */ /* Init GConf and read startup values. */ static void init_gconf (void) { #if defined (HAVE_GCONF) char *s; #if ! GLIB_CHECK_VERSION (2, 36, 0) g_type_init (); #endif gconf_client = gconf_client_get_default (); gconf_client_set_error_handling (gconf_client, GCONF_CLIENT_HANDLE_NONE); gconf_client_add_dir (gconf_client, GCONF_TOOL_BAR_STYLE, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); gconf_client_notify_add (gconf_client, GCONF_TOOL_BAR_STYLE, something_changed_gconfCB, NULL, NULL, NULL); s = gconf_client_get_string (gconf_client, GCONF_TOOL_BAR_STYLE, NULL); if (s) { current_tool_bar_style = map_tool_bar_style (s); g_free (s); } #if defined USE_CAIRO || defined HAVE_XFT s = gconf_client_get_string (gconf_client, GCONF_MONO_FONT, NULL); if (s) { dupstring (¤t_mono_font, s); g_free (s); } s = gconf_client_get_string (gconf_client, GCONF_FONT_NAME, NULL); if (s) { dupstring (¤t_font, s); g_free (s); } gconf_client_add_dir (gconf_client, GCONF_MONO_FONT, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); gconf_client_notify_add (gconf_client, GCONF_MONO_FONT, something_changed_gconfCB, NULL, NULL, NULL); gconf_client_add_dir (gconf_client, GCONF_FONT_NAME, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); gconf_client_notify_add (gconf_client, GCONF_FONT_NAME, something_changed_gconfCB, NULL, NULL, NULL); #endif /* USE_CAIRO || HAVE_XFT */ #endif /* HAVE_GCONF */ } #ifndef HAVE_PGTK /* Init Xsettings and read startup values. */ static void init_xsettings (Display_Info *dpyinfo) { Display *dpy = dpyinfo->display; block_input (); /* Select events so we can detect client messages sent when selection owner changes. */ XSelectInput (dpy, dpyinfo->root_window, StructureNotifyMask); get_prop_window (dpyinfo); if (dpyinfo->xsettings_window != None) read_and_apply_settings (dpyinfo, false); unblock_input (); } #endif void xsettings_initialize (Display_Info *dpyinfo) { if (first_dpyinfo == NULL) first_dpyinfo = dpyinfo; init_gconf (); #ifndef HAVE_PGTK init_xsettings (dpyinfo); #endif init_gsettings (); } /* Return the system monospaced font. May be NULL if not known. */ const char * xsettings_get_system_font (void) { return current_mono_font; } #ifdef USE_LUCID /* Return the system font. May be NULL if not known. */ const char * xsettings_get_system_normal_font (void) { return current_font; } #endif #ifdef HAVE_PGTK /* Return the cairo font options, updated from the gsettings font config entries. The caller should call cairo_font_options_destroy on the result. */ cairo_font_options_t * xsettings_get_font_options (void) { if (font_options != NULL) return cairo_font_options_copy (font_options); else /* GSettings is not configured. */ return cairo_font_options_create (); } #endif DEFUN ("font-get-system-normal-font", Ffont_get_system_normal_font, Sfont_get_system_normal_font, 0, 0, 0, doc: /* Get the system default application font. The font is returned as either a font-spec or font name. */) (void) { return current_font ? build_string (current_font) : Qnil; } DEFUN ("font-get-system-font", Ffont_get_system_font, Sfont_get_system_font, 0, 0, 0, doc: /* Get the system default fixed width font. The font is returned as either a font-spec or font name. */) (void) { return current_mono_font ? build_string (current_mono_font) : Qnil; } DEFUN ("tool-bar-get-system-style", Ftool_bar_get_system_style, Stool_bar_get_system_style, 0, 0, 0, doc: /* Get the system tool bar style. If no system tool bar style is known, return `tool-bar-style' if set to a known style. Otherwise return image. */) (void) { if (EQ (Vtool_bar_style, Qimage) || EQ (Vtool_bar_style, Qtext) || EQ (Vtool_bar_style, Qboth) || EQ (Vtool_bar_style, Qboth_horiz) || EQ (Vtool_bar_style, Qtext_image_horiz)) return Vtool_bar_style; if (!NILP (current_tool_bar_style)) return current_tool_bar_style; return Qimage; } void syms_of_xsettings (void) { current_mono_font = NULL; PDUMPER_IGNORE (current_mono_font); current_font = NULL; PDUMPER_IGNORE (current_font); first_dpyinfo = NULL; PDUMPER_IGNORE (first_dpyinfo); #ifdef HAVE_GSETTINGS gsettings_client = NULL; PDUMPER_IGNORE (gsettings_client); #endif #ifdef HAVE_GCONF gconf_client = NULL; PDUMPER_IGNORE (gconf_client); #endif #ifdef HAVE_PGTK font_options = NULL; PDUMPER_IGNORE (font_options); #endif DEFSYM (Qmonospace_font_name, "monospace-font-name"); DEFSYM (Qfont_name, "font-name"); DEFSYM (Qfont_render, "font-render"); DEFSYM (Qdynamic_setting, "dynamic-setting"); DEFSYM (Qfont_render_setting, "font-render-setting"); DEFSYM (Qsystem_font_setting, "system-font-setting"); defsubr (&Sfont_get_system_font); defsubr (&Sfont_get_system_normal_font); DEFVAR_BOOL ("font-use-system-font", use_system_font, doc: /* Non-nil means to apply the system defined font dynamically. When this is non-nil and the system defined fixed width font changes, we update frames dynamically. If this variable is nil, Emacs ignores system font changes. */); use_system_font = false; DEFVAR_LISP ("xft-settings", Vxft_settings, doc: /* Font settings applied to Xft. */); Vxft_settings = empty_unibyte_string; #if defined USE_CAIRO || defined HAVE_XFT Fprovide (Qfont_render_setting, Qnil); #if defined (HAVE_GCONF) || defined (HAVE_GSETTINGS) Fprovide (Qsystem_font_setting, Qnil); #endif #endif current_tool_bar_style = Qnil; DEFSYM (Qtool_bar_style, "tool-bar-style"); defsubr (&Stool_bar_get_system_style); Fprovide (Qdynamic_setting, Qnil); }