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