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