1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2008 Red Hat, Inc.
4  * Copyright (C) 2017 Jan Alexander Steffens (heftig) <jan.steffens@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Author:  Behdad Esfahbod, Red Hat, Inc.
20  */
21 
22 #include "fc-monitor.h"
23 
24 #include <gio/gio.h>
25 #include <fontconfig/fontconfig.h>
26 
27 #define TIMEOUT_MILLISECONDS 1000
28 
29 static void
fontconfig_cache_update_thread(GTask * task,gpointer source_object G_GNUC_UNUSED,gpointer task_data G_GNUC_UNUSED,GCancellable * cancellable G_GNUC_UNUSED)30 fontconfig_cache_update_thread (GTask *task,
31                                 gpointer source_object G_GNUC_UNUSED,
32                                 gpointer task_data G_GNUC_UNUSED,
33                                 GCancellable *cancellable G_GNUC_UNUSED)
34 {
35         if (FcConfigUptoDate (NULL)) {
36                 g_task_return_boolean (task, FALSE);
37                 return;
38         }
39 
40         if (!FcInitReinitialize ()) {
41                 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
42                                          "FcInitReinitialize failed");
43                 return;
44         }
45 
46         g_task_return_boolean (task, TRUE);
47 }
48 
49 static void
fontconfig_cache_update_async(GAsyncReadyCallback callback,gpointer user_data)50 fontconfig_cache_update_async (GAsyncReadyCallback callback,
51                                gpointer user_data)
52 {
53         GTask *task = g_task_new (NULL, NULL, callback, user_data);
54         g_task_run_in_thread (task, fontconfig_cache_update_thread);
55         g_object_unref (task);
56 }
57 
58 static gboolean
fontconfig_cache_update_finish(GAsyncResult * result,GError ** error)59 fontconfig_cache_update_finish (GAsyncResult *result,
60                                 GError **error)
61 {
62         return g_task_propagate_boolean (G_TASK (result), error);
63 }
64 
65 typedef enum {
66         UPDATE_IDLE,
67         UPDATE_PENDING,
68         UPDATE_RUNNING,
69         UPDATE_RESTART,
70 } UpdateState;
71 
72 struct _FcMonitor {
73         GObject parent_instance;
74 
75         GPtrArray *monitors;
76 
77         guint timeout;
78         UpdateState state;
79         gboolean notify;
80 };
81 
82 enum {
83         SIGNAL_UPDATED,
84 
85         N_SIGNALS
86 };
87 
88 static guint signals[N_SIGNALS] = { 0, };
89 
90 static void fc_monitor_finalize (GObject *object);
91 static void monitor_files (FcMonitor *self, FcStrList *list);
92 static void stuff_changed (GFileMonitor *monitor, GFile *file, GFile *other_file,
93                            GFileMonitorEvent event_type, gpointer data);
94 static void start_timeout (FcMonitor *self);
95 static gboolean start_update (gpointer data);
96 static void update_done (GObject *source_object, GAsyncResult *result, gpointer user_data);
97 
98 G_DEFINE_TYPE (FcMonitor, fc_monitor, G_TYPE_OBJECT);
99 
100 static void
fc_monitor_class_init(FcMonitorClass * klass)101 fc_monitor_class_init (FcMonitorClass *klass)
102 {
103         GObjectClass *object_class = G_OBJECT_CLASS (klass);
104 
105         object_class->finalize = fc_monitor_finalize;
106 
107         signals[SIGNAL_UPDATED] = g_signal_new ("updated",
108                                                 G_TYPE_FROM_CLASS (klass),
109                                                 G_SIGNAL_RUN_LAST,
110                                                 0,
111                                                 NULL,
112                                                 NULL,
113                                                 NULL,
114                                                 G_TYPE_NONE,
115                                                 0);
116 }
117 
118 FcMonitor *
fc_monitor_new(void)119 fc_monitor_new (void)
120 {
121         return g_object_new (FC_TYPE_MONITOR, NULL);
122 }
123 
124 static void
fc_monitor_init(FcMonitor * self G_GNUC_UNUSED)125 fc_monitor_init (FcMonitor *self G_GNUC_UNUSED)
126 {
127         FcInit ();
128 }
129 
130 static void
fc_monitor_finalize(GObject * object)131 fc_monitor_finalize (GObject *object)
132 {
133         FcMonitor *self = FC_MONITOR (object);
134 
135         if (self->timeout)
136                 g_source_remove (self->timeout);
137         self->timeout = 0;
138 
139         g_clear_pointer (&self->monitors, g_ptr_array_unref);
140 
141         G_OBJECT_CLASS (fc_monitor_parent_class)->finalize (object);
142 }
143 
144 void
fc_monitor_start(FcMonitor * self)145 fc_monitor_start (FcMonitor *self)
146 {
147         g_return_if_fail (FC_IS_MONITOR (self));
148         g_return_if_fail (self->monitors == NULL);
149 
150         self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
151 
152         monitor_files (self, FcConfigGetConfigFiles (NULL));
153         monitor_files (self, FcConfigGetFontDirs (NULL));
154 }
155 
156 void
fc_monitor_stop(FcMonitor * self)157 fc_monitor_stop (FcMonitor *self)
158 {
159         g_return_if_fail (FC_IS_MONITOR (self));
160         g_clear_pointer (&self->monitors, g_ptr_array_unref);
161 }
162 
163 static void
monitor_files(FcMonitor * self,FcStrList * list)164 monitor_files (FcMonitor *self,
165                FcStrList *list)
166 {
167         const char *str;
168 
169         while ((str = (const char *) FcStrListNext (list))) {
170                 GFile *file;
171                 GFileMonitor *monitor;
172 
173                 file = g_file_new_for_path (str);
174 
175                 monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL);
176 
177                 g_object_unref (file);
178 
179                 if (!monitor)
180                         continue;
181 
182                 g_signal_connect (monitor, "changed", G_CALLBACK (stuff_changed), self);
183 
184                 g_ptr_array_add (self->monitors, monitor);
185         }
186 
187         FcStrListDone (list);
188 }
189 
190 static const gchar *
get_name(GType enum_type,gint enum_value)191 get_name (GType enum_type,
192           gint enum_value)
193 {
194         GEnumClass *klass = g_type_class_ref (enum_type);
195         GEnumValue *value = g_enum_get_value (klass, enum_value);
196         const gchar *name = value ? value->value_name : "(unknown)";
197         g_type_class_unref (klass);
198         return name;
199 }
200 
201 static void
stuff_changed(GFileMonitor * monitor G_GNUC_UNUSED,GFile * file G_GNUC_UNUSED,GFile * other_file G_GNUC_UNUSED,GFileMonitorEvent event_type,gpointer data)202 stuff_changed (GFileMonitor *monitor G_GNUC_UNUSED,
203                GFile *file G_GNUC_UNUSED,
204                GFile *other_file G_GNUC_UNUSED,
205                GFileMonitorEvent event_type,
206                gpointer data)
207 {
208         FcMonitor *self = FC_MONITOR (data);
209         const gchar *event_name = get_name (G_TYPE_FILE_MONITOR_EVENT, event_type);
210 
211         switch (self->state) {
212         case UPDATE_IDLE:
213                 g_debug ("Got %-38s: starting fontconfig update timeout", event_name);
214                 start_timeout (self);
215                 break;
216 
217         case UPDATE_PENDING:
218                 /* wait for quiescence */
219                 g_debug ("Got %-38s: restarting fontconfig update timeout", event_name);
220                 g_source_remove (self->timeout);
221                 start_timeout (self);
222                 break;
223 
224         case UPDATE_RUNNING:
225                 g_debug ("Got %-38s: restarting fontconfig update", event_name);
226                 self->state = UPDATE_RESTART;
227                 break;
228 
229         case UPDATE_RESTART:
230                 g_debug ("Got %-38s: waiting on fontconfig update", event_name);
231                 break;
232         }
233 }
234 
235 static void
start_timeout(FcMonitor * self)236 start_timeout (FcMonitor *self)
237 {
238         self->state = UPDATE_PENDING;
239         self->timeout = g_timeout_add (TIMEOUT_MILLISECONDS, start_update, self);
240         g_source_set_name_by_id (self->timeout, "[gnome-settings-daemon] update");
241 }
242 
243 static gboolean
start_update(gpointer data)244 start_update (gpointer data)
245 {
246         FcMonitor *self = FC_MONITOR (data);
247 
248         self->state = UPDATE_RUNNING;
249         self->timeout = 0;
250 
251         g_debug ("Timeout completed: starting fontconfig update");
252         fontconfig_cache_update_async (update_done, g_object_ref (self));
253 
254         return G_SOURCE_REMOVE;
255 }
256 
257 static void
update_done(GObject * source_object G_GNUC_UNUSED,GAsyncResult * result,gpointer data)258 update_done (GObject *source_object G_GNUC_UNUSED,
259              GAsyncResult *result,
260              gpointer data)
261 {
262         FcMonitor *self = FC_MONITOR (data);
263         gboolean restart = self->state == UPDATE_RESTART;
264         GError *error = NULL;
265 
266         self->state = UPDATE_IDLE;
267 
268         if (fontconfig_cache_update_finish (result, &error)) {
269                 g_debug ("Fontconfig update successful");
270                 /* Remember we had a successful update even if we have to restart it */
271                 self->notify = TRUE;
272         } else if (error) {
273                 g_warning ("Fontconfig update failed: %s", error->message);
274                 g_error_free (error);
275         } else
276                 g_debug ("Fontconfig update was unnecessary");
277 
278         if (restart) {
279                 g_debug ("Concurrent change: restarting fontconfig update timeout");
280                 start_timeout (self);
281         } else if (self->notify) {
282                 self->notify = FALSE;
283 
284                 if (self->monitors) {
285                         fc_monitor_stop (self);
286                         fc_monitor_start (self);
287                 }
288 
289                 /* we finish modifying self before emitting the signal,
290                  * allowing the callback to stop us if it decides to. */
291                 g_signal_emit (self, signals[SIGNAL_UPDATED], 0);
292         }
293 
294         /* release ref taken in start_update */
295         g_object_unref (self);
296 }
297 
298 #ifdef FONTCONFIG_MONITOR_TEST
299 static void
yay(void)300 yay (void)
301 {
302         g_message ("yay");
303 }
304 
305 int
main(void)306 main (void)
307 {
308         GMainLoop *loop = g_main_loop_new (NULL, TRUE);
309         FcMonitor *monitor = fc_monitor_new ();
310 
311         fc_monitor_start (monitor);
312         g_signal_connect (monitor, "updated", G_CALLBACK (yay), NULL);
313 
314         g_main_loop_run (loop);
315         return 0;
316 }
317 #endif
318