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