1/*
2 * Copyright © 2001, 2007 Red Hat, Inc.
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation, and that the name of Red Hat not be used in advertising or
9 * publicity pertaining to distribution of the software without specific,
10 * written prior permission. Red Hat makes no representations about the
11 * suitability of this software for any purpose. It is provided "as is"
12 * without express or implied warranty.
13 *
14 * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
16 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
18 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
19 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 *
21 * Author: Owen Taylor, Red Hat, Inc.
22 */
23
24#include "config.h"
25
26#include "xsettings-client.h"
27
28#include <gdk/x11/gdkx11display.h>
29#include <gdk/x11/gdkx11property.h>
30#include <gdk/x11/gdkx11screen.h>
31#include <gdk/x11/gdkx11surface.h>
32#include <gdk/x11/gdkprivate-x11.h>
33#include <gdk/x11/gdkdisplay-x11.h>
34#include <gdk/x11/gdkscreen-x11.h>
35
36#include <string.h>
37
38#include <X11/Xlib.h>
39#include <X11/Xmd.h> /* For CARD16 */
40
41typedef enum
42{
43 GDK_SETTING_ACTION_NEW,
44 GDK_SETTING_ACTION_CHANGED,
45 GDK_SETTING_ACTION_DELETED
46} GdkSettingAction;
47
48#include "gdksettings.c"
49
50/* Types of settings possible. Enum values correspond to
51 * protocol values.
52 */
53typedef enum
54{
55 XSETTINGS_TYPE_INT = 0,
56 XSETTINGS_TYPE_STRING = 1,
57 XSETTINGS_TYPE_COLOR = 2
58} XSettingsType;
59
60typedef struct _XSettingsBuffer XSettingsBuffer;
61
62struct _XSettingsBuffer
63{
64 char byte_order;
65 size_t len;
66 unsigned char *data;
67 unsigned char *pos;
68};
69
70static void
71gdk_xsettings_notify (GdkX11Screen *x11_screen,
72 const char *name,
73 GdkSettingAction action)
74{
75 gdk_display_setting_changed (display: x11_screen->display, name);
76}
77
78static gboolean
79value_equal (const GValue *value_a,
80 const GValue *value_b)
81{
82 if (G_VALUE_TYPE (value_a) != G_VALUE_TYPE (value_b))
83 return FALSE;
84
85 switch (G_VALUE_TYPE (value_a))
86 {
87 case G_TYPE_INT:
88 return g_value_get_int (value: value_a) == g_value_get_int (value: value_b);
89 case XSETTINGS_TYPE_COLOR:
90 return gdk_rgba_equal (p1: g_value_get_boxed (value: value_a), p2: g_value_get_boxed (value: value_b));
91 case G_TYPE_STRING:
92 return g_str_equal (v1: g_value_get_string (value: value_a), v2: g_value_get_string (value: value_b));
93 default:
94 g_warning ("unable to compare values of type %s", g_type_name (G_VALUE_TYPE (value_a)));
95 return FALSE;
96 }
97}
98
99static void
100notify_changes (GdkX11Screen *x11_screen,
101 GHashTable *old_list)
102{
103 GHashTableIter iter;
104 GValue *setting, *old_setting;
105 const char *name;
106
107 if (x11_screen->xsettings != NULL)
108 {
109 g_hash_table_iter_init (iter: &iter, hash_table: x11_screen->xsettings);
110 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *) &name, value: (gpointer*) &setting))
111 {
112 old_setting = old_list ? g_hash_table_lookup (hash_table: old_list, key: name) : NULL;
113
114 if (old_setting == NULL)
115 gdk_xsettings_notify (x11_screen, name, action: GDK_SETTING_ACTION_NEW);
116 else if (!value_equal (value_a: setting, value_b: old_setting))
117 gdk_xsettings_notify (x11_screen, name, action: GDK_SETTING_ACTION_CHANGED);
118
119 /* remove setting from old_list */
120 if (old_setting != NULL)
121 g_hash_table_remove (hash_table: old_list, key: name);
122 }
123 }
124
125 if (old_list != NULL)
126 {
127 /* old_list now contains only deleted settings */
128 g_hash_table_iter_init (iter: &iter, hash_table: old_list);
129 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *) &name, value: (gpointer*) &old_setting))
130 gdk_xsettings_notify (x11_screen, name, action: GDK_SETTING_ACTION_DELETED);
131 }
132}
133
134#define BYTES_LEFT(buffer) ((buffer)->data + (buffer)->len - (buffer)->pos)
135
136#define return_if_fail_bytes(buffer, n_bytes) G_STMT_START{ \
137 if (BYTES_LEFT (buffer) < (n_bytes)) \
138 { \
139 g_warning ("Invalid XSETTINGS property (read off end: Expected %u bytes, only %"G_GSIZE_FORMAT" left", \
140 (n_bytes), BYTES_LEFT (buffer)); \
141 return FALSE; \
142 } \
143}G_STMT_END
144
145static gboolean
146fetch_card16 (XSettingsBuffer *buffer,
147 CARD16 *result)
148{
149 CARD16 x;
150
151 return_if_fail_bytes (buffer, 2);
152
153 x = *(CARD16 *)buffer->pos;
154 buffer->pos += 2;
155
156 if (buffer->byte_order == MSBFirst)
157 *result = GUINT16_FROM_BE (x);
158 else
159 *result = GUINT16_FROM_LE (x);
160
161 return TRUE;
162}
163
164static gboolean
165fetch_ushort (XSettingsBuffer *buffer,
166 unsigned short *result)
167{
168 CARD16 x;
169 gboolean r;
170
171 r = fetch_card16 (buffer, result: &x);
172 if (r)
173 *result = x;
174
175 return r;
176}
177
178static gboolean
179fetch_card32 (XSettingsBuffer *buffer,
180 CARD32 *result)
181{
182 CARD32 x;
183
184 return_if_fail_bytes (buffer, 4);
185
186 x = *(CARD32 *)buffer->pos;
187 buffer->pos += 4;
188
189 if (buffer->byte_order == MSBFirst)
190 *result = GUINT32_FROM_BE (x);
191 else
192 *result = GUINT32_FROM_LE (x);
193
194 return TRUE;
195}
196
197static gboolean
198fetch_card8 (XSettingsBuffer *buffer,
199 CARD8 *result)
200{
201 return_if_fail_bytes (buffer, 1);
202
203 *result = *(CARD8 *)buffer->pos;
204 buffer->pos += 1;
205
206 return TRUE;
207}
208
209#define XSETTINGS_PAD(n,m) ((n + m - 1) & (~(m-1)))
210
211static gboolean
212fetch_string (XSettingsBuffer *buffer,
213 guint length,
214 char **result)
215{
216 guint pad_len;
217
218 pad_len = XSETTINGS_PAD (length, 4);
219 if (pad_len < length) /* guard against overflow */
220 {
221 g_warning ("Invalid XSETTINGS property (overflow in string length)");
222 return FALSE;
223 }
224
225 return_if_fail_bytes (buffer, pad_len);
226
227 *result = g_strndup (str: (char *) buffer->pos, n: length);
228 buffer->pos += pad_len;
229
230 return TRUE;
231}
232
233static void
234free_value (gpointer data)
235{
236 GValue *value = data;
237
238 g_value_unset (value);
239 g_free (mem: value);
240}
241
242static GHashTable *
243parse_settings (unsigned char *data,
244 size_t len)
245{
246 XSettingsBuffer buffer;
247 GHashTable *settings = NULL;
248 CARD32 serial;
249 CARD32 n_entries;
250 CARD32 i;
251 GValue *value = NULL;
252 char *x_name = NULL;
253 const char *gdk_name;
254
255 buffer.pos = buffer.data = data;
256 buffer.len = len;
257
258 if (!fetch_card8 (buffer: &buffer, result: (unsigned char *)&buffer.byte_order))
259 goto out;
260
261 if (buffer.byte_order != MSBFirst &&
262 buffer.byte_order != LSBFirst)
263 {
264 g_warning ("Invalid XSETTINGS property (unknown byte order %u)", buffer.byte_order);
265 goto out;
266 }
267
268 buffer.pos += 3;
269
270 if (!fetch_card32 (buffer: &buffer, result: &serial) ||
271 !fetch_card32 (buffer: &buffer, result: &n_entries))
272 goto out;
273
274 GDK_NOTE (SETTINGS, g_message ("reading %lu settings (serial %lu byte order %u)",
275 (unsigned long)n_entries, (unsigned long)serial, buffer.byte_order));
276
277 for (i = 0; i < n_entries; i++)
278 {
279 CARD8 type;
280 CARD16 name_len;
281 CARD32 v_int;
282
283 if (!fetch_card8 (buffer: &buffer, result: &type))
284 goto out;
285
286 buffer.pos += 1;
287
288 if (!fetch_card16 (buffer: &buffer, result: &name_len))
289 goto out;
290
291 if (!fetch_string (buffer: &buffer, length: name_len, result: &x_name) ||
292 /* last change serial (we ignore it) */
293 !fetch_card32 (buffer: &buffer, result: &v_int))
294 goto out;
295
296 switch (type)
297 {
298 case XSETTINGS_TYPE_INT:
299 if (!fetch_card32 (buffer: &buffer, result: &v_int))
300 goto out;
301
302 value = g_new0 (GValue, 1);
303 g_value_init (value, G_TYPE_INT);
304 g_value_set_int (value, v_int: (gint32) v_int);
305
306 GDK_NOTE (SETTINGS, g_message (" %s = %d", x_name, (gint32) v_int));
307 break;
308 case XSETTINGS_TYPE_STRING:
309 {
310 char *s;
311
312 if (!fetch_card32 (buffer: &buffer, result: &v_int) ||
313 !fetch_string (buffer: &buffer, length: v_int, result: &s))
314 goto out;
315
316 value = g_new0 (GValue, 1);
317 g_value_init (value, G_TYPE_STRING);
318 g_value_take_string (value, v_string: s);
319
320 GDK_NOTE (SETTINGS, g_message (" %s = \"%s\"", x_name, s));
321 }
322 break;
323 case XSETTINGS_TYPE_COLOR:
324 {
325 unsigned short red, green, blue, alpha;
326 GdkRGBA rgba;
327
328 if (!fetch_ushort (buffer: &buffer, result: &red) ||
329 !fetch_ushort (buffer: &buffer, result: &green) ||
330 !fetch_ushort (buffer: &buffer, result: &blue) ||
331 !fetch_ushort (buffer: &buffer, result: &alpha))
332 goto out;
333
334 rgba.red = red / 65535.0;
335 rgba.green = green / 65535.0;
336 rgba.blue = blue / 65535.0;
337 rgba.alpha = alpha / 65535.0;
338
339 value = g_new0 (GValue, 1);
340 g_value_init (value, GDK_TYPE_RGBA);
341 g_value_set_boxed (value, v_boxed: &rgba);
342
343 GDK_NOTE (SETTINGS, g_message (" %s = #%02X%02X%02X%02X", x_name, alpha,red, green, blue));
344 }
345 break;
346 default:
347 /* Quietly ignore unknown types */
348 GDK_NOTE (SETTINGS, g_message (" %s = ignored (unknown type %u)", x_name, type));
349 break;
350 }
351
352 gdk_name = gdk_from_xsettings_name (xname: x_name);
353 g_free (mem: x_name);
354 x_name = NULL;
355
356 if (gdk_name == NULL)
357 {
358 GDK_NOTE (SETTINGS, g_message (" ==> unknown to GTK"));
359 free_value (data: value);
360 }
361 else
362 {
363 GDK_NOTE (SETTINGS, g_message (" ==> storing as '%s'", gdk_name));
364
365 if (settings == NULL)
366 settings = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal,
367 NULL,
368 value_destroy_func: free_value);
369
370 if (g_hash_table_lookup (hash_table: settings, key: gdk_name) != NULL)
371 {
372 g_warning ("Invalid XSETTINGS property (Duplicate entry for '%s')", gdk_name);
373 goto out;
374 }
375
376 g_hash_table_insert (hash_table: settings, key: (gpointer) gdk_name, value);
377 }
378
379 value = NULL;
380 }
381
382 return settings;
383
384 out:
385
386 if (value)
387 free_value (data: value);
388
389 if (settings)
390 g_hash_table_unref (hash_table: settings);
391
392 g_free (mem: x_name);
393
394 return NULL;
395}
396
397static void
398read_settings (GdkX11Screen *x11_screen,
399 gboolean do_notify)
400{
401 GdkDisplay *display = x11_screen->display;
402
403 Atom type;
404 int format;
405 unsigned long n_items;
406 unsigned long bytes_after;
407 unsigned char *data;
408 int result;
409
410 GHashTable *old_list = x11_screen->xsettings;
411 GValue value = G_VALUE_INIT;
412 GValue *setting, *copy;
413
414 x11_screen->xsettings = NULL;
415
416 if (x11_screen->xsettings_manager_window != 0)
417 {
418 Atom xsettings_atom = gdk_x11_get_xatom_by_name_for_display (display, atom_name: "_XSETTINGS_SETTINGS");
419
420 gdk_x11_display_error_trap_push (display);
421 result = XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
422 x11_screen->xsettings_manager_window,
423 xsettings_atom, 0, LONG_MAX,
424 False, xsettings_atom,
425 &type, &format, &n_items, &bytes_after, &data);
426 gdk_x11_display_error_trap_pop_ignored (display);
427
428 if (result == Success && type != None)
429 {
430 if (type != xsettings_atom)
431 {
432 g_warning ("Invalid type for XSETTINGS property: %s", gdk_x11_get_xatom_name_for_display (display, type));
433 }
434 else if (format != 8)
435 {
436 g_warning ("Invalid format for XSETTINGS property: %d", format);
437 }
438 else
439 x11_screen->xsettings = parse_settings (data, len: n_items);
440
441 XFree (data);
442 }
443 }
444
445 /* Since we support scaling we look at the specific Gdk/UnscaledDPI
446 setting if it exists and use that instead of Xft/DPI if it is set */
447 if (x11_screen->xsettings && !x11_screen->fixed_surface_scale)
448 {
449 setting = g_hash_table_lookup (hash_table: x11_screen->xsettings, key: "gdk-unscaled-dpi");
450 if (setting)
451 {
452 copy = g_new0 (GValue, 1);
453 g_value_init (value: copy, G_VALUE_TYPE (setting));
454 g_value_copy (src_value: setting, dest_value: copy);
455 g_hash_table_insert (hash_table: x11_screen->xsettings,
456 key: (gpointer) "gtk-xft-dpi", value: copy);
457 }
458 }
459
460 if (do_notify)
461 notify_changes (x11_screen, old_list);
462 if (old_list)
463 g_hash_table_unref (hash_table: old_list);
464
465 g_value_init (value: &value, G_TYPE_INT);
466
467 if (!x11_screen->fixed_surface_scale &&
468 gdk_display_get_setting (display, name: "gdk-window-scaling-factor", value: &value))
469 _gdk_x11_screen_set_surface_scale (x11_screen, scale: g_value_get_int (value: &value));
470}
471
472static Atom
473get_selection_atom (GdkX11Screen *x11_screen)
474{
475 return _gdk_x11_get_xatom_for_display_printf (display: x11_screen->display, format: "_XSETTINGS_S%d", x11_screen->screen_num);
476}
477
478static void
479check_manager_window (GdkX11Screen *x11_screen,
480 gboolean notify_changes)
481{
482 GdkDisplay *display;
483 Display *xdisplay;
484
485 display = x11_screen->display;
486 xdisplay = gdk_x11_display_get_xdisplay (display);
487
488 gdk_x11_display_grab (display);
489
490 if (!GDK_DISPLAY_DEBUG_CHECK (display, DEFAULT_SETTINGS))
491 x11_screen->xsettings_manager_window = XGetSelectionOwner (xdisplay, get_selection_atom (x11_screen));
492
493 if (x11_screen->xsettings_manager_window != 0)
494 XSelectInput (xdisplay,
495 x11_screen->xsettings_manager_window,
496 PropertyChangeMask | StructureNotifyMask);
497
498 gdk_x11_display_ungrab (display);
499
500 gdk_display_flush (display);
501
502 read_settings (x11_screen, do_notify: notify_changes);
503}
504
505GdkFilterReturn
506gdk_xsettings_root_window_filter (const XEvent *xev,
507 gpointer data)
508{
509 GdkX11Screen *x11_screen = data;
510 GdkDisplay *display = x11_screen->display;
511
512 /* The checks here will not unlikely cause us to reread
513 * the properties from the manager window a number of
514 * times when the manager changes from A->B. But manager changes
515 * are going to be pretty rare.
516 */
517 if (xev->xany.type == ClientMessage &&
518 xev->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, atom_name: "MANAGER") &&
519 xev->xclient.data.l[1] == get_selection_atom (x11_screen))
520 {
521 check_manager_window (x11_screen, TRUE);
522 return GDK_FILTER_REMOVE;
523 }
524
525 return GDK_FILTER_CONTINUE;
526}
527
528GdkFilterReturn
529gdk_xsettings_manager_window_filter (const XEvent *xev,
530 gpointer data)
531{
532 GdkX11Screen *x11_screen = data;
533
534 if (xev->xany.type == DestroyNotify)
535 {
536 check_manager_window (x11_screen, TRUE);
537 /* let GDK do its cleanup */
538 return GDK_FILTER_CONTINUE;
539 }
540 else if (xev->xany.type == PropertyNotify)
541 {
542 read_settings (x11_screen, TRUE);
543 return GDK_FILTER_REMOVE;
544 }
545
546 return GDK_FILTER_CONTINUE;
547}
548
549void
550_gdk_x11_xsettings_init (GdkX11Screen *x11_screen)
551{
552 check_manager_window (x11_screen, FALSE);
553}
554
555void
556_gdk_x11_settings_force_reread (GdkX11Screen *x11_screen)
557{
558 read_settings (x11_screen, TRUE);
559}
560
561void
562_gdk_x11_xsettings_finish (GdkX11Screen *x11_screen)
563{
564 if (x11_screen->xsettings_manager_window)
565 x11_screen->xsettings_manager_window = 0;
566
567 if (x11_screen->xsettings)
568 {
569 g_hash_table_unref (hash_table: x11_screen->xsettings);
570 x11_screen->xsettings = NULL;
571 }
572}
573

source code of gtk/gdk/x11/xsettings-client.c