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