]> Pileus Git - ~andy/gtk/blob - gdk/x11/xsettings-client.c
x11: Get rid of XSettingsClient object
[~andy/gtk] / gdk / x11 / xsettings-client.c
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
65 gdk_xsettings_notify (GdkX11Screen     *x11_screen,
66                       const char       *name,
67                       GdkSettingAction  action)
68 {
69   GdkEvent new_event;
70
71   new_event.type = GDK_SETTING;
72   new_event.setting.window = gdk_screen_get_root_window (GDK_SCREEN (x11_screen));
73   new_event.setting.send_event = FALSE;
74   new_event.setting.action = action;
75   new_event.setting.name = (char*) name;
76
77   gdk_event_put (&new_event);
78 }
79
80 static gboolean
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
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 %ld left", \
142                  (n_bytes), BYTES_LEFT (buffer)); \
143       return FALSE; \
144     } \
145 }G_STMT_END
146
147 static gboolean
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
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
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
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
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
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 *
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_print("reading %u settings (serial %u byte order %u)\n", n_entries, serial, buffer.byte_order));
277
278   for (i = 0; i < n_entries; i++)
279     {
280       CARD8 type;
281       CARD16 name_len;
282       CARD32 v_int;
283       
284       if (!fetch_card8 (&buffer, &type))
285         goto out;
286
287       buffer.pos += 1;
288
289       if (!fetch_card16 (&buffer, &name_len))
290         goto out;
291
292       if (!fetch_string (&buffer, name_len, &x_name) ||
293           /* last change serial (we ignore it) */
294           !fetch_card32 (&buffer, &v_int))
295         goto out;
296
297       switch (type)
298         {
299         case XSETTINGS_TYPE_INT:
300           if (!fetch_card32 (&buffer, &v_int))
301             goto out;
302
303           value = g_new0 (GValue, 1);
304           g_value_init (value, G_TYPE_INT);
305           g_value_set_int (value, (gint32) v_int);
306
307           GDK_NOTE(SETTINGS, g_print("  %s = %d\n", x_name, (gint32) v_int));
308           break;
309         case XSETTINGS_TYPE_STRING:
310           {
311             char *s;
312
313             if (!fetch_card32 (&buffer, &v_int) ||
314                 !fetch_string (&buffer, v_int, &s))
315               goto out;
316             
317             value = g_new0 (GValue, 1);
318             g_value_init (value, G_TYPE_STRING);
319             g_value_take_string (value, s);
320
321             GDK_NOTE(SETTINGS, g_print("  %s = \"%s\"\n", x_name, s));
322           }
323           break;
324         case XSETTINGS_TYPE_COLOR:
325           {
326             unsigned short red, green, blue, alpha;
327             GdkRGBA rgba;
328
329             if (!fetch_ushort (&buffer, &red) ||
330                 !fetch_ushort (&buffer, &green) ||
331                 !fetch_ushort (&buffer, &blue) ||
332                 !fetch_ushort (&buffer, &alpha))
333               goto out;
334
335             rgba.red = red / 65535.0;
336             rgba.green = green / 65535.0;
337             rgba.blue = blue / 65535.0;
338             rgba.alpha = alpha / 65535.0;
339
340             value = g_new0 (GValue, 1);
341             g_value_init (value, G_TYPE_STRING);
342             g_value_set_boxed (value, &rgba);
343
344             GDK_NOTE(SETTINGS, g_print("  %s = #%02X%02X%02X%02X\n", x_name, alpha,red, green, blue));
345           }
346           break;
347         default:
348           /* Quietly ignore unknown types */
349           GDK_NOTE(SETTINGS, g_print("  %s = ignored (unknown type %u)\n", x_name, type));
350           break;
351         }
352
353       gdk_name = gdk_from_xsettings_name (x_name);
354       g_free (x_name);
355       x_name = NULL;
356
357       if (gdk_name == NULL)
358         {
359           GDK_NOTE(SETTINGS, g_print("    ==> unknown to GTK\n"));
360         }
361       else
362         {
363           GDK_NOTE(SETTINGS, g_print("    ==> storing as '%s'\n", gdk_name));
364
365           if (settings == NULL)
366             settings = g_hash_table_new_full (g_str_hash, g_str_equal,
367                                               NULL,
368                                               free_value);
369
370           if (g_hash_table_lookup (settings, 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 (settings, (gpointer) gdk_name, value);
377         }
378
379       value = NULL;
380     }
381
382   return settings;
383
384  out:
385
386   if (value)
387     free_value (value);
388
389   if (settings)
390     g_hash_table_unref (settings);
391
392   g_free (x_name);
393
394   return NULL;
395 }
396
397 static void
398 read_settings (GdkX11Screen *x11_screen,
399                gboolean      do_notify)
400 {
401   Atom type;
402   int format;
403   unsigned long n_items;
404   unsigned long bytes_after;
405   unsigned char *data;
406   int result;
407
408   GHashTable *old_list = x11_screen->xsettings;
409
410   x11_screen->xsettings = NULL;
411
412   if (x11_screen->xsettings_manager_window)
413     {
414       GdkDisplay *display = x11_screen->display;
415       Atom xsettings_atom = gdk_x11_get_xatom_by_name_for_display (display, "_XSETTINGS_SETTINGS");
416
417       gdk_x11_display_error_trap_push (display);
418       result = XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
419                                    gdk_x11_window_get_xid (x11_screen->xsettings_manager_window),
420                                    xsettings_atom, 0, LONG_MAX,
421                                    False, xsettings_atom,
422                                    &type, &format, &n_items, &bytes_after, &data);
423       gdk_x11_display_error_trap_pop_ignored (display);
424       
425       if (result == Success && type != None)
426         {
427           if (type != xsettings_atom)
428             {
429               g_warning ("Invalid type for XSETTINGS property: %s", gdk_x11_get_xatom_name_for_display (display, type));
430             }
431           else if (format != 8)
432             {
433               g_warning ("Invalid format for XSETTINGS property: %d", format);
434             }
435           else
436             x11_screen->xsettings = parse_settings (data, n_items);
437           
438           XFree (data);
439         }
440     }
441
442   if (do_notify)
443     notify_changes (x11_screen, old_list);
444   if (old_list)
445     g_hash_table_unref (old_list);
446 }
447
448 static Atom
449 get_selection_atom (GdkX11Screen *x11_screen)
450 {
451   return _gdk_x11_get_xatom_for_display_printf (x11_screen->display, "_XSETTINGS_S%d", x11_screen->screen_num);
452 }
453
454 static GdkFilterReturn
455 gdk_xsettings_manager_window_filter (GdkXEvent *xevent,
456                                      GdkEvent  *event,
457                                      gpointer   data);
458
459 static void
460 check_manager_window (GdkX11Screen *x11_screen,
461                       gboolean      notify_changes)
462 {
463   GdkDisplay *display;
464   Display *xdisplay;
465   Window manager_window_xid;
466
467   display = x11_screen->display;
468   xdisplay = gdk_x11_display_get_xdisplay (display);
469
470   if (x11_screen->xsettings_manager_window)
471     {
472       gdk_window_remove_filter (x11_screen->xsettings_manager_window, gdk_xsettings_manager_window_filter, x11_screen);
473       g_object_unref (x11_screen->xsettings_manager_window);
474     }
475
476   gdk_x11_display_grab (display);
477
478   manager_window_xid = XGetSelectionOwner (xdisplay, get_selection_atom (x11_screen));
479   x11_screen->xsettings_manager_window = gdk_x11_window_foreign_new_for_display (display,
480                                                                    manager_window_xid);
481   /* XXX: Can't use gdk_window_set_events() here because the first call to this
482    * function happens too early in gdk_init() */
483   if (x11_screen->xsettings_manager_window)
484     XSelectInput (xdisplay,
485                   gdk_x11_window_get_xid (x11_screen->xsettings_manager_window),
486                   PropertyChangeMask | StructureNotifyMask);
487
488   gdk_x11_display_ungrab (display);
489   
490   gdk_display_flush (display);
491
492   if (x11_screen->xsettings_manager_window)
493     {
494       gdk_window_add_filter (x11_screen->xsettings_manager_window, gdk_xsettings_manager_window_filter, x11_screen);
495     }
496       
497   read_settings (x11_screen, notify_changes);
498 }
499
500 static GdkFilterReturn
501 gdk_xsettings_root_window_filter (GdkXEvent *xevent,
502                                   GdkEvent  *event,
503                                   gpointer   data)
504 {
505   GdkX11Screen *x11_screen = data;
506   GdkDisplay *display = x11_screen->display;
507   XEvent *xev = xevent;
508
509   /* The checks here will not unlikely cause us to reread
510    * the properties from the manager window a number of
511    * times when the manager changes from A->B. But manager changes
512    * are going to be pretty rare.
513    */
514   if (xev->xany.type == ClientMessage &&
515       xev->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "MANAGER") &&
516       xev->xclient.data.l[1] == get_selection_atom (x11_screen))
517     {
518       check_manager_window (x11_screen, TRUE);
519       return GDK_FILTER_REMOVE;
520     }
521   
522   return GDK_FILTER_CONTINUE;
523 }
524
525 static GdkFilterReturn
526 gdk_xsettings_manager_window_filter (GdkXEvent *xevent,
527                                      GdkEvent  *event,
528                                      gpointer   data)
529 {
530   GdkX11Screen *x11_screen = data;
531   XEvent *xev = xevent;
532
533   if (xev->xany.type == DestroyNotify)
534     {
535       check_manager_window (x11_screen, TRUE);
536       /* let GDK do its cleanup */
537       return GDK_FILTER_CONTINUE; 
538     }
539   else if (xev->xany.type == PropertyNotify)
540     {
541       read_settings (x11_screen, TRUE);
542       return GDK_FILTER_REMOVE;
543     }
544   
545   return GDK_FILTER_CONTINUE;;
546 }
547
548 void
549 _gdk_x11_xsettings_init (GdkX11Screen *x11_screen)
550 {
551   gdk_window_add_filter (gdk_screen_get_root_window (GDK_SCREEN (x11_screen)), gdk_xsettings_root_window_filter, x11_screen);
552
553   check_manager_window (x11_screen, FALSE);
554 }
555
556 void
557 _gdk_x11_xsettings_finish (GdkX11Screen *x11_screen)
558 {
559   gdk_window_remove_filter (gdk_screen_get_root_window (GDK_SCREEN (x11_screen)), gdk_xsettings_root_window_filter, x11_screen);
560   if (x11_screen->xsettings_manager_window)
561     {
562       gdk_window_remove_filter (x11_screen->xsettings_manager_window, gdk_xsettings_manager_window_filter, x11_screen);
563       g_object_unref (x11_screen->xsettings_manager_window);
564       x11_screen->xsettings_manager_window = NULL;
565     }
566   
567   if (x11_screen->xsettings)
568     {
569       g_hash_table_unref (x11_screen->xsettings);
570       x11_screen->xsettings = NULL;
571     }
572 }
573