]> Pileus Git - ~andy/gtk/blob - gdk/x11/xsettings-client.c
x11: Don't store name in XSettingsSetting anymore
[~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/gdkx11screen.h>
30 #include <gdk/x11/gdkx11window.h>
31
32 #include <gdkinternals.h>
33
34 #include <string.h>
35
36 #include <X11/Xlib.h>
37 #include <X11/Xmd.h>            /* For CARD16 */
38
39 typedef struct _XSettingsBuffer  XSettingsBuffer;
40
41 struct _XSettingsBuffer
42 {
43   char byte_order;
44   size_t len;
45   unsigned char *data;
46   unsigned char *pos;
47 };
48
49 struct _XSettingsClient
50 {
51   GdkScreen *screen;
52   Display *display;
53   XSettingsNotifyFunc notify;
54   XSettingsWatchFunc watch;
55   void *cb_data;
56
57   Window manager_window;
58   Atom manager_atom;
59   Atom selection_atom;
60   Atom xsettings_atom;
61
62   GHashTable *settings; /* string => XSettingsSetting */
63 };
64
65 static void
66 notify_changes (XSettingsClient *client,
67                 GHashTable      *old_list)
68 {
69   GHashTableIter iter;
70   XSettingsSetting *setting, *old_setting;
71   const char *name;
72
73   if (!client->notify)
74     return;
75
76   if (client->settings != NULL)
77     {
78       g_hash_table_iter_init (&iter, client->settings);
79       while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &setting))
80         {
81           old_setting = old_list ? g_hash_table_lookup (old_list, name) : NULL;
82
83           if (old_setting == NULL)
84             client->notify (name, XSETTINGS_ACTION_NEW, setting, client->cb_data);
85           else if (!xsettings_setting_equal (setting, old_setting))
86             client->notify (name, XSETTINGS_ACTION_CHANGED, setting, client->cb_data);
87             
88           /* remove setting from old_list */
89           if (old_setting != NULL)
90             g_hash_table_remove (old_list, name);
91         }
92     }
93
94   if (old_list != NULL)
95     {
96       /* old_list now contains only deleted settings */
97       g_hash_table_iter_init (&iter, old_list);
98       while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &old_setting))
99         client->notify (name, XSETTINGS_ACTION_DELETED, NULL, client->cb_data);
100     }
101 }
102
103 #define BYTES_LEFT(buffer) ((buffer)->data + (buffer)->len - (buffer)->pos)
104
105 #define return_if_fail_bytes(buffer, n_bytes) G_STMT_START{ \
106   if (BYTES_LEFT (buffer) < (n_bytes)) \
107     { \
108       g_warning ("Invalid XSETTINGS property (read off end: Expected %u bytes, only %ld left", \
109                  (n_bytes), BYTES_LEFT (buffer)); \
110       return FALSE; \
111     } \
112 }G_STMT_END
113
114 static gboolean
115 fetch_card16 (XSettingsBuffer *buffer,
116               CARD16          *result)
117 {
118   CARD16 x;
119
120   return_if_fail_bytes (buffer, 2);
121
122   x = *(CARD16 *)buffer->pos;
123   buffer->pos += 2;
124   
125   if (buffer->byte_order == MSBFirst)
126     *result = GUINT16_FROM_BE (x);
127   else
128     *result = GUINT16_FROM_LE (x);
129
130   return TRUE;
131 }
132
133 static gboolean
134 fetch_ushort (XSettingsBuffer *buffer,
135               unsigned short  *result) 
136 {
137   CARD16 x;
138   gboolean r;  
139
140   r = fetch_card16 (buffer, &x);
141   if (r)
142     *result = x;
143
144   return r;
145 }
146
147 static gboolean
148 fetch_card32 (XSettingsBuffer *buffer,
149               CARD32          *result)
150 {
151   CARD32 x;
152
153   return_if_fail_bytes (buffer, 4);
154
155   x = *(CARD32 *)buffer->pos;
156   buffer->pos += 4;
157   
158   if (buffer->byte_order == MSBFirst)
159     *result = GUINT32_FROM_BE (x);
160   else
161     *result = GUINT32_FROM_LE (x);
162   
163   return TRUE;
164 }
165
166 static gboolean
167 fetch_card8 (XSettingsBuffer *buffer,
168              CARD8           *result)
169 {
170   return_if_fail_bytes (buffer, 1);
171
172   *result = *(CARD8 *)buffer->pos;
173   buffer->pos += 1;
174
175   return TRUE;
176 }
177
178 #define XSETTINGS_PAD(n,m) ((n + m - 1) & (~(m-1)))
179
180 static gboolean
181 fetch_string (XSettingsBuffer  *buffer,
182               guint             length,
183               char            **result)
184 {
185   guint pad_len;
186
187   pad_len = XSETTINGS_PAD (length, 4);
188   if (pad_len < length) /* guard against overflow */
189     {
190       g_warning ("Invalid XSETTINGS property (overflow in string length)");
191       return FALSE;
192     }
193
194   return_if_fail_bytes (buffer, pad_len);
195
196   *result = g_strndup ((char *) buffer->pos, length);
197   buffer->pos += pad_len;
198
199   return TRUE;
200 }
201
202 static GHashTable *
203 parse_settings (unsigned char *data,
204                 size_t         len)
205 {
206   XSettingsBuffer buffer;
207   GHashTable *settings = NULL;
208   CARD32 serial;
209   CARD32 n_entries;
210   CARD32 i;
211   XSettingsSetting *setting = NULL;
212   char *name;
213   
214   buffer.pos = buffer.data = data;
215   buffer.len = len;
216   
217   if (!fetch_card8 (&buffer, (unsigned char *)&buffer.byte_order))
218     goto out;
219
220   if (buffer.byte_order != MSBFirst &&
221       buffer.byte_order != LSBFirst)
222     {
223       g_warning ("Invalid XSETTINGS property (unknown byte order %u)", buffer.byte_order);
224       goto out;
225     }
226
227   buffer.pos += 3;
228
229   if (!fetch_card32 (&buffer, &serial) ||
230       !fetch_card32 (&buffer, &n_entries))
231     goto out;
232
233   GDK_NOTE(SETTINGS, g_print("reading %u settings (serial %u byte order %u)\n", n_entries, serial, buffer.byte_order));
234
235   for (i = 0; i < n_entries; i++)
236     {
237       CARD8 type;
238       CARD16 name_len;
239       CARD32 v_int;
240       
241       if (!fetch_card8 (&buffer, &type))
242         goto out;
243
244       buffer.pos += 1;
245
246       if (!fetch_card16 (&buffer, &name_len))
247         goto out;
248
249       setting = g_new (XSettingsSetting, 1);
250       setting->type = XSETTINGS_TYPE_INT; /* No allocated memory */
251
252       if (!fetch_string (&buffer, name_len, &name) ||
253           /* last change serial (we ignore it) */
254           !fetch_card32 (&buffer, &v_int))
255         goto out;
256
257       switch (type)
258         {
259         case XSETTINGS_TYPE_INT:
260           if (!fetch_card32 (&buffer, &v_int))
261             goto out;
262
263           setting->data.v_int = (INT32)v_int;
264           GDK_NOTE(SETTINGS, g_print("  %s = %d\n", name, (gint) setting->data.v_int));
265           break;
266         case XSETTINGS_TYPE_STRING:
267           if (!fetch_card32 (&buffer, &v_int) ||
268               !fetch_string (&buffer, v_int, &setting->data.v_string))
269             goto out;
270           
271           GDK_NOTE(SETTINGS, g_print("  %s = \"%s\"\n", name, setting->data.v_string));
272           break;
273         case XSETTINGS_TYPE_COLOR:
274           if (!fetch_ushort (&buffer, &setting->data.v_color.red) ||
275               !fetch_ushort (&buffer, &setting->data.v_color.green) ||
276               !fetch_ushort (&buffer, &setting->data.v_color.blue) ||
277               !fetch_ushort (&buffer, &setting->data.v_color.alpha))
278             goto out;
279
280           GDK_NOTE(SETTINGS, g_print("  %s = #%02X%02X%02X%02X\n", name, 
281                                  setting->data.v_color.alpha, setting->data.v_color.red,
282                                  setting->data.v_color.green, setting->data.v_color.blue));
283           break;
284         default:
285           /* Quietly ignore unknown types */
286           GDK_NOTE(SETTINGS, g_print("  %s = ignored (unknown type %u)\n", name, type));
287           break;
288         }
289
290       setting->type = type;
291
292       if (settings == NULL)
293         settings = g_hash_table_new_full (g_str_hash, g_str_equal,
294                                           g_free,
295                                           (GDestroyNotify) xsettings_setting_free);
296
297       if (g_hash_table_lookup (settings, name) != NULL)
298         {
299           g_warning ("Invalid XSETTINGS property (Duplicate entry for '%s')", name);
300           goto out;
301         }
302
303       g_hash_table_insert (settings, name, setting);
304       setting = NULL;
305       name = NULL;
306     }
307
308   return settings;
309
310  out:
311
312   if (setting)
313     xsettings_setting_free (setting);
314
315   if (settings)
316     g_hash_table_unref (settings);
317
318   return NULL;
319 }
320
321 static void
322 read_settings (XSettingsClient *client)
323 {
324   Atom type;
325   int format;
326   unsigned long n_items;
327   unsigned long bytes_after;
328   unsigned char *data;
329   int result;
330
331   GHashTable *old_list = client->settings;
332
333   client->settings = NULL;
334
335   if (client->manager_window)
336     {
337       gdk_x11_display_error_trap_push (gdk_screen_get_display (client->screen));
338       result = XGetWindowProperty (client->display, client->manager_window,
339                                    client->xsettings_atom, 0, LONG_MAX,
340                                    False, client->xsettings_atom,
341                                    &type, &format, &n_items, &bytes_after, &data);
342       gdk_x11_display_error_trap_pop_ignored (gdk_screen_get_display (client->screen));
343       
344       if (result == Success && type != None)
345         {
346           if (type != client->xsettings_atom)
347             {
348               fprintf (stderr, "Invalid type for XSETTINGS property");
349             }
350           else if (format != 8)
351             {
352               fprintf (stderr, "Invalid format for XSETTINGS property %d", format);
353             }
354           else
355             client->settings = parse_settings (data, n_items);
356           
357           XFree (data);
358         }
359     }
360
361   notify_changes (client, old_list);
362   if (old_list)
363     g_hash_table_unref (old_list);
364 }
365
366 static void
367 add_events (Display *display,
368             Window   window,
369             long     mask)
370 {
371   XWindowAttributes attr;
372
373   XGetWindowAttributes (display, window, &attr);
374   XSelectInput (display, window, attr.your_event_mask | mask);
375 }
376
377 static void
378 check_manager_window (XSettingsClient *client)
379 {
380   if (client->manager_window && client->watch)
381     client->watch (client->manager_window, False, 0, client->cb_data);
382
383   gdk_x11_display_grab (gdk_screen_get_display (client->screen));
384
385   client->manager_window = XGetSelectionOwner (client->display,
386                                                client->selection_atom);
387   if (client->manager_window)
388     XSelectInput (client->display, client->manager_window,
389                   PropertyChangeMask | StructureNotifyMask);
390
391   gdk_x11_display_ungrab (gdk_screen_get_display (client->screen));
392   
393   XFlush (client->display);
394
395   if (client->manager_window && client->watch)
396     {
397       if (!client->watch (client->manager_window, True, 
398                           PropertyChangeMask | StructureNotifyMask,
399                           client->cb_data))
400         {
401           /* Inability to watch the window probably means that it was destroyed
402            * after we ungrabbed
403            */
404           client->manager_window = None;
405           return;
406         }
407     }
408       
409   
410   read_settings (client);
411 }
412
413 XSettingsClient *
414 xsettings_client_new (GdkScreen           *screen,
415                       XSettingsNotifyFunc  notify,
416                       XSettingsWatchFunc   watch,
417                       void                *cb_data)
418 {
419   XSettingsClient *client;
420   char buffer[256];
421   char *atom_names[3];
422   Atom atoms[3];
423   
424   client = g_new (XSettingsClient, 1);
425   if (!client)
426     return NULL;
427
428   client->screen = screen;
429   client->display = gdk_x11_display_get_xdisplay (gdk_screen_get_display (screen));
430   client->notify = notify;
431   client->watch = watch;
432   client->cb_data = cb_data;
433   client->manager_window = None;
434   client->settings = NULL;
435
436   sprintf(buffer, "_XSETTINGS_S%d", gdk_x11_screen_get_screen_number (screen));
437   atom_names[0] = buffer;
438   atom_names[1] = "_XSETTINGS_SETTINGS";
439   atom_names[2] = "MANAGER";
440
441   XInternAtoms (client->display, atom_names, 3, False, atoms);
442
443   client->selection_atom = atoms[0];
444   client->xsettings_atom = atoms[1];
445   client->manager_atom = atoms[2];
446
447   /* Select on StructureNotify so we get MANAGER events
448    */
449   add_events (client->display, gdk_x11_window_get_xid (gdk_screen_get_root_window (screen)), StructureNotifyMask);
450
451   if (client->watch)
452     client->watch (gdk_x11_window_get_xid (gdk_screen_get_root_window (screen)), True, StructureNotifyMask,
453                    client->cb_data);
454
455   check_manager_window (client);
456
457   return client;
458 }
459
460 void
461 xsettings_client_destroy (XSettingsClient *client)
462 {
463   if (client->watch)
464     client->watch (gdk_x11_window_get_xid (gdk_screen_get_root_window (client->screen)),
465                    False, 0, client->cb_data);
466   if (client->manager_window && client->watch)
467     client->watch (client->manager_window, False, 0, client->cb_data);
468   
469   if (client->settings)
470     g_hash_table_unref (client->settings);
471   g_free (client);
472 }
473
474 const XSettingsSetting *
475 xsettings_client_get_setting (XSettingsClient   *client,
476                               const char        *name)
477 {
478   return g_hash_table_lookup (client->settings, name);
479 }
480
481 Bool
482 xsettings_client_process_event (XSettingsClient *client,
483                                 XEvent          *xev)
484 {
485   /* The checks here will not unlikely cause us to reread
486    * the properties from the manager window a number of
487    * times when the manager changes from A->B. But manager changes
488    * are going to be pretty rare.
489    */
490   if (xev->xany.window == gdk_x11_window_get_xid (gdk_screen_get_root_window (client->screen)))
491     {
492       if (xev->xany.type == ClientMessage &&
493           xev->xclient.message_type == client->manager_atom &&
494           xev->xclient.data.l[1] == client->selection_atom)
495         {
496           check_manager_window (client);
497           return True;
498         }
499     }
500   else if (xev->xany.window == client->manager_window)
501     {
502       if (xev->xany.type == DestroyNotify)
503         {
504           check_manager_window (client);
505           /* let GDK do its cleanup */
506           return False; 
507         }
508       else if (xev->xany.type == PropertyNotify)
509         {
510           read_settings (client);
511           return True;
512         }
513     }
514   
515   return False;
516 }
517
518 int
519 xsettings_setting_equal (XSettingsSetting *setting_a,
520                          XSettingsSetting *setting_b)
521 {
522   if (setting_a->type != setting_b->type)
523     return 0;
524
525   switch (setting_a->type)
526     {
527     case XSETTINGS_TYPE_INT:
528       return setting_a->data.v_int == setting_b->data.v_int;
529     case XSETTINGS_TYPE_COLOR:
530       return (setting_a->data.v_color.red == setting_b->data.v_color.red &&
531               setting_a->data.v_color.green == setting_b->data.v_color.green &&
532               setting_a->data.v_color.blue == setting_b->data.v_color.blue &&
533               setting_a->data.v_color.alpha == setting_b->data.v_color.alpha);
534     case XSETTINGS_TYPE_STRING:
535       return strcmp (setting_a->data.v_string, setting_b->data.v_string) == 0;
536     }
537
538   return 0;
539 }
540
541 void
542 xsettings_setting_free (XSettingsSetting *setting)
543 {
544   if (setting->type == XSETTINGS_TYPE_STRING)
545     g_free (setting->data.v_string);
546
547   g_free (setting);
548 }
549