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