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/gdkx11window.h>
32 #include <gdk/x11/gdkprivate-x11.h>
33 #include <gdk/x11/gdkscreen-x11.h>
34 
35 #include <gdkinternals.h>
36 
37 #include <string.h>
38 
39 #include <X11/Xlib.h>
40 #include <X11/Xmd.h>		/* For CARD16 */
41 
42 #include "gdksettings.c"
43 
44 /* Types of settings possible. Enum values correspond to
45  * protocol values.
46  */
47 typedef enum
48 {
49   XSETTINGS_TYPE_INT     = 0,
50   XSETTINGS_TYPE_STRING  = 1,
51   XSETTINGS_TYPE_COLOR   = 2
52 } XSettingsType;
53 
54 typedef struct _XSettingsBuffer  XSettingsBuffer;
55 
56 struct _XSettingsBuffer
57 {
58   char byte_order;
59   size_t len;
60   unsigned char *data;
61   unsigned char *pos;
62 };
63 
64 static void
gdk_xsettings_notify(GdkX11Screen * x11_screen,const char * name,GdkSettingAction action)65 gdk_xsettings_notify (GdkX11Screen     *x11_screen,
66                       const char       *name,
67 		      GdkSettingAction  action)
68 {
69   GdkEvent new_event;
70 
71   if (!g_str_has_prefix (name, "gtk-"))
72     return;
73 
74   new_event.type = GDK_SETTING;
75   new_event.setting.window = gdk_screen_get_root_window (GDK_SCREEN (x11_screen));
76   new_event.setting.send_event = FALSE;
77   new_event.setting.action = action;
78   new_event.setting.name = (char*) name;
79 
80   gdk_event_put (&new_event);
81 }
82 
83 static gboolean
value_equal(const GValue * value_a,const GValue * value_b)84 value_equal (const GValue *value_a,
85              const GValue *value_b)
86 {
87   if (G_VALUE_TYPE (value_a) != G_VALUE_TYPE (value_b))
88     return FALSE;
89 
90   switch (G_VALUE_TYPE (value_a))
91     {
92     case G_TYPE_INT:
93       return g_value_get_int (value_a) == g_value_get_int (value_b);
94     case XSETTINGS_TYPE_COLOR:
95       return gdk_rgba_equal (g_value_get_boxed (value_a), g_value_get_boxed (value_b));
96     case G_TYPE_STRING:
97       return g_str_equal (g_value_get_string (value_a), g_value_get_string (value_b));
98     default:
99       g_warning ("unable to compare values of type %s", g_type_name (G_VALUE_TYPE (value_a)));
100       return FALSE;
101     }
102 }
103 
104 static void
notify_changes(GdkX11Screen * x11_screen,GHashTable * old_list)105 notify_changes (GdkX11Screen *x11_screen,
106 		GHashTable   *old_list)
107 {
108   GHashTableIter iter;
109   GValue *setting, *old_setting;
110   const char *name;
111 
112   if (x11_screen->xsettings != NULL)
113     {
114       g_hash_table_iter_init (&iter, x11_screen->xsettings);
115       while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &setting))
116 	{
117 	  old_setting = old_list ? g_hash_table_lookup (old_list, name) : NULL;
118 
119 	  if (old_setting == NULL)
120 	    gdk_xsettings_notify (x11_screen, name, GDK_SETTING_ACTION_NEW);
121 	  else if (!value_equal (setting, old_setting))
122 	    gdk_xsettings_notify (x11_screen, name, GDK_SETTING_ACTION_CHANGED);
123 
124 	  /* remove setting from old_list */
125 	  if (old_setting != NULL)
126 	    g_hash_table_remove (old_list, name);
127 	}
128     }
129 
130   if (old_list != NULL)
131     {
132       /* old_list now contains only deleted settings */
133       g_hash_table_iter_init (&iter, old_list);
134       while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer*) &old_setting))
135 	gdk_xsettings_notify (x11_screen, name, GDK_SETTING_ACTION_DELETED);
136     }
137 }
138 
139 #define BYTES_LEFT(buffer) ((buffer)->data + (buffer)->len - (buffer)->pos)
140 
141 #define return_if_fail_bytes(buffer, n_bytes) G_STMT_START{ \
142   if (BYTES_LEFT (buffer) < (n_bytes)) \
143     { \
144       g_warning ("Invalid XSETTINGS property (read off end: Expected %u bytes, only %ld left", \
145                  (n_bytes), BYTES_LEFT (buffer)); \
146       return FALSE; \
147     } \
148 }G_STMT_END
149 
150 static gboolean
fetch_card16(XSettingsBuffer * buffer,CARD16 * result)151 fetch_card16 (XSettingsBuffer *buffer,
152 	      CARD16          *result)
153 {
154   CARD16 x;
155 
156   return_if_fail_bytes (buffer, 2);
157 
158   x = *(CARD16 *)buffer->pos;
159   buffer->pos += 2;
160 
161   if (buffer->byte_order == MSBFirst)
162     *result = GUINT16_FROM_BE (x);
163   else
164     *result = GUINT16_FROM_LE (x);
165 
166   return TRUE;
167 }
168 
169 static gboolean
fetch_ushort(XSettingsBuffer * buffer,unsigned short * result)170 fetch_ushort (XSettingsBuffer *buffer,
171 	      unsigned short  *result)
172 {
173   CARD16 x;
174   gboolean r;
175 
176   r = fetch_card16 (buffer, &x);
177   if (r)
178     *result = x;
179 
180   return r;
181 }
182 
183 static gboolean
fetch_card32(XSettingsBuffer * buffer,CARD32 * result)184 fetch_card32 (XSettingsBuffer *buffer,
185 	      CARD32          *result)
186 {
187   CARD32 x;
188 
189   return_if_fail_bytes (buffer, 4);
190 
191   x = *(CARD32 *)buffer->pos;
192   buffer->pos += 4;
193 
194   if (buffer->byte_order == MSBFirst)
195     *result = GUINT32_FROM_BE (x);
196   else
197     *result = GUINT32_FROM_LE (x);
198 
199   return TRUE;
200 }
201 
202 static gboolean
fetch_card8(XSettingsBuffer * buffer,CARD8 * result)203 fetch_card8 (XSettingsBuffer *buffer,
204 	     CARD8           *result)
205 {
206   return_if_fail_bytes (buffer, 1);
207 
208   *result = *(CARD8 *)buffer->pos;
209   buffer->pos += 1;
210 
211   return TRUE;
212 }
213 
214 #define XSETTINGS_PAD(n,m) ((n + m - 1) & (~(m-1)))
215 
216 static gboolean
fetch_string(XSettingsBuffer * buffer,guint length,char ** result)217 fetch_string (XSettingsBuffer  *buffer,
218               guint             length,
219               char            **result)
220 {
221   guint pad_len;
222 
223   pad_len = XSETTINGS_PAD (length, 4);
224   if (pad_len < length) /* guard against overflow */
225     {
226       g_warning ("Invalid XSETTINGS property (overflow in string length)");
227       return FALSE;
228     }
229 
230   return_if_fail_bytes (buffer, pad_len);
231 
232   *result = g_strndup ((char *) buffer->pos, length);
233   buffer->pos += pad_len;
234 
235   return TRUE;
236 }
237 
238 static void
free_value(gpointer data)239 free_value (gpointer data)
240 {
241   GValue *value = data;
242 
243   g_value_unset (value);
244   g_free (value);
245 }
246 
247 static GHashTable *
parse_settings(unsigned char * data,size_t len)248 parse_settings (unsigned char *data,
249 		size_t         len)
250 {
251   XSettingsBuffer buffer;
252   GHashTable *settings = NULL;
253   CARD32 serial;
254   CARD32 n_entries;
255   CARD32 i;
256   GValue *value = NULL;
257   char *x_name = NULL;
258   const char *gdk_name;
259 
260   buffer.pos = buffer.data = data;
261   buffer.len = len;
262 
263   if (!fetch_card8 (&buffer, (unsigned char *)&buffer.byte_order))
264     goto out;
265 
266   if (buffer.byte_order != MSBFirst &&
267       buffer.byte_order != LSBFirst)
268     {
269       g_warning ("Invalid XSETTINGS property (unknown byte order %u)", buffer.byte_order);
270       goto out;
271     }
272 
273   buffer.pos += 3;
274 
275   if (!fetch_card32 (&buffer, &serial) ||
276       !fetch_card32 (&buffer, &n_entries))
277     goto out;
278 
279   GDK_NOTE(SETTINGS, g_message ("reading %u settings (serial %u byte order %u)", n_entries, serial, buffer.byte_order));
280 
281   for (i = 0; i < n_entries; i++)
282     {
283       CARD8 type;
284       CARD16 name_len;
285       CARD32 v_int;
286 
287       if (!fetch_card8 (&buffer, &type))
288 	goto out;
289 
290       buffer.pos += 1;
291 
292       if (!fetch_card16 (&buffer, &name_len))
293 	goto out;
294 
295       if (!fetch_string (&buffer, name_len, &x_name) ||
296           /* last change serial (we ignore it) */
297           !fetch_card32 (&buffer, &v_int))
298 	goto out;
299 
300       switch (type)
301 	{
302 	case XSETTINGS_TYPE_INT:
303 	  if (!fetch_card32 (&buffer, &v_int))
304 	    goto out;
305 
306           value = g_new0 (GValue, 1);
307           g_value_init (value, G_TYPE_INT);
308           g_value_set_int (value, (gint32) v_int);
309 
310           GDK_NOTE(SETTINGS, g_message ("  %s = %d", x_name, (gint32) v_int));
311 	  break;
312 	case XSETTINGS_TYPE_STRING:
313           {
314             char *s;
315 
316             if (!fetch_card32 (&buffer, &v_int) ||
317                 !fetch_string (&buffer, v_int, &s))
318               goto out;
319 
320             value = g_new0 (GValue, 1);
321             g_value_init (value, G_TYPE_STRING);
322             g_value_take_string (value, s);
323 
324             GDK_NOTE(SETTINGS, g_message ("  %s = \"%s\"", x_name, s));
325           }
326 	  break;
327 	case XSETTINGS_TYPE_COLOR:
328           {
329             unsigned short red, green, blue, alpha;
330             GdkRGBA rgba;
331 
332             if (!fetch_ushort (&buffer, &red) ||
333                 !fetch_ushort (&buffer, &green) ||
334                 !fetch_ushort (&buffer, &blue) ||
335                 !fetch_ushort (&buffer, &alpha))
336               goto out;
337 
338             rgba.red = red / 65535.0;
339             rgba.green = green / 65535.0;
340             rgba.blue = blue / 65535.0;
341             rgba.alpha = alpha / 65535.0;
342 
343             value = g_new0 (GValue, 1);
344             g_value_init (value, G_TYPE_STRING);
345             g_value_set_boxed (value, &rgba);
346 
347             GDK_NOTE(SETTINGS, g_message ("  %s = #%02X%02X%02X%02X", x_name, alpha,red, green, blue));
348           }
349 	  break;
350 	default:
351 	  /* Quietly ignore unknown types */
352           GDK_NOTE(SETTINGS, g_message ("  %s = ignored (unknown type %u)", x_name, type));
353 	  break;
354 	}
355 
356       gdk_name = gdk_from_xsettings_name (x_name);
357       g_free (x_name);
358       x_name = NULL;
359 
360       if (gdk_name == NULL)
361         {
362           GDK_NOTE(SETTINGS, g_message ("    ==> unknown to GTK"));
363           free_value (value);
364         }
365       else
366         {
367           GDK_NOTE(SETTINGS, g_message ("    ==> storing as '%s'", gdk_name));
368 
369           if (settings == NULL)
370             settings = g_hash_table_new_full (g_str_hash, g_str_equal,
371                                               NULL,
372                                               free_value);
373 
374           if (g_hash_table_lookup (settings, gdk_name) != NULL)
375             {
376               g_warning ("Invalid XSETTINGS property (Duplicate entry for '%s')", gdk_name);
377               goto out;
378             }
379 
380           g_hash_table_insert (settings, (gpointer) gdk_name, value);
381         }
382 
383       value = NULL;
384     }
385 
386   return settings;
387 
388  out:
389 
390   if (value)
391     free_value (value);
392 
393   if (settings)
394     g_hash_table_unref (settings);
395 
396   g_free (x_name);
397 
398   return NULL;
399 }
400 
401 static void
read_settings(GdkX11Screen * x11_screen,gboolean do_notify)402 read_settings (GdkX11Screen *x11_screen,
403                gboolean      do_notify)
404 {
405   GdkScreen *screen = GDK_SCREEN (x11_screen);
406 
407   Atom type;
408   int format;
409   unsigned long n_items;
410   unsigned long bytes_after;
411   unsigned char *data;
412   int result;
413 
414   GHashTable *old_list = x11_screen->xsettings;
415   GValue value = G_VALUE_INIT;
416   GValue *setting, *copy;
417 
418   x11_screen->xsettings = NULL;
419 
420   if (x11_screen->xsettings_manager_window)
421     {
422       GdkDisplay *display = x11_screen->display;
423       Atom xsettings_atom = gdk_x11_get_xatom_by_name_for_display (display, "_XSETTINGS_SETTINGS");
424 
425       gdk_x11_display_error_trap_push (display);
426       result = XGetWindowProperty (gdk_x11_display_get_xdisplay (display),
427                                    gdk_x11_window_get_xid (x11_screen->xsettings_manager_window),
428 				   xsettings_atom, 0, LONG_MAX,
429 				   False, xsettings_atom,
430 				   &type, &format, &n_items, &bytes_after, &data);
431       gdk_x11_display_error_trap_pop_ignored (display);
432 
433       if (result == Success && type != None)
434 	{
435 	  if (type != xsettings_atom)
436 	    {
437 	      g_warning ("Invalid type for XSETTINGS property: %s", gdk_x11_get_xatom_name_for_display (display, type));
438 	    }
439 	  else if (format != 8)
440 	    {
441 	      g_warning ("Invalid format for XSETTINGS property: %d", format);
442 	    }
443 	  else
444 	    x11_screen->xsettings = parse_settings (data, n_items);
445 
446 	  XFree (data);
447 	}
448     }
449 
450   /* Since we support scaling we look at the specific Gdk/UnscaledDPI
451      setting if it exists and use that instead of Xft/DPI if it is set */
452   if (x11_screen->xsettings && !x11_screen->fixed_window_scale)
453     {
454       setting = g_hash_table_lookup (x11_screen->xsettings, "gdk-unscaled-dpi");
455       if (setting)
456 	{
457 	  copy = g_new0 (GValue, 1);
458 	  g_value_init (copy, G_VALUE_TYPE (setting));
459 	  g_value_copy (setting, copy);
460 	  g_hash_table_insert (x11_screen->xsettings,
461 			       "gtk-xft-dpi", copy);
462 	}
463     }
464 
465   if (do_notify)
466     notify_changes (x11_screen, old_list);
467   if (old_list)
468     g_hash_table_unref (old_list);
469 
470   g_value_init (&value, G_TYPE_INT);
471 
472   if (!screen->resolution_set)
473     {
474       /* This code is duplicated with gtksettings.c:settings_update_resolution().
475        * The update of the screen resolution needs to happen immediately when
476        * gdk_x11_display_set_window_scale() is called, and not wait for events
477        * to be processed, so we can't always handling it in gtksettings.c.
478        * But we can't always handle it here because the DPI can be set through
479        * GtkSettings, which we don't have access to.
480        */
481       int dpi_int = 0;
482       double dpi;
483       const char *scale_env;
484       double scale;
485 
486       if (gdk_screen_get_setting (GDK_SCREEN (x11_screen),
487                                   "gtk-xft-dpi", &value))
488         dpi_int = g_value_get_int (&value);
489 
490       if (dpi_int > 0)
491         dpi = dpi_int / 1024.;
492       else
493         dpi = -1.;
494 
495       scale_env = g_getenv ("GDK_DPI_SCALE");
496       if (scale_env)
497         {
498           scale = g_ascii_strtod (scale_env, NULL);
499           if (scale != 0 && dpi > 0)
500             dpi *= scale;
501         }
502 
503       _gdk_screen_set_resolution (screen, dpi);
504     }
505 
506   if (!x11_screen->fixed_window_scale &&
507       gdk_screen_get_setting (GDK_SCREEN (x11_screen),
508 			      "gdk-window-scaling-factor", &value))
509     _gdk_x11_screen_set_window_scale (x11_screen,
510 				      g_value_get_int (&value));
511 }
512 
513 static Atom
get_selection_atom(GdkX11Screen * x11_screen)514 get_selection_atom (GdkX11Screen *x11_screen)
515 {
516   return _gdk_x11_get_xatom_for_display_printf (x11_screen->display, "_XSETTINGS_S%d", x11_screen->screen_num);
517 }
518 
519 static GdkFilterReturn
520 gdk_xsettings_manager_window_filter (GdkXEvent *xevent,
521                                      GdkEvent  *event,
522                                      gpointer   data);
523 
524 static void
check_manager_window(GdkX11Screen * x11_screen,gboolean notify_changes)525 check_manager_window (GdkX11Screen *x11_screen,
526                       gboolean      notify_changes)
527 {
528   GdkDisplay *display;
529   Display *xdisplay;
530   Window manager_window_xid;
531 
532   display = x11_screen->display;
533   xdisplay = gdk_x11_display_get_xdisplay (display);
534 
535   if (x11_screen->xsettings_manager_window)
536     {
537       gdk_window_remove_filter (x11_screen->xsettings_manager_window, gdk_xsettings_manager_window_filter, x11_screen);
538       g_object_unref (x11_screen->xsettings_manager_window);
539     }
540 
541   gdk_x11_display_grab (display);
542 
543   manager_window_xid = XGetSelectionOwner (xdisplay, get_selection_atom (x11_screen));
544   x11_screen->xsettings_manager_window = gdk_x11_window_foreign_new_for_display (display,
545                                                                    manager_window_xid);
546   /* XXX: Can't use gdk_window_set_events() here because the first call to this
547    * function happens too early in gdk_init() */
548   if (x11_screen->xsettings_manager_window)
549     XSelectInput (xdisplay,
550                   gdk_x11_window_get_xid (x11_screen->xsettings_manager_window),
551                   PropertyChangeMask | StructureNotifyMask);
552 
553   gdk_x11_display_ungrab (display);
554 
555   gdk_display_flush (display);
556 
557   if (x11_screen->xsettings_manager_window)
558     {
559       gdk_window_add_filter (x11_screen->xsettings_manager_window, gdk_xsettings_manager_window_filter, x11_screen);
560     }
561 
562   read_settings (x11_screen, notify_changes);
563 }
564 
565 static GdkFilterReturn
gdk_xsettings_root_window_filter(GdkXEvent * xevent,GdkEvent * event,gpointer data)566 gdk_xsettings_root_window_filter (GdkXEvent *xevent,
567                                   GdkEvent  *event,
568                                   gpointer   data)
569 {
570   GdkX11Screen *x11_screen = data;
571   GdkDisplay *display = x11_screen->display;
572   XEvent *xev = xevent;
573 
574   /* The checks here will not unlikely cause us to reread
575    * the properties from the manager window a number of
576    * times when the manager changes from A->B. But manager changes
577    * are going to be pretty rare.
578    */
579   if (xev->xany.type == ClientMessage &&
580       xev->xclient.message_type == gdk_x11_get_xatom_by_name_for_display (display, "MANAGER") &&
581       xev->xclient.data.l[1] == get_selection_atom (x11_screen))
582     {
583       check_manager_window (x11_screen, TRUE);
584       return GDK_FILTER_REMOVE;
585     }
586 
587   return GDK_FILTER_CONTINUE;
588 }
589 
590 static GdkFilterReturn
gdk_xsettings_manager_window_filter(GdkXEvent * xevent,GdkEvent * event,gpointer data)591 gdk_xsettings_manager_window_filter (GdkXEvent *xevent,
592                                      GdkEvent  *event,
593                                      gpointer   data)
594 {
595   GdkX11Screen *x11_screen = data;
596   XEvent *xev = xevent;
597 
598   if (xev->xany.type == DestroyNotify)
599     {
600       check_manager_window (x11_screen, TRUE);
601       /* let GDK do its cleanup */
602       return GDK_FILTER_CONTINUE;
603     }
604   else if (xev->xany.type == PropertyNotify)
605     {
606       read_settings (x11_screen, TRUE);
607       return GDK_FILTER_REMOVE;
608     }
609 
610   return GDK_FILTER_CONTINUE;;
611 }
612 
613 void
_gdk_x11_xsettings_init(GdkX11Screen * x11_screen)614 _gdk_x11_xsettings_init (GdkX11Screen *x11_screen)
615 {
616   gdk_window_add_filter (gdk_screen_get_root_window (GDK_SCREEN (x11_screen)), gdk_xsettings_root_window_filter, x11_screen);
617 
618   check_manager_window (x11_screen, FALSE);
619 }
620 
621 void
_gdk_x11_settings_force_reread(GdkX11Screen * x11_screen)622 _gdk_x11_settings_force_reread (GdkX11Screen *x11_screen)
623 {
624   read_settings (x11_screen, TRUE);
625 }
626 
627 void
_gdk_x11_xsettings_finish(GdkX11Screen * x11_screen)628 _gdk_x11_xsettings_finish (GdkX11Screen *x11_screen)
629 {
630   gdk_window_remove_filter (gdk_screen_get_root_window (GDK_SCREEN (x11_screen)), gdk_xsettings_root_window_filter, x11_screen);
631   if (x11_screen->xsettings_manager_window)
632     {
633       gdk_window_remove_filter (x11_screen->xsettings_manager_window, gdk_xsettings_manager_window_filter, x11_screen);
634       g_object_unref (x11_screen->xsettings_manager_window);
635       x11_screen->xsettings_manager_window = NULL;
636     }
637 
638   if (x11_screen->xsettings)
639     {
640       g_hash_table_unref (x11_screen->xsettings);
641       x11_screen->xsettings = NULL;
642     }
643 }
644 
645