1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2007 Rodrigo Moya
4  * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
5  * Copyright (C) 2012-2021 MATE Developers
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "config.h"
24 
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <time.h>
33 
34 #include <X11/Xatom.h>
35 
36 #include <glib.h>
37 #include <glib/gi18n.h>
38 #include <gdk/gdk.h>
39 #include <gdk/gdkx.h>
40 #include <gtk/gtk.h>
41 #include <gio/gio.h>
42 
43 #include "mate-settings-profile.h"
44 #include "msd-xsettings-manager.h"
45 #include "xsettings-manager.h"
46 #include "fontconfig-monitor.h"
47 #include "wm-common.h"
48 
49 #define MOUSE_SCHEMA          "org.mate.peripherals-mouse"
50 #define INTERFACE_SCHEMA      "org.mate.interface"
51 #define SOUND_SCHEMA          "org.mate.sound"
52 
53 #define CURSOR_THEME_KEY      "cursor-theme"
54 #define CURSOR_SIZE_KEY       "cursor-size"
55 #define SCALING_FACTOR_KEY    "window-scaling-factor"
56 #define SCALING_FACTOR_QT_KEY "window-scaling-factor-qt-sync"
57 
58 #define FONT_RENDER_SCHEMA    "org.mate.font-rendering"
59 #define FONT_ANTIALIASING_KEY "antialiasing"
60 #define FONT_HINTING_KEY      "hinting"
61 #define FONT_RGBA_ORDER_KEY   "rgba-order"
62 #define FONT_DPI_KEY          "dpi"
63 
64 /* X servers sometimes lie about the screen's physical dimensions, so we cannot
65  * compute an accurate DPI value.  When this happens, the user gets fonts that
66  * are too huge or too tiny.  So, we see what the server returns:  if it reports
67  * something outside of the range [DPI_LOW_REASONABLE_VALUE,
68  * DPI_HIGH_REASONABLE_VALUE], then we assume that it is lying and we use
69  * DPI_FALLBACK instead.
70  *
71  * See get_dpi_from_gsettings_or_server() below, and also
72  * https://bugzilla.novell.com/show_bug.cgi?id=217790
73  */
74 #define DPI_FALLBACK 96
75 #define DPI_LOW_REASONABLE_VALUE 50
76 #define DPI_HIGH_REASONABLE_VALUE 500
77 
78 /* The minimum resolution at which we turn on a window-scale of 2 */
79 #define HIDPI_LIMIT (DPI_FALLBACK * 2)
80 
81 /* The minimum screen height at which we turn on a window-scale of 2;
82  * below this there just isn't enough vertical real estate for GNOME
83  * apps to work, and it's better to just be tiny */
84 #define HIDPI_MIN_HEIGHT 1500
85 
86 #define GPOINTER_TO_BOOLEAN(i) ((gboolean) ((GPOINTER_TO_INT(i) == 2) ? TRUE : FALSE))
87 #define GBOOLEAN_TO_POINTER(i) (GINT_TO_POINTER ((i) ? 2 : 1))
88 
89 typedef struct _TranslationEntry TranslationEntry;
90 typedef void (* TranslationFunc) (MateXSettingsManager  *manager,
91                                   TranslationEntry      *trans,
92                                   GVariant              *value);
93 
94 struct _TranslationEntry {
95         const char     *gsettings_schema;
96         const char     *gsettings_key;
97         const char     *xsetting_name;
98 
99         TranslationFunc translate;
100 };
101 
102 struct MateXSettingsManagerPrivate
103 {
104         XSettingsManager **managers;
105         GHashTable *gsettings;
106         GSettings *gsettings_font;
107         fontconfig_monitor_handle_t *fontconfig_handle;
108         gint window_scale;
109 };
110 
111 #define MSD_XSETTINGS_ERROR msd_xsettings_error_quark ()
112 
113 enum {
114         MSD_XSETTINGS_ERROR_INIT
115 };
116 
117 static void mate_xsettings_manager_finalize (GObject *object);
118 
119 G_DEFINE_TYPE_WITH_PRIVATE (MateXSettingsManager, mate_xsettings_manager, G_TYPE_OBJECT)
120 
121 static gpointer manager_object = NULL;
122 
123 static GQuark
msd_xsettings_error_quark(void)124 msd_xsettings_error_quark (void)
125 {
126         return g_quark_from_static_string ("msd-xsettings-error-quark");
127 }
128 
129 static void
translate_bool_int(MateXSettingsManager * manager,TranslationEntry * trans,GVariant * value)130 translate_bool_int (MateXSettingsManager  *manager,
131                     TranslationEntry      *trans,
132                     GVariant              *value)
133 {
134         int i;
135 
136         for (i = 0; manager->priv->managers [i]; i++) {
137                 xsettings_manager_set_int (manager->priv->managers [i], trans->xsetting_name,
138                                            g_variant_get_boolean (value));
139         }
140 }
141 
142 static void
translate_int_int(MateXSettingsManager * manager,TranslationEntry * trans,GVariant * value)143 translate_int_int (MateXSettingsManager  *manager,
144                    TranslationEntry      *trans,
145                    GVariant              *value)
146 {
147         int i;
148 
149         for (i = 0; manager->priv->managers [i]; i++) {
150                 xsettings_manager_set_int (manager->priv->managers [i], trans->xsetting_name,
151                                            g_variant_get_int32 (value));
152         }
153 }
154 
155 static void
translate_string_string(MateXSettingsManager * manager,TranslationEntry * trans,GVariant * value)156 translate_string_string (MateXSettingsManager  *manager,
157                          TranslationEntry      *trans,
158                          GVariant              *value)
159 {
160         int i;
161 
162         for (i = 0; manager->priv->managers [i]; i++) {
163                 xsettings_manager_set_string (manager->priv->managers [i],
164                                               trans->xsetting_name,
165                                               g_variant_get_string (value, NULL));
166         }
167 }
168 
169 static void
translate_string_string_toolbar(MateXSettingsManager * manager,TranslationEntry * trans,GVariant * value)170 translate_string_string_toolbar (MateXSettingsManager  *manager,
171                                  TranslationEntry      *trans,
172                                  GVariant              *value)
173 {
174         int         i;
175         const char *tmp;
176 
177         /* This is kind of a workaround since GNOME expects the key value to be
178          * "both_horiz" and gtk+ wants the XSetting to be "both-horiz".
179          */
180         tmp = g_variant_get_string (value, NULL);
181         if (tmp && strcmp (tmp, "both_horiz") == 0) {
182                 tmp = "both-horiz";
183         }
184 
185         for (i = 0; manager->priv->managers [i]; i++) {
186                 xsettings_manager_set_string (manager->priv->managers [i],
187                                               trans->xsetting_name,
188                                               tmp);
189         }
190 }
191 
192 static TranslationEntry translations [] = {
193         { MOUSE_SCHEMA,     "double-click",           "Net/DoubleClickTime",           translate_int_int },
194         { MOUSE_SCHEMA,     "drag-threshold",         "Net/DndDragThreshold",          translate_int_int },
195         { MOUSE_SCHEMA,     "cursor-theme",           "Gtk/CursorThemeName",           translate_string_string },
196         { MOUSE_SCHEMA,     "cursor-size",            "Gtk/CursorThemeSize",           translate_int_int },
197 
198         { INTERFACE_SCHEMA, "font-name",              "Gtk/FontName",                  translate_string_string },
199         { INTERFACE_SCHEMA, "gtk-key-theme",          "Gtk/KeyThemeName",              translate_string_string },
200         { INTERFACE_SCHEMA, "toolbar-style",          "Gtk/ToolbarStyle",              translate_string_string_toolbar },
201         { INTERFACE_SCHEMA, "toolbar-icons-size",     "Gtk/ToolbarIconSize",           translate_string_string },
202         { INTERFACE_SCHEMA, "cursor-blink",           "Net/CursorBlink",               translate_bool_int },
203         { INTERFACE_SCHEMA, "cursor-blink-time",      "Net/CursorBlinkTime",           translate_int_int },
204         { INTERFACE_SCHEMA, "gtk-theme",              "Net/ThemeName",                 translate_string_string },
205         { INTERFACE_SCHEMA, "gtk-color-scheme",       "Gtk/ColorScheme",               translate_string_string },
206         { INTERFACE_SCHEMA, "gtk-im-preedit-style",   "Gtk/IMPreeditStyle",            translate_string_string },
207         { INTERFACE_SCHEMA, "gtk-im-status-style",    "Gtk/IMStatusStyle",             translate_string_string },
208         { INTERFACE_SCHEMA, "gtk-im-module",          "Gtk/IMModule",                  translate_string_string },
209         { INTERFACE_SCHEMA, "icon-theme",             "Net/IconThemeName",             translate_string_string },
210         { INTERFACE_SCHEMA, "file-chooser-backend",   "Gtk/FileChooserBackend",        translate_string_string },
211         { INTERFACE_SCHEMA, "gtk-decoration-layout",  "Gtk/DecorationLayout",          translate_string_string },
212         { INTERFACE_SCHEMA, "gtk-shell-shows-app-menu","Gtk/ShellShowsAppMenu",        translate_bool_int },
213         { INTERFACE_SCHEMA, "gtk-shell-shows-menubar","Gtk/ShellShowsMenubar",         translate_bool_int },
214         { INTERFACE_SCHEMA, "menus-have-icons",       "Gtk/MenuImages",                translate_bool_int },
215         { INTERFACE_SCHEMA, "buttons-have-icons",     "Gtk/ButtonImages",              translate_bool_int },
216         { INTERFACE_SCHEMA, "menubar-accel",          "Gtk/MenuBarAccel",              translate_string_string },
217         { INTERFACE_SCHEMA, "show-input-method-menu", "Gtk/ShowInputMethodMenu",       translate_bool_int },
218         { INTERFACE_SCHEMA, "show-unicode-menu",      "Gtk/ShowUnicodeMenu",           translate_bool_int },
219         { INTERFACE_SCHEMA, "automatic-mnemonics",    "Gtk/AutoMnemonics",             translate_bool_int },
220         {INTERFACE_SCHEMA, "gtk-enable-primary-paste", "Gtk/EnablePrimaryPaste",
221 translate_bool_int },
222         { INTERFACE_SCHEMA, "gtk-enable-animations",  "Gtk/EnableAnimations",          translate_bool_int },
223         { INTERFACE_SCHEMA, "gtk-dialogs-use-header", "Gtk/DialogsUseHeader",          translate_bool_int },
224 
225         { SOUND_SCHEMA, "theme-name",                 "Net/SoundThemeName",            translate_string_string },
226         { SOUND_SCHEMA, "event-sounds",               "Net/EnableEventSounds" ,        translate_bool_int },
227         { SOUND_SCHEMA, "input-feedback-sounds",      "Net/EnableInputFeedbackSounds", translate_bool_int }
228 };
229 
230 /* Auto-detect the most appropriate scale factor for the primary monitor.
231  * A lot of this code is shamelessly copied and adapted from Linux Mint/Cinnamon.
232  */
233 static int
get_window_scale_auto(void)234 get_window_scale_auto (void)
235 {
236         GdkDisplay   *display;
237         GdkMonitor   *monitor;
238         GdkRectangle  rect;
239         int width_mm, height_mm;
240         int monitor_scale, window_scale;
241 
242         display = gdk_display_get_default ();
243         monitor = gdk_display_get_primary_monitor (display);
244 
245         /* Use current value as the default */
246         window_scale = 1;
247 
248         gdk_monitor_get_geometry (monitor, &rect);
249         width_mm = gdk_monitor_get_width_mm (monitor);
250         height_mm = gdk_monitor_get_height_mm (monitor);
251         monitor_scale = gdk_monitor_get_scale_factor (monitor);
252 
253         if (rect.height * monitor_scale < HIDPI_MIN_HEIGHT)
254                 return 1;
255 
256         /* Some monitors/TV encode the aspect ratio (16/9 or 16/10) instead of the physical size */
257         if ((width_mm == 160 && height_mm == 90) ||
258             (width_mm == 160 && height_mm == 100) ||
259             (width_mm == 16 && height_mm == 9) ||
260             (width_mm == 16 && height_mm == 10))
261                 return 1;
262 
263         if (width_mm > 0 && height_mm > 0) {
264                 double dpi_x, dpi_y;
265 
266                 dpi_x = (double)rect.width * monitor_scale / (width_mm / 25.4);
267                 dpi_y = (double)rect.height * monitor_scale / (height_mm / 25.4);
268                 /* We don't completely trust these values so both must be high, and never pick
269                  * higher ratio than 2 automatically */
270                 if (dpi_x > HIDPI_LIMIT && dpi_y > HIDPI_LIMIT)
271                         window_scale = 2;
272         }
273 
274         return window_scale;
275 }
276 
277 static int
get_window_scale(MateXSettingsManager * manager)278 get_window_scale (MateXSettingsManager *manager)
279 {
280         GSettings   *gsettings;
281         gint         scale;
282 
283         /* Get scale factor from gsettings */
284         gsettings = g_hash_table_lookup (manager->priv->gsettings, INTERFACE_SCHEMA);
285         scale = g_settings_get_int (gsettings, SCALING_FACTOR_KEY);
286 
287         /* Auto-detect */
288         if (scale == 0)
289                 scale = get_window_scale_auto ();
290 
291         return scale;
292 }
293 
294 static double
dpi_from_pixels_and_mm(int pixels,int mm)295 dpi_from_pixels_and_mm (int pixels,
296                         int mm)
297 {
298         double dpi;
299 
300         if (mm >= 1)
301                 dpi = pixels / (mm / 25.4);
302         else
303                 dpi = 0;
304 
305         return dpi;
306 }
307 
308 static double
get_dpi_from_x_server(void)309 get_dpi_from_x_server (void)
310 {
311         GdkScreen *screen;
312         double     dpi;
313 
314         screen = gdk_screen_get_default ();
315         if (screen != NULL) {
316                 double width_dpi, height_dpi;
317 
318                 Screen *xscreen = gdk_x11_screen_get_xscreen (screen);
319 
320                 width_dpi = dpi_from_pixels_and_mm (WidthOfScreen (xscreen), WidthMMOfScreen (xscreen));
321                 height_dpi = dpi_from_pixels_and_mm (HeightOfScreen (xscreen), HeightMMOfScreen (xscreen));
322 
323                 if (width_dpi < DPI_LOW_REASONABLE_VALUE || width_dpi > DPI_HIGH_REASONABLE_VALUE
324                     || height_dpi < DPI_LOW_REASONABLE_VALUE || height_dpi > DPI_HIGH_REASONABLE_VALUE) {
325                         dpi = DPI_FALLBACK;
326                 } else {
327                         dpi = (width_dpi + height_dpi) / 2.0;
328                 }
329         } else {
330                 /* Huh!?  No screen? */
331 
332                 dpi = DPI_FALLBACK;
333         }
334 
335         return dpi;
336 }
337 
338 static double
get_dpi_from_gsettings_or_x_server(GSettings * gsettings,gint scale)339 get_dpi_from_gsettings_or_x_server (GSettings *gsettings, gint scale)
340 {
341         double dpi;
342 
343         dpi = g_settings_get_double (gsettings, FONT_DPI_KEY);
344 
345         /* If the user has ever set the DPI preference in GSettings, we use that.
346          * Otherwise, we see if the X server reports a reasonable DPI value:  some X
347          * servers report completely bogus values, and the user gets huge or tiny
348          * fonts which are unusable.
349          */
350 
351         if (dpi == 0)
352                 dpi = get_dpi_from_x_server ();
353 
354         dpi *= (double)scale;
355         dpi = CLAMP(dpi, DPI_LOW_REASONABLE_VALUE, DPI_HIGH_REASONABLE_VALUE);
356 
357         return dpi;
358 }
359 
360 typedef struct
361 {
362         gboolean    antialias;
363         gboolean    hinting;
364         int         window_scale;
365         int         dpi;
366         int         scaled_dpi;
367         char       *cursor_theme;
368         int         cursor_size;
369         const char *rgba;
370         const char *hintstyle;
371 } MateXftSettings;
372 
373 static const char *rgba_types[] = { "rgb", "bgr", "vbgr", "vrgb" };
374 
375 /* Read GSettings values and determine the appropriate Xft settings based on them
376  * This probably could be done a bit more cleanly with g_settings_get_enum
377  */
378 static void
xft_settings_get(MateXSettingsManager * manager,MateXftSettings * settings)379 xft_settings_get (MateXSettingsManager *manager,
380                   MateXftSettings *settings)
381 {
382         GSettings *mouse_gsettings;
383         char      *antialiasing;
384         char      *hinting;
385         char      *rgba_order;
386         double     dpi;
387         gint       scale;
388 
389         mouse_gsettings = g_hash_table_lookup (manager->priv->gsettings, MOUSE_SCHEMA);
390 
391         antialiasing = g_settings_get_string (manager->priv->gsettings_font, FONT_ANTIALIASING_KEY);
392         hinting = g_settings_get_string (manager->priv->gsettings_font, FONT_HINTING_KEY);
393         rgba_order = g_settings_get_string (manager->priv->gsettings_font, FONT_RGBA_ORDER_KEY);
394         scale = get_window_scale (manager);
395         dpi = get_dpi_from_gsettings_or_x_server (manager->priv->gsettings_font, scale);
396 
397         settings->antialias = TRUE;
398         settings->hinting = TRUE;
399         settings->hintstyle = "hintslight";
400         settings->window_scale = scale;
401         settings->dpi = dpi / scale * 1024; /* Xft wants 1/1024ths of an inch */
402         settings->scaled_dpi = dpi * 1024;
403         settings->cursor_theme = g_settings_get_string (mouse_gsettings, CURSOR_THEME_KEY);
404         settings->cursor_size = scale * g_settings_get_int (mouse_gsettings, CURSOR_SIZE_KEY);
405         settings->rgba = "rgb";
406 
407         if (rgba_order) {
408                 int i;
409                 gboolean found = FALSE;
410 
411                 for (i = 0; i < G_N_ELEMENTS (rgba_types) && !found; i++) {
412                         if (strcmp (rgba_order, rgba_types[i]) == 0) {
413                                 settings->rgba = rgba_types[i];
414                                 found = TRUE;
415                         }
416                 }
417 
418                 if (!found) {
419                         g_warning ("Invalid value for " FONT_RGBA_ORDER_KEY ": '%s'",
420                                    rgba_order);
421                 }
422         }
423 
424         if (hinting) {
425                 if (strcmp (hinting, "none") == 0) {
426                         settings->hinting = 0;
427                         settings->hintstyle = "hintnone";
428                 } else if (strcmp (hinting, "slight") == 0) {
429                         settings->hinting = 1;
430                         settings->hintstyle = "hintslight";
431                 } else if (strcmp (hinting, "medium") == 0) {
432                         settings->hinting = 1;
433                         settings->hintstyle = "hintmedium";
434                 } else if (strcmp (hinting, "full") == 0) {
435                         settings->hinting = 1;
436                         settings->hintstyle = "hintfull";
437                 } else {
438                         g_warning ("Invalid value for " FONT_HINTING_KEY ": '%s'",
439                                    hinting);
440                 }
441         }
442 
443         if (antialiasing) {
444                 gboolean use_rgba = FALSE;
445 
446                 if (strcmp (antialiasing, "none") == 0) {
447                         settings->antialias = 0;
448                 } else if (strcmp (antialiasing, "grayscale") == 0) {
449                         settings->antialias = 1;
450                 } else if (strcmp (antialiasing, "rgba") == 0) {
451                         settings->antialias = 1;
452                         use_rgba = TRUE;
453                 } else {
454                         g_warning ("Invalid value for " FONT_ANTIALIASING_KEY " : '%s'",
455                                    antialiasing);
456                 }
457 
458                 if (!use_rgba) {
459                         settings->rgba = "none";
460                 }
461         }
462 
463         g_free (rgba_order);
464         g_free (hinting);
465         g_free (antialiasing);
466 }
467 
468 /* Set environment variable.
469  * This should only work during the initialization phase. */
470 static gboolean
update_user_env_variable(const gchar * variable,const gchar * value,GError ** error)471 update_user_env_variable (const gchar  *variable,
472                           const gchar  *value,
473                           GError      **error)
474 {
475         GDBusConnection *connection;
476         gboolean         environment_updated;
477         GVariant        *reply;
478         GError          *bus_error = NULL;
479 
480         g_setenv (variable, value, TRUE);
481 
482         environment_updated = FALSE;
483         connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
484 
485         if (connection == NULL) {
486                 return FALSE;
487         }
488 
489         reply = g_dbus_connection_call_sync (connection,
490                         "org.gnome.SessionManager",
491                         "/org/gnome/SessionManager",
492                         "org.gnome.SessionManager",
493                         "Setenv",
494                         g_variant_new ("(ss)", variable, value),
495                         NULL,
496                         G_DBUS_CALL_FLAGS_NONE,
497                         -1, NULL, &bus_error);
498 
499         if (bus_error != NULL) {
500                 g_propagate_error (error, bus_error);
501         } else {
502                 environment_updated = TRUE;
503                 g_variant_unref (reply);
504         }
505 
506         g_clear_object (&connection);
507 
508         return environment_updated;
509 }
510 
511 static gboolean
delayed_toggle_bg_draw(gpointer value)512 delayed_toggle_bg_draw (gpointer value)
513 {
514         GSettings *settings;
515 
516         settings = g_settings_new ("org.mate.background");
517         g_settings_set_boolean (settings, "show-desktop-icons", GPOINTER_TO_BOOLEAN (value));
518         g_object_unref (settings);
519 
520         return G_SOURCE_REMOVE;
521 }
522 
523 static void
scale_change_workarounds(MateXSettingsManager * manager,int new_scale,int unscaled_dpi)524 scale_change_workarounds (MateXSettingsManager *manager, int new_scale, int unscaled_dpi)
525 {
526         if (manager->priv->window_scale == new_scale)
527                 return;
528 
529         GError *error = NULL;
530 
531         /* This is only useful during the Initialization phase, so we guard against
532          * unnecessarily attempting to set it later. */
533         if (!manager->priv->window_scale) {
534                 GSettings   *gsettings;
535                 gsettings = g_hash_table_lookup (manager->priv->gsettings, INTERFACE_SCHEMA);
536                 /* If enabled, set env variables to properly scale QT applications */
537                 if (g_settings_get_boolean (gsettings, SCALING_FACTOR_QT_KEY)) {
538                         char dpibuf[G_ASCII_DTOSTR_BUF_SIZE];
539                         g_snprintf (dpibuf, sizeof (dpibuf), "%d", (int) (unscaled_dpi / 1024.0 + 0.5));
540 
541                         if (!update_user_env_variable ("QT_FONT_DPI", dpibuf, &error)) {
542                                 g_warning ("There was a problem when setting QT_FONT_DPI=%s: %s", dpibuf, error->message);
543                                 g_clear_error (&error);
544                         }
545                         if (!update_user_env_variable ("QT_SCALE_FACTOR", new_scale == 2 ? "2" : "1", &error)) {
546                                 g_warning ("There was a problem when setting QT_SCALE_FACTOR=%d: %s", new_scale, error->message);
547                                 g_clear_error (&error);
548                         }
549                 }
550         } else {
551                 /* Restart marco */
552                 /* FIXME: The ideal scenario would be for marco to respect window scaling and thus
553                  * resize itself. Currently this is not happening, so msd restarts it when the window
554                  * scaling factor changes so that it's visually correct. */
555                 wm_common_update_window();
556                 gchar *wm = wm_common_get_current_window_manager ();
557                 if (g_strcmp0 (wm, WM_COMMON_MARCO) == 0) {
558                         gchar *marco[3] = {"marco", "--replace", NULL};
559                         if (!g_spawn_async (NULL, marco, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) {
560                                 g_warning ("There was a problem restarting marco: %s", error->message);
561                                 g_clear_error (&error);
562                         }
563                 }
564                 g_free (wm);
565 
566                 /* Restart mate-panel */
567                 /* FIXME: The ideal scenario would be for mate-panel to respect window scaling and thus
568                  * resize itself. Currently this is not happening, so msd restarts it when the window
569                  * scaling factor changes so that it's visually correct. */
570                 gchar *mate_panel[3] = {"killall", "mate-panel", NULL};
571                 if (!g_spawn_async (NULL, mate_panel, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) {
572                         g_warning ("There was a problem restarting mate-panel: %s", error->message);
573                         g_clear_error (&error);
574                 }
575 
576                 /* Toggle icons on desktop to fix size */
577                 /* FIXME: The ideal scenario would be for caja to respect window scaling and thus
578                  * resize itself. Currently this is not happening, so msd restarts it when the window
579                  * scaling factor changes so that it's visually correct. */
580                 GSettings *desktop_settings;
581                 desktop_settings = g_settings_new ("org.mate.background");
582                 if (g_settings_get_boolean (desktop_settings, "show-desktop-icons")) {
583                         /* Delay the toggle to allow enough time for the desktop to redraw */
584                         g_timeout_add_seconds (1, delayed_toggle_bg_draw, GBOOLEAN_TO_POINTER (FALSE));
585                         g_timeout_add_seconds (2, delayed_toggle_bg_draw, GBOOLEAN_TO_POINTER (TRUE));
586                 }
587                 g_object_unref (desktop_settings);
588         }
589 
590         /* Store new scale value */
591         manager->priv->window_scale = new_scale;
592 }
593 
594 static void
xft_settings_set_xsettings(MateXSettingsManager * manager,MateXftSettings * settings)595 xft_settings_set_xsettings (MateXSettingsManager *manager,
596                             MateXftSettings      *settings)
597 {
598         int i;
599 
600         mate_settings_profile_start (NULL);
601 
602         for (i = 0; manager->priv->managers [i]; i++) {
603                 xsettings_manager_set_int (manager->priv->managers [i], "Xft/Antialias", settings->antialias);
604                 xsettings_manager_set_int (manager->priv->managers [i], "Xft/Hinting", settings->hinting);
605                 xsettings_manager_set_string (manager->priv->managers [i], "Xft/HintStyle", settings->hintstyle);
606                 xsettings_manager_set_int (manager->priv->managers [i], "Gdk/WindowScalingFactor", settings->window_scale);
607                 xsettings_manager_set_int (manager->priv->managers [i], "Gdk/UnscaledDPI", settings->dpi);
608                 xsettings_manager_set_int (manager->priv->managers [i], "Xft/DPI", settings->scaled_dpi);
609                 xsettings_manager_set_string (manager->priv->managers [i], "Xft/RGBA", settings->rgba);
610                 xsettings_manager_set_string (manager->priv->managers [i], "Xft/lcdfilter",
611                                               g_str_equal (settings->rgba, "rgb") ? "lcddefault" : "none");
612                 xsettings_manager_set_int (manager->priv->managers [i], "Gtk/CursorThemeSize", settings->cursor_size);
613                 xsettings_manager_set_string (manager->priv->managers [i], "Gtk/CursorThemeName", settings->cursor_theme);
614         }
615         mate_settings_profile_end (NULL);
616 
617         scale_change_workarounds (manager, settings->window_scale, settings->dpi);
618 }
619 
620 static void
update_property(GString * props,const gchar * key,const gchar * value)621 update_property (GString *props, const gchar* key, const gchar* value)
622 {
623         gchar* needle;
624         size_t needle_len;
625         gchar* found = NULL;
626 
627         /* update an existing property */
628         needle = g_strconcat (key, ":", NULL);
629         needle_len = strlen (needle);
630         if (g_str_has_prefix (props->str, needle))
631                 found = props->str;
632         else
633                 found = strstr (props->str, needle);
634 
635         if (found) {
636                 size_t value_index;
637                 gchar* end;
638 
639                 end = strchr (found, '\n');
640                 value_index = (found - props->str) + needle_len + 1;
641                 g_string_erase (props, value_index, end ? (end - found - needle_len) : -1);
642                 g_string_insert (props, value_index, "\n");
643                 g_string_insert (props, value_index, value);
644         } else {
645                 g_string_append_printf (props, "%s:\t%s\n", key, value);
646         }
647 
648         g_free (needle);
649 }
650 
651 static void
xft_settings_set_xresources(MateXftSettings * settings)652 xft_settings_set_xresources (MateXftSettings *settings)
653 {
654         GString    *add_string;
655         char        dpibuf[G_ASCII_DTOSTR_BUF_SIZE];
656         Display    *dpy;
657 
658         mate_settings_profile_start (NULL);
659 
660         /* get existing properties */
661         dpy = XOpenDisplay (NULL);
662         g_return_if_fail (dpy != NULL);
663         add_string = g_string_new (XResourceManagerString (dpy));
664 
665         g_debug("xft_settings_set_xresources: orig res '%s'", add_string->str);
666 
667         g_snprintf (dpibuf, sizeof (dpibuf), "%d", (int) (settings->scaled_dpi / 1024.0 + 0.5));
668         update_property (add_string, "Xft.dpi", dpibuf);
669         update_property (add_string, "Xft.antialias",
670                                 settings->antialias ? "1" : "0");
671         update_property (add_string, "Xft.hinting",
672                                 settings->hinting ? "1" : "0");
673         update_property (add_string, "Xft.hintstyle",
674                                 settings->hintstyle);
675         update_property (add_string, "Xft.rgba",
676                                 settings->rgba);
677         update_property (add_string, "Xft.lcdfilter",
678                          g_str_equal (settings->rgba, "rgb") ? "lcddefault" : "none");
679         update_property (add_string, "Xcursor.theme",
680                                 settings->cursor_theme);
681         update_property (add_string, "Xcursor.size",
682                                 g_ascii_dtostr (dpibuf, sizeof (dpibuf), (double) settings->cursor_size));
683 
684         g_debug("xft_settings_set_xresources: new res '%s'", add_string->str);
685 
686         /* Set the new X property */
687         XChangeProperty(dpy, RootWindow (dpy, 0),
688                 XA_RESOURCE_MANAGER, XA_STRING, 8, PropModeReplace, (unsigned char *) add_string->str, add_string->len);
689         XCloseDisplay (dpy);
690 
691         g_string_free (add_string, TRUE);
692 
693         mate_settings_profile_end (NULL);
694 }
695 
696 /* We mirror the Xft properties both through XSETTINGS and through
697  * X resources
698  */
699 static void
update_xft_settings(MateXSettingsManager * manager)700 update_xft_settings (MateXSettingsManager *manager)
701 {
702         MateXftSettings settings;
703 
704         mate_settings_profile_start (NULL);
705 
706         xft_settings_get (manager, &settings);
707         xft_settings_set_xsettings (manager, &settings);
708         xft_settings_set_xresources (&settings);
709 
710         mate_settings_profile_end (NULL);
711 }
712 
713 static void
recalculate_scale_callback(GdkScreen * screen G_GNUC_UNUSED,MateXSettingsManager * manager)714 recalculate_scale_callback (GdkScreen            *screen G_GNUC_UNUSED,
715                             MateXSettingsManager *manager)
716 {
717         int i;
718         int new_scale = get_window_scale (manager);
719 
720         if (manager->priv->window_scale == new_scale)
721                 return;
722 
723         update_xft_settings (manager);
724 
725         for (i = 0; manager->priv->managers [i]; i++) {
726                 xsettings_manager_notify (manager->priv->managers [i]);
727         }
728 }
729 
730 static void
xft_callback(GSettings * gsettings G_GNUC_UNUSED,const gchar * key G_GNUC_UNUSED,MateXSettingsManager * manager)731 xft_callback (GSettings            *gsettings G_GNUC_UNUSED,
732               const gchar          *key G_GNUC_UNUSED,
733               MateXSettingsManager *manager)
734 {
735         int i;
736 
737         update_xft_settings (manager);
738 
739         for (i = 0; manager->priv->managers [i]; i++) {
740                 xsettings_manager_notify (manager->priv->managers [i]);
741         }
742 }
743 
744 static void
fontconfig_callback(fontconfig_monitor_handle_t * handle,MateXSettingsManager * manager)745 fontconfig_callback (fontconfig_monitor_handle_t *handle,
746                      MateXSettingsManager       *manager)
747 {
748         int i;
749         int timestamp = time (NULL);
750 
751         mate_settings_profile_start (NULL);
752 
753         for (i = 0; manager->priv->managers [i]; i++) {
754                 xsettings_manager_set_int (manager->priv->managers [i], "Fontconfig/Timestamp", timestamp);
755                 xsettings_manager_notify (manager->priv->managers [i]);
756         }
757         mate_settings_profile_end (NULL);
758 }
759 
760 static gboolean
start_fontconfig_monitor_idle_cb(MateXSettingsManager * manager)761 start_fontconfig_monitor_idle_cb (MateXSettingsManager *manager)
762 {
763         mate_settings_profile_start (NULL);
764 
765         manager->priv->fontconfig_handle = fontconfig_monitor_start ((GFunc) fontconfig_callback, manager);
766 
767         mate_settings_profile_end (NULL);
768 
769         return FALSE;
770 }
771 
772 static void
start_fontconfig_monitor(MateXSettingsManager * manager)773 start_fontconfig_monitor (MateXSettingsManager  *manager)
774 {
775         mate_settings_profile_start (NULL);
776 
777         fontconfig_cache_init ();
778 
779         g_idle_add ((GSourceFunc) start_fontconfig_monitor_idle_cb, manager);
780 
781         mate_settings_profile_end (NULL);
782 }
783 
784 static void
stop_fontconfig_monitor(MateXSettingsManager * manager)785 stop_fontconfig_monitor (MateXSettingsManager  *manager)
786 {
787         if (manager->priv->fontconfig_handle) {
788                 fontconfig_monitor_stop (manager->priv->fontconfig_handle);
789                 manager->priv->fontconfig_handle = NULL;
790         }
791 }
792 
793 static void
process_value(MateXSettingsManager * manager,TranslationEntry * trans,GVariant * value)794 process_value (MateXSettingsManager *manager,
795                TranslationEntry     *trans,
796                GVariant             *value)
797 {
798         (* trans->translate) (manager, trans, value);
799 }
800 
801 static TranslationEntry *
find_translation_entry(GSettings * gsettings,const char * key)802 find_translation_entry (GSettings *gsettings, const char *key)
803 {
804         guint i;
805         char *schema;
806 
807         g_object_get (gsettings, "schema", &schema, NULL);
808 
809         for (i = 0; i < G_N_ELEMENTS (translations); i++) {
810                 if (g_str_equal (schema, translations[i].gsettings_schema) &&
811                     g_str_equal (key, translations[i].gsettings_key)) {
812                             g_free (schema);
813                         return &translations[i];
814                 }
815         }
816 
817         g_free (schema);
818 
819         return NULL;
820 }
821 
822 static void
xsettings_callback(GSettings * gsettings,const char * key,MateXSettingsManager * manager)823 xsettings_callback (GSettings             *gsettings,
824                     const char            *key,
825                     MateXSettingsManager  *manager)
826 {
827         TranslationEntry *trans;
828         int               i;
829         GVariant         *value;
830 
831         if (g_str_equal (key, CURSOR_THEME_KEY) ||
832             g_str_equal (key, SCALING_FACTOR_KEY) ||
833             g_str_equal (key, CURSOR_SIZE_KEY)) {
834                 xft_callback (NULL, key, manager);
835                 return;
836 	}
837 
838         trans = find_translation_entry (gsettings, key);
839         if (trans == NULL) {
840                 return;
841         }
842 
843         value = g_settings_get_value (gsettings, key);
844 
845         process_value (manager, trans, value);
846 
847         g_variant_unref (value);
848 
849         for (i = 0; manager->priv->managers [i]; i++) {
850                 xsettings_manager_set_string (manager->priv->managers [i],
851                                               "Net/FallbackIconTheme",
852                                               "mate");
853         }
854 
855         for (i = 0; manager->priv->managers [i]; i++) {
856                 xsettings_manager_notify (manager->priv->managers [i]);
857         }
858 }
859 
860 static void
terminate_cb(void * data)861 terminate_cb (void *data)
862 {
863         gboolean *terminated = data;
864 
865         if (*terminated) {
866                 return;
867         }
868 
869         *terminated = TRUE;
870 
871         gtk_main_quit ();
872 }
873 
874 static gboolean
setup_xsettings_managers(MateXSettingsManager * manager)875 setup_xsettings_managers (MateXSettingsManager *manager)
876 {
877         GdkDisplay *display;
878         gboolean    res;
879         gboolean    terminated;
880 
881         display = gdk_display_get_default ();
882 
883         res = xsettings_manager_check_running (gdk_x11_display_get_xdisplay (display),
884                                                gdk_x11_screen_get_screen_number (gdk_screen_get_default ()));
885         if (res) {
886                 g_warning ("You can only run one xsettings manager at a time; exiting");
887                 return FALSE;
888         }
889 
890         manager->priv->managers = g_new0 (XSettingsManager *, 2);
891 
892         terminated = FALSE;
893 
894         GdkScreen *screen;
895 
896         screen = gdk_display_get_default_screen (display);
897 
898         manager->priv->managers [0] = xsettings_manager_new (gdk_x11_display_get_xdisplay (display),
899                                                              gdk_x11_screen_get_screen_number (screen),
900                                                              terminate_cb,
901                                                              &terminated);
902         if (! manager->priv->managers [0]) {
903                g_warning ("Could not create xsettings manager for screen!");
904                 return FALSE;
905         }
906 
907         return TRUE;
908 }
909 
910 gboolean
mate_xsettings_manager_start(MateXSettingsManager * manager,GError ** error)911 mate_xsettings_manager_start (MateXSettingsManager *manager,
912                                GError               **error)
913 {
914         guint        i;
915         GList       *list, *l;
916         GdkScreen   *screen;
917 
918         g_debug ("Starting xsettings manager");
919         mate_settings_profile_start (NULL);
920 
921         if (!setup_xsettings_managers (manager)) {
922                 g_set_error (error, MSD_XSETTINGS_ERROR,
923                              MSD_XSETTINGS_ERROR_INIT,
924                              "Could not initialize xsettings manager.");
925                 return FALSE;
926         }
927 
928         manager->priv->gsettings = g_hash_table_new_full (g_str_hash, g_str_equal,
929                                                          NULL, (GDestroyNotify) g_object_unref);
930 
931         g_hash_table_insert (manager->priv->gsettings,
932                              MOUSE_SCHEMA, g_settings_new (MOUSE_SCHEMA));
933         g_hash_table_insert (manager->priv->gsettings,
934                              INTERFACE_SCHEMA, g_settings_new (INTERFACE_SCHEMA));
935         g_hash_table_insert (manager->priv->gsettings,
936                              SOUND_SCHEMA, g_settings_new (SOUND_SCHEMA));
937 
938         list = g_hash_table_get_values (manager->priv->gsettings);
939         for (l = list; l != NULL; l = l->next) {
940                 g_signal_connect_object (G_OBJECT (l->data), "changed",
941                 			 G_CALLBACK (xsettings_callback), manager, 0);
942         }
943 
944         g_list_free (list);
945 
946         for (i = 0; i < G_N_ELEMENTS (translations); i++) {
947                 GVariant  *val;
948                 GSettings *gsettings;
949 
950                 gsettings = g_hash_table_lookup (manager->priv->gsettings,
951                                                 translations[i].gsettings_schema);
952 
953 		if (gsettings == NULL) {
954 			g_warning ("Schemas '%s' has not been setup", translations[i].gsettings_schema);
955 			continue;
956 		}
957 
958                 val = g_settings_get_value (gsettings, translations[i].gsettings_key);
959 
960                 process_value (manager, &translations[i], val);
961                 g_variant_unref (val);
962         }
963 
964         /* Detect changes in screen resolution */
965         screen = gdk_screen_get_default();
966         g_signal_connect(screen, "size-changed", G_CALLBACK (recalculate_scale_callback), manager);
967         g_signal_connect(screen, "monitors-changed", G_CALLBACK (recalculate_scale_callback), manager);
968 
969         manager->priv->gsettings_font = g_settings_new (FONT_RENDER_SCHEMA);
970         g_signal_connect (manager->priv->gsettings_font, "changed", G_CALLBACK (xft_callback), manager);
971         update_xft_settings (manager);
972 
973         start_fontconfig_monitor (manager);
974 
975         for (i = 0; manager->priv->managers [i]; i++)
976                 xsettings_manager_set_string (manager->priv->managers [i],
977                                               "Net/FallbackIconTheme",
978                                               "mate");
979 
980         for (i = 0; manager->priv->managers [i]; i++) {
981                 xsettings_manager_notify (manager->priv->managers [i]);
982         }
983 
984         mate_settings_profile_end (NULL);
985 
986         return TRUE;
987 }
988 
989 void
mate_xsettings_manager_stop(MateXSettingsManager * manager)990 mate_xsettings_manager_stop (MateXSettingsManager *manager)
991 {
992         MateXSettingsManagerPrivate *p = manager->priv;
993         int i;
994 
995         g_debug ("Stopping xsettings manager");
996 
997         if (p->managers != NULL) {
998                 for (i = 0; p->managers [i]; ++i)
999                         xsettings_manager_destroy (p->managers [i]);
1000 
1001                 g_free (p->managers);
1002                 p->managers = NULL;
1003         }
1004 
1005         if (p->gsettings != NULL) {
1006                 g_hash_table_destroy (p->gsettings);
1007                 p->gsettings = NULL;
1008         }
1009 
1010         if (p->gsettings_font != NULL) {
1011                 g_object_unref (p->gsettings_font);
1012                 p->gsettings_font = NULL;
1013         }
1014 
1015         stop_fontconfig_monitor (manager);
1016 
1017 }
1018 
1019 static void
mate_xsettings_manager_class_init(MateXSettingsManagerClass * klass)1020 mate_xsettings_manager_class_init (MateXSettingsManagerClass *klass)
1021 {
1022         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1023 
1024         object_class->finalize = mate_xsettings_manager_finalize;
1025 }
1026 
1027 static void
mate_xsettings_manager_init(MateXSettingsManager * manager)1028 mate_xsettings_manager_init (MateXSettingsManager *manager)
1029 {
1030         manager->priv = mate_xsettings_manager_get_instance_private (manager);
1031 }
1032 
1033 static void
mate_xsettings_manager_finalize(GObject * object)1034 mate_xsettings_manager_finalize (GObject *object)
1035 {
1036         MateXSettingsManager *xsettings_manager;
1037 
1038         g_return_if_fail (object != NULL);
1039         g_return_if_fail (MATE_IS_XSETTINGS_MANAGER (object));
1040 
1041         xsettings_manager = MATE_XSETTINGS_MANAGER (object);
1042 
1043         g_return_if_fail (xsettings_manager->priv != NULL);
1044 
1045         G_OBJECT_CLASS (mate_xsettings_manager_parent_class)->finalize (object);
1046 }
1047 
1048 MateXSettingsManager *
mate_xsettings_manager_new(void)1049 mate_xsettings_manager_new (void)
1050 {
1051         if (manager_object != NULL) {
1052                 g_object_ref (manager_object);
1053         } else {
1054                 manager_object = g_object_new (MATE_TYPE_XSETTINGS_MANAGER, NULL);
1055                 g_object_add_weak_pointer (manager_object,
1056                                            (gpointer *) &manager_object);
1057         }
1058 
1059         return MATE_XSETTINGS_MANAGER (manager_object);
1060 }
1061