/* 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);
}