gtk/gdk/x11/xsettings-client.c
Alexander Larsson a3be0ec5f0 Don't use xsettings or xft defaults in testsuite
This adds a GDK_DEBUG=default-settings flag which disables reads
from xsettings and Xft resources, and enables this for the testsuite.

This is one less way to get different testresults depending on the
environment. In particular, it was failing the css tests for me
due to getting the wrong font size because i have a different dpi.
2020-02-03 15:11:35 +01:00

577 lines
15 KiB
C

/*
* Copyright © 2001, 2007 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of Red Hat not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. Red Hat makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Owen Taylor, Red Hat, Inc.
*/
#include "config.h"
#include "xsettings-client.h"
#include <gdk/x11/gdkx11display.h>
#include <gdk/x11/gdkx11property.h>
#include <gdk/x11/gdkx11screen.h>
#include <gdk/x11/gdkx11surface.h>
#include <gdk/x11/gdkprivate-x11.h>
#include <gdk/x11/gdkdisplay-x11.h>
#include <gdk/x11/gdkscreen-x11.h>
#include <gdkinternals.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xmd.h> /* For CARD16 */
typedef enum
{
GDK_SETTING_ACTION_NEW,
GDK_SETTING_ACTION_CHANGED,
GDK_SETTING_ACTION_DELETED
} GdkSettingAction;
#include "gdksettings.c"
/* Types of settings possible. Enum values correspond to
* protocol values.
*/
typedef enum
{
XSETTINGS_TYPE_INT = 0,
XSETTINGS_TYPE_STRING = 1,
XSETTINGS_TYPE_COLOR = 2
} XSettingsType;
typedef struct _XSettingsBuffer XSettingsBuffer;
struct _XSettingsBuffer
{
char byte_order;
size_t len;
unsigned char *data;
unsigned char *pos;
};
static void
gdk_xsettings_notify (GdkX11Screen *x11_screen,
const char *name,
GdkSettingAction action)
{
gdk_display_setting_changed (x11_screen->display, name);
}
static gboolean
value_equal (const GValue *value_a,
const GValue *value_b)
{
if (G_VALUE_TYPE (value_a) != G_VALUE_TYPE (value_b))
return FALSE;
switch (G_VALUE_TYPE (value_a))
{
case G_TYPE_INT:
return g_value_get_int (value_a) == g_value_get_int (value_b);
case XSETTINGS_TYPE_COLOR:
return gdk_rgba_equal (g_value_get_boxed (value_a), g_value_get_boxed (value_b));
case G_TYPE_STRING:
return g_str_equal (g_value_get_string (value_a), g_value_get_string (value_b));
default:
g_warning ("unable to compare values of type %s", g_type_name (G_VALUE_TYPE (value_a)));
return FALSE;
}
}
static void
notify_changes (GdkX11Screen *x11_screen,
GHashTable *old_list)
{
GHashTableIter iter;
GValue *setting, *old_setting;
const char *name;
if (x11_screen->xsettings != NULL)
{
g_hash_table_iter_init (&iter, x11_screen->xsettings);
while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &setting))
{
old_setting = old_list ? g_hash_table_lookup (old_list, name) : NULL;
if (old_setting == NULL)
gdk_xsettings_notify (x11_screen, name, GDK_SETTING_ACTION_NEW);
else if (!value_equal (setting, old_setting))
gdk_xsettings_notify (x11_screen, name, GDK_SETTING_ACTION_CHANGED);
/* remove setting from old_list */
if (old_setting != NULL)
g_hash_table_remove (old_list, name);
}
}
if (old_list != NULL)
{
/* old_list now contains only deleted settings */
g_hash_table_iter_init (&iter, old_list);
while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &old_setting))
gdk_xsettings_notify (x11_screen, name, GDK_SETTING_ACTION_DELETED);
}
}
#define BYTES_LEFT(buffer) ((buffer)->data + (buffer)->len - (buffer)->pos)
#define return_if_fail_bytes(buffer, n_bytes) G_STMT_START{ \
if (BYTES_LEFT (buffer) < (n_bytes)) \
{ \
g_warning ("Invalid XSETTINGS property (read off end: Expected %u bytes, only %"G_GSIZE_FORMAT" left", \
(n_bytes), BYTES_LEFT (buffer)); \
return FALSE; \
} \
}G_STMT_END
static gboolean
fetch_card16 (XSettingsBuffer *buffer,
CARD16 *result)
{
CARD16 x;
return_if_fail_bytes (buffer, 2);
x = *(CARD16 *)buffer->pos;
buffer->pos += 2;
if (buffer->byte_order == MSBFirst)
*result = GUINT16_FROM_BE (x);
else
*result = GUINT16_FROM_LE (x);
return TRUE;
}
static gboolean
fetch_ushort (XSettingsBuffer *buffer,
unsigned short *result)
{
CARD16 x;
gboolean r;
r = fetch_card16 (buffer, &x);
if (r)
*result = x;
return r;
}
static gboolean
fetch_card32 (XSettingsBuffer *buffer,
CARD32 *result)
{
CARD32 x;
return_if_fail_bytes (buffer, 4);
x = *(CARD32 *)buffer->pos;
buffer->pos += 4;
if (buffer->byte_order == MSBFirst)
*result = GUINT32_FROM_BE (x);
else
*result = GUINT32_FROM_LE (x);
return TRUE;
}
static gboolean
fetch_card8 (XSettingsBuffer *buffer,
CARD8 *result)
{
return_if_fail_bytes (buffer, 1);
*result = *(CARD8 *)buffer->pos;
buffer->pos += 1;
return TRUE;
}
#define XSETTINGS_PAD(n,m) ((n + m - 1) & (~(m-1)))
static gboolean
fetch_string (XSettingsBuffer *buffer,
guint length,
char **result)
{
guint pad_len;
pad_len = XSETTINGS_PAD (length, 4);
if (pad_len < length) /* guard against overflow */
{
g_warning ("Invalid XSETTINGS property (overflow in string length)");
return FALSE;
}
return_if_fail_bytes (buffer, pad_len);
*result = g_strndup ((char *) buffer->pos, length);
buffer->pos += pad_len;
return TRUE;
}
static void
free_value (gpointer data)
{
GValue *value = data;
g_value_unset (value);
g_free (value);
}
static GHashTable *
parse_settings (unsigned char *data,
size_t len)
{
XSettingsBuffer buffer;
GHashTable *settings = NULL;
CARD32 serial;
CARD32 n_entries;
CARD32 i;
GValue *value = NULL;
char *x_name = NULL;
const char *gdk_name;
buffer.pos = buffer.data = data;
buffer.len = len;
if (!fetch_card8 (&buffer, (unsigned char *)&buffer.byte_order))
goto out;
if (buffer.byte_order != MSBFirst &&
buffer.byte_order != LSBFirst)
{
g_warning ("Invalid XSETTINGS property (unknown byte order %u)", buffer.byte_order);
goto out;
}
buffer.pos += 3;
if (!fetch_card32 (&buffer, &serial) ||
!fetch_card32 (&buffer, &n_entries))
goto out;
GDK_NOTE (SETTINGS, g_message ("reading %lu settings (serial %lu byte order %u)",
(unsigned long)n_entries, (unsigned long)serial, buffer.byte_order));
for (i = 0; i < n_entries; i++)
{
CARD8 type;
CARD16 name_len;
CARD32 v_int;
if (!fetch_card8 (&buffer, &type))
goto out;
buffer.pos += 1;
if (!fetch_card16 (&buffer, &name_len))
goto out;
if (!fetch_string (&buffer, name_len, &x_name) ||
/* last change serial (we ignore it) */
!fetch_card32 (&buffer, &v_int))
goto out;
switch (type)
{
case XSETTINGS_TYPE_INT:
if (!fetch_card32 (&buffer, &v_int))
goto out;
value = g_new0 (GValue, 1);
g_value_init (value, G_TYPE_INT);
g_value_set_int (value, (gint32) v_int);
GDK_NOTE (SETTINGS, g_message (" %s = %d", x_name, (gint32) v_int));
break;
case XSETTINGS_TYPE_STRING:
{
char *s;
if (!fetch_card32 (&buffer, &v_int) ||
!fetch_string (&buffer, v_int, &s))
goto out;
value = g_new0 (GValue, 1);
g_value_init (value, G_TYPE_STRING);
g_value_take_string (value, s);
GDK_NOTE (SETTINGS, g_message (" %s = \"%s\"", x_name, s));
}
break;
case XSETTINGS_TYPE_COLOR:
{
unsigned short red, green, blue, alpha;
GdkRGBA rgba;
if (!fetch_ushort (&buffer, &red) ||
!fetch_ushort (&buffer, &green) ||
!fetch_ushort (&buffer, &blue) ||
!fetch_ushort (&buffer, &alpha))
goto out;
rgba.red = red / 65535.0;
rgba.green = green / 65535.0;
rgba.blue = blue / 65535.0;
rgba.alpha = alpha / 65535.0;
value = g_new0 (GValue, 1);
g_value_init (value, G_TYPE_STRING);
g_value_set_boxed (value, &rgba);
GDK_NOTE (SETTINGS, g_message (" %s = #%02X%02X%02X%02X", x_name, alpha,red, green, blue));
}
break;
default:
/* Quietly ignore unknown types */
GDK_NOTE (SETTINGS, g_message (" %s = ignored (unknown type %u)", x_name, type));
break;
}
gdk_name = gdk_from_xsettings_name (x_name);
g_free (x_name);
x_name = NULL;
if (gdk_name == NULL)
{
GDK_NOTE (SETTINGS, g_message (" ==> unknown to GTK"));
free_value (value);
}
else
{
GDK_NOTE (SETTINGS, g_message (" ==> storing as '%s'", gdk_name));
if (settings == NULL)
settings = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL,
free_value);
if (g_hash_table_lookup (settings, gdk_name) != NULL)
{
g_warning ("Invalid XSETTINGS property (Duplicate entry for '%s')", gdk_name);
goto out;
}
g_hash_table_insert (settings, (gpointer) gdk_name, value);
}
value = NULL;
}
return settings;
out:
if (value)
free_value (value);
if (settings)
g_hash_table_unref (settings);
g_free (x_name);
return NULL;
}
static void
read_settings (GdkX11Screen *x11_screen,
gboolean do_notify)
{
GdkDisplay *display = x11_screen->display;
Atom type;
int format;
unsigned long n_items;
unsigned long bytes_after;
unsigned char *data;
int result;
GHashTable *old_list = x11_screen->xsettings;
GValue value = G_VALUE_INIT;
GValue *setting, *copy;
x11_screen->xsettings = NULL;
if (x11_screen->xsettings_manager_window != 0)
{
Atom xsettings_atom = gdk_x11_get_xatom_by_name_for_display (display, "_XSETTINGS_SETTINGS");
gdk_x11_display_error_trap_push (display);
result = XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
x11_screen->xsettings_manager_window,
xsettings_atom, 0, LONG_MAX,
False, xsettings_atom,
&type, &format, &n_items, &bytes_after, &data);
gdk_x11_display_error_trap_pop_ignored (display);
if (result == Success && type != None)
{
if (type != xsettings_atom)
{
g_warning ("Invalid type for XSETTINGS property: %s", gdk_x11_get_xatom_name_for_display (display, type));
}
else if (format != 8)
{
g_warning ("Invalid format for XSETTINGS property: %d", format);
}
else
x11_screen->xsettings = parse_settings (data, n_items);
XFree (data);
}
}
/* Since we support scaling we look at the specific Gdk/UnscaledDPI
setting if it exists and use that instead of Xft/DPI if it is set */
if (x11_screen->xsettings && !x11_screen->fixed_surface_scale)
{
setting = g_hash_table_lookup (x11_screen->xsettings, "gdk-unscaled-dpi");
if (setting)
{
copy = g_new0 (GValue, 1);
g_value_init (copy, G_VALUE_TYPE (setting));
g_value_copy (setting, copy);
g_hash_table_insert (x11_screen->xsettings,
(gpointer) "gtk-xft-dpi", copy);
}
}
if (do_notify)
notify_changes (x11_screen, old_list);
if (old_list)
g_hash_table_unref (old_list);
g_value_init (&value, G_TYPE_INT);
if (!x11_screen->fixed_surface_scale &&
gdk_display_get_setting (display, "gdk-window-scaling-factor", &value))
_gdk_x11_screen_set_surface_scale (x11_screen, g_value_get_int (&value));
}
static Atom
get_selection_atom (GdkX11Screen *x11_screen)
{
return _gdk_x11_get_xatom_for_display_printf (x11_screen->display, "_XSETTINGS_S%d", x11_screen->screen_num);
}
static void
check_manager_window (GdkX11Screen *x11_screen,
gboolean notify_changes)
{
GdkDisplay *display;
Display *xdisplay;
display = x11_screen->display;
xdisplay = gdk_x11_display_get_xdisplay (display);
gdk_x11_display_grab (display);
if (!GDK_DISPLAY_DEBUG_CHECK (display, DEFAULT_SETTINGS))
x11_screen->xsettings_manager_window = XGetSelectionOwner (xdisplay, get_selection_atom (x11_screen));
if (x11_screen->xsettings_manager_window != 0)
XSelectInput (xdisplay,
x11_screen->xsettings_manager_window,
PropertyChangeMask | StructureNotifyMask);
gdk_x11_display_ungrab (display);
gdk_display_flush (display);
read_settings (x11_screen, notify_changes);
}
GdkFilterReturn
gdk_xsettings_root_window_filter (const XEvent *xev,
GdkEvent *event,
gpointer data)
{
GdkX11Screen *x11_screen = data;
GdkDisplay *display = x11_screen->display;
/* The checks here will not unlikely cause us to reread
* the properties from the manager window a number of
* times when the manager changes from A->B. But manager changes
* are going to be pretty rare.
*/
if (xev->xany.type == ClientMessage &&
xev->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "MANAGER") &&
xev->xclient.data.l[1] == get_selection_atom (x11_screen))
{
check_manager_window (x11_screen, TRUE);
return GDK_FILTER_REMOVE;
}
return GDK_FILTER_CONTINUE;
}
GdkFilterReturn
gdk_xsettings_manager_window_filter (const XEvent *xev,
GdkEvent *event,
gpointer data)
{
GdkX11Screen *x11_screen = data;
if (xev->xany.type == DestroyNotify)
{
check_manager_window (x11_screen, TRUE);
/* let GDK do its cleanup */
return GDK_FILTER_CONTINUE;
}
else if (xev->xany.type == PropertyNotify)
{
read_settings (x11_screen, TRUE);
return GDK_FILTER_REMOVE;
}
return GDK_FILTER_CONTINUE;
}
void
_gdk_x11_xsettings_init (GdkX11Screen *x11_screen)
{
check_manager_window (x11_screen, FALSE);
}
void
_gdk_x11_settings_force_reread (GdkX11Screen *x11_screen)
{
read_settings (x11_screen, TRUE);
}
void
_gdk_x11_xsettings_finish (GdkX11Screen *x11_screen)
{
if (x11_screen->xsettings_manager_window)
x11_screen->xsettings_manager_window = 0;
if (x11_screen->xsettings)
{
g_hash_table_unref (x11_screen->xsettings);
x11_screen->xsettings = NULL;
}
}