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 | |
41 | typedef 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 | */ |
53 | typedef enum |
54 | { |
55 | XSETTINGS_TYPE_INT = 0, |
56 | XSETTINGS_TYPE_STRING = 1, |
57 | XSETTINGS_TYPE_COLOR = 2 |
58 | } XSettingsType; |
59 | |
60 | typedef struct _XSettingsBuffer XSettingsBuffer; |
61 | |
62 | struct _XSettingsBuffer |
63 | { |
64 | char byte_order; |
65 | size_t len; |
66 | unsigned char *data; |
67 | unsigned char *pos; |
68 | }; |
69 | |
70 | static void |
71 | gdk_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 | |
78 | static gboolean |
79 | value_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 | |
99 | static void |
100 | notify_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 | |
145 | static gboolean |
146 | fetch_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 | |
164 | static gboolean |
165 | fetch_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 | |
178 | static gboolean |
179 | fetch_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 | |
197 | static gboolean |
198 | fetch_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 | |
211 | static gboolean |
212 | fetch_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 | |
233 | static void |
234 | free_value (gpointer data) |
235 | { |
236 | GValue *value = data; |
237 | |
238 | g_value_unset (value); |
239 | g_free (mem: value); |
240 | } |
241 | |
242 | static GHashTable * |
243 | parse_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 | |
397 | static void |
398 | read_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 | |
472 | static Atom |
473 | get_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 | |
478 | static void |
479 | check_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 | |
505 | GdkFilterReturn |
506 | gdk_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 | |
528 | GdkFilterReturn |
529 | gdk_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 | |
549 | void |
550 | _gdk_x11_xsettings_init (GdkX11Screen *x11_screen) |
551 | { |
552 | check_manager_window (x11_screen, FALSE); |
553 | } |
554 | |
555 | void |
556 | _gdk_x11_settings_force_reread (GdkX11Screen *x11_screen) |
557 | { |
558 | read_settings (x11_screen, TRUE); |
559 | } |
560 | |
561 | void |
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 | |