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/gdkx11surface.h>
32 #include <gdk/x11/gdkprivate-x11.h>
33 #include <gdk/x11/gdkdisplay-x11.h>
34 #include <gdk/x11/gdkscreen-x11.h>
35 
36 #include <gdkinternals.h>
37 
38 #include <string.h>
39 
40 #include <X11/Xlib.h>
41 #include <X11/Xmd.h>		/* For CARD16 */
42 
43 typedef enum
44 {
45   GDK_SETTING_ACTION_NEW,
46   GDK_SETTING_ACTION_CHANGED,
47   GDK_SETTING_ACTION_DELETED
48 } GdkSettingAction;
49 
50 #include "gdksettings.c"
51 
52 /* Types of settings possible. Enum values correspond to
53  * protocol values.
54  */
55 typedef enum
56 {
57   XSETTINGS_TYPE_INT     = 0,
58   XSETTINGS_TYPE_STRING  = 1,
59   XSETTINGS_TYPE_COLOR   = 2
60 } XSettingsType;
61 
62 typedef struct _XSettingsBuffer  XSettingsBuffer;
63 
64 struct _XSettingsBuffer
65 {
66   char byte_order;
67   size_t len;
68   unsigned char *data;
69   unsigned char *pos;
70 };
71 
72 static void
gdk_xsettings_notify(GdkX11Screen * x11_screen,const char * name,GdkSettingAction action)73 gdk_xsettings_notify (GdkX11Screen     *x11_screen,
74                       const char       *name,
75 		      GdkSettingAction  action)
76 {
77   gdk_display_setting_changed (x11_screen->display, name);
78 }
79 
80 static gboolean
value_equal(const GValue * value_a,const GValue * value_b)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
notify_changes(GdkX11Screen * x11_screen,GHashTable * old_list)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 %"G_GSIZE_FORMAT" left", \
142                  (n_bytes), BYTES_LEFT (buffer)); \
143       return FALSE; \
144     } \
145 }G_STMT_END
146 
147 static gboolean
fetch_card16(XSettingsBuffer * buffer,CARD16 * result)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
fetch_ushort(XSettingsBuffer * buffer,unsigned short * result)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
fetch_card32(XSettingsBuffer * buffer,CARD32 * result)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
fetch_card8(XSettingsBuffer * buffer,CARD8 * result)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
fetch_string(XSettingsBuffer * buffer,guint length,char ** result)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
free_value(gpointer data)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 *
parse_settings(unsigned char * data,size_t len)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_message ("reading %lu settings (serial %lu byte order %u)",
277                                  (unsigned long)n_entries, (unsigned long)serial, buffer.byte_order));
278 
279   for (i = 0; i < n_entries; i++)
280     {
281       CARD8 type;
282       CARD16 name_len;
283       CARD32 v_int;
284 
285       if (!fetch_card8 (&buffer, &type))
286 	goto out;
287 
288       buffer.pos += 1;
289 
290       if (!fetch_card16 (&buffer, &name_len))
291 	goto out;
292 
293       if (!fetch_string (&buffer, name_len, &x_name) ||
294           /* last change serial (we ignore it) */
295           !fetch_card32 (&buffer, &v_int))
296 	goto out;
297 
298       switch (type)
299 	{
300 	case XSETTINGS_TYPE_INT:
301 	  if (!fetch_card32 (&buffer, &v_int))
302 	    goto out;
303 
304           value = g_new0 (GValue, 1);
305           g_value_init (value, G_TYPE_INT);
306           g_value_set_int (value, (gint32) v_int);
307 
308           GDK_NOTE (SETTINGS, g_message ("  %s = %d", x_name, (gint32) v_int));
309 	  break;
310 	case XSETTINGS_TYPE_STRING:
311           {
312             char *s;
313 
314             if (!fetch_card32 (&buffer, &v_int) ||
315                 !fetch_string (&buffer, v_int, &s))
316               goto out;
317 
318             value = g_new0 (GValue, 1);
319             g_value_init (value, G_TYPE_STRING);
320             g_value_take_string (value, s);
321 
322             GDK_NOTE (SETTINGS, g_message ("  %s = \"%s\"", x_name, s));
323           }
324 	  break;
325 	case XSETTINGS_TYPE_COLOR:
326           {
327             unsigned short red, green, blue, alpha;
328             GdkRGBA rgba;
329 
330             if (!fetch_ushort (&buffer, &red) ||
331                 !fetch_ushort (&buffer, &green) ||
332                 !fetch_ushort (&buffer, &blue) ||
333                 !fetch_ushort (&buffer, &alpha))
334               goto out;
335 
336             rgba.red = red / 65535.0;
337             rgba.green = green / 65535.0;
338             rgba.blue = blue / 65535.0;
339             rgba.alpha = alpha / 65535.0;
340 
341             value = g_new0 (GValue, 1);
342             g_value_init (value, GDK_TYPE_RGBA);
343             g_value_set_boxed (value, &rgba);
344 
345             GDK_NOTE (SETTINGS, g_message ("  %s = #%02X%02X%02X%02X", x_name, alpha,red, green, blue));
346           }
347 	  break;
348 	default:
349 	  /* Quietly ignore unknown types */
350           GDK_NOTE (SETTINGS, g_message ("  %s = ignored (unknown type %u)", x_name, type));
351 	  break;
352 	}
353 
354       gdk_name = gdk_from_xsettings_name (x_name);
355       g_free (x_name);
356       x_name = NULL;
357 
358       if (gdk_name == NULL)
359         {
360           GDK_NOTE (SETTINGS, g_message ("    ==> unknown to GTK"));
361           free_value (value);
362         }
363       else
364         {
365           GDK_NOTE (SETTINGS, g_message ("    ==> storing as '%s'", gdk_name));
366 
367           if (settings == NULL)
368             settings = g_hash_table_new_full (g_str_hash, g_str_equal,
369                                               NULL,
370                                               free_value);
371 
372           if (g_hash_table_lookup (settings, gdk_name) != NULL)
373             {
374               g_warning ("Invalid XSETTINGS property (Duplicate entry for '%s')", gdk_name);
375               goto out;
376             }
377 
378           g_hash_table_insert (settings, (gpointer) gdk_name, value);
379         }
380 
381       value = NULL;
382     }
383 
384   return settings;
385 
386  out:
387 
388   if (value)
389     free_value (value);
390 
391   if (settings)
392     g_hash_table_unref (settings);
393 
394   g_free (x_name);
395 
396   return NULL;
397 }
398 
399 static void
read_settings(GdkX11Screen * x11_screen,gboolean do_notify)400 read_settings (GdkX11Screen *x11_screen,
401                gboolean      do_notify)
402 {
403   GdkDisplay *display = x11_screen->display;
404 
405   Atom type;
406   int format;
407   unsigned long n_items;
408   unsigned long bytes_after;
409   unsigned char *data;
410   int result;
411 
412   GHashTable *old_list = x11_screen->xsettings;
413   GValue value = G_VALUE_INIT;
414   GValue *setting, *copy;
415 
416   x11_screen->xsettings = NULL;
417 
418   if (x11_screen->xsettings_manager_window != 0)
419     {
420       Atom xsettings_atom = gdk_x11_get_xatom_by_name_for_display (display, "_XSETTINGS_SETTINGS");
421 
422       gdk_x11_display_error_trap_push (display);
423       result = XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
424                                    x11_screen->xsettings_manager_window,
425 				   xsettings_atom, 0, LONG_MAX,
426 				   False, xsettings_atom,
427 				   &type, &format, &n_items, &bytes_after, &data);
428       gdk_x11_display_error_trap_pop_ignored (display);
429 
430       if (result == Success && type != None)
431 	{
432 	  if (type != xsettings_atom)
433 	    {
434 	      g_warning ("Invalid type for XSETTINGS property: %s", gdk_x11_get_xatom_name_for_display (display, type));
435 	    }
436 	  else if (format != 8)
437 	    {
438 	      g_warning ("Invalid format for XSETTINGS property: %d", format);
439 	    }
440 	  else
441 	    x11_screen->xsettings = parse_settings (data, n_items);
442 
443 	  XFree (data);
444 	}
445     }
446 
447   /* Since we support scaling we look at the specific Gdk/UnscaledDPI
448      setting if it exists and use that instead of Xft/DPI if it is set */
449   if (x11_screen->xsettings && !x11_screen->fixed_surface_scale)
450     {
451       setting = g_hash_table_lookup (x11_screen->xsettings, "gdk-unscaled-dpi");
452       if (setting)
453 	{
454 	  copy = g_new0 (GValue, 1);
455 	  g_value_init (copy, G_VALUE_TYPE (setting));
456 	  g_value_copy (setting, copy);
457 	  g_hash_table_insert (x11_screen->xsettings,
458 			       (gpointer) "gtk-xft-dpi", copy);
459 	}
460     }
461 
462   if (do_notify)
463     notify_changes (x11_screen, old_list);
464   if (old_list)
465     g_hash_table_unref (old_list);
466 
467   g_value_init (&value, G_TYPE_INT);
468 
469   if (!x11_screen->fixed_surface_scale &&
470       gdk_display_get_setting (display, "gdk-window-scaling-factor", &value))
471     _gdk_x11_screen_set_surface_scale (x11_screen, g_value_get_int (&value));
472 }
473 
474 static Atom
get_selection_atom(GdkX11Screen * x11_screen)475 get_selection_atom (GdkX11Screen *x11_screen)
476 {
477   return _gdk_x11_get_xatom_for_display_printf (x11_screen->display, "_XSETTINGS_S%d", x11_screen->screen_num);
478 }
479 
480 static void
check_manager_window(GdkX11Screen * x11_screen,gboolean notify_changes)481 check_manager_window (GdkX11Screen *x11_screen,
482                       gboolean      notify_changes)
483 {
484   GdkDisplay *display;
485   Display *xdisplay;
486 
487   display = x11_screen->display;
488   xdisplay = gdk_x11_display_get_xdisplay (display);
489 
490   gdk_x11_display_grab (display);
491 
492   if (!GDK_DISPLAY_DEBUG_CHECK (display, DEFAULT_SETTINGS))
493     x11_screen->xsettings_manager_window = XGetSelectionOwner (xdisplay, get_selection_atom (x11_screen));
494 
495   if (x11_screen->xsettings_manager_window != 0)
496     XSelectInput (xdisplay,
497                   x11_screen->xsettings_manager_window,
498                   PropertyChangeMask | StructureNotifyMask);
499 
500   gdk_x11_display_ungrab (display);
501 
502   gdk_display_flush (display);
503 
504   read_settings (x11_screen, notify_changes);
505 }
506 
507 GdkFilterReturn
gdk_xsettings_root_window_filter(const XEvent * xev,gpointer data)508 gdk_xsettings_root_window_filter (const XEvent *xev,
509                                   gpointer      data)
510 {
511   GdkX11Screen *x11_screen = data;
512   GdkDisplay *display = x11_screen->display;
513 
514   /* The checks here will not unlikely cause us to reread
515    * the properties from the manager window a number of
516    * times when the manager changes from A->B. But manager changes
517    * are going to be pretty rare.
518    */
519   if (xev->xany.type == ClientMessage &&
520       xev->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "MANAGER") &&
521       xev->xclient.data.l[1] == get_selection_atom (x11_screen))
522     {
523       check_manager_window (x11_screen, TRUE);
524       return GDK_FILTER_REMOVE;
525     }
526 
527   return GDK_FILTER_CONTINUE;
528 }
529 
530 GdkFilterReturn
gdk_xsettings_manager_window_filter(const XEvent * xev,gpointer data)531 gdk_xsettings_manager_window_filter (const XEvent *xev,
532                                      gpointer      data)
533 {
534   GdkX11Screen *x11_screen = data;
535 
536   if (xev->xany.type == DestroyNotify)
537     {
538       check_manager_window (x11_screen, TRUE);
539       /* let GDK do its cleanup */
540       return GDK_FILTER_CONTINUE;
541     }
542   else if (xev->xany.type == PropertyNotify)
543     {
544       read_settings (x11_screen, TRUE);
545       return GDK_FILTER_REMOVE;
546     }
547 
548   return GDK_FILTER_CONTINUE;
549 }
550 
551 void
_gdk_x11_xsettings_init(GdkX11Screen * x11_screen)552 _gdk_x11_xsettings_init (GdkX11Screen *x11_screen)
553 {
554   check_manager_window (x11_screen, FALSE);
555 }
556 
557 void
_gdk_x11_settings_force_reread(GdkX11Screen * x11_screen)558 _gdk_x11_settings_force_reread (GdkX11Screen *x11_screen)
559 {
560   read_settings (x11_screen, TRUE);
561 }
562 
563 void
_gdk_x11_xsettings_finish(GdkX11Screen * x11_screen)564 _gdk_x11_xsettings_finish (GdkX11Screen *x11_screen)
565 {
566   if (x11_screen->xsettings_manager_window)
567     x11_screen->xsettings_manager_window = 0;
568 
569   if (x11_screen->xsettings)
570     {
571       g_hash_table_unref (x11_screen->xsettings);
572       x11_screen->xsettings = NULL;
573     }
574 }
575