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