summaryrefslogtreecommitdiffstats
path: root/plugins/xsettings/fc-monitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/xsettings/fc-monitor.c')
-rw-r--r--plugins/xsettings/fc-monitor.c306
1 files changed, 306 insertions, 0 deletions
diff --git a/plugins/xsettings/fc-monitor.c b/plugins/xsettings/fc-monitor.c
new file mode 100644
index 0000000..cafc8bf
--- /dev/null
+++ b/plugins/xsettings/fc-monitor.c
@@ -0,0 +1,306 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2017 Jan Alexander Steffens (heftig) <jan.steffens@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Behdad Esfahbod, Red Hat, Inc.
+ */
+
+#include "fc-monitor.h"
+
+#include <gio/gio.h>
+#include <fontconfig/fontconfig.h>
+
+#define TIMEOUT_MILLISECONDS 1000
+
+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)
+{
+ if (FcConfigUptoDate (NULL)) {
+ g_task_return_boolean (task, FALSE);
+ return;
+ }
+
+ if (!FcInitReinitialize ()) {
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "FcInitReinitialize failed");
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+fontconfig_cache_update_async (GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task = g_task_new (NULL, NULL, callback, user_data);
+ g_task_run_in_thread (task, fontconfig_cache_update_thread);
+ g_object_unref (task);
+}
+
+static gboolean
+fontconfig_cache_update_finish (GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+typedef enum {
+ UPDATE_IDLE,
+ UPDATE_PENDING,
+ UPDATE_RUNNING,
+ UPDATE_RESTART,
+} UpdateState;
+
+struct _FcMonitor {
+ GObject parent_instance;
+
+ GPtrArray *monitors;
+
+ guint timeout;
+ UpdateState state;
+ gboolean notify;
+};
+
+enum {
+ SIGNAL_UPDATED,
+
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0, };
+
+static void fc_monitor_finalize (GObject *object);
+static void monitor_files (FcMonitor *self, FcStrList *list);
+static void stuff_changed (GFileMonitor *monitor, GFile *file, GFile *other_file,
+ GFileMonitorEvent event_type, gpointer data);
+static void start_timeout (FcMonitor *self);
+static gboolean start_update (gpointer data);
+static void update_done (GObject *source_object, GAsyncResult *result, gpointer user_data);
+
+G_DEFINE_TYPE (FcMonitor, fc_monitor, G_TYPE_OBJECT);
+
+static void
+fc_monitor_class_init (FcMonitorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = fc_monitor_finalize;
+
+ signals[SIGNAL_UPDATED] = g_signal_new ("updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+FcMonitor *
+fc_monitor_new (void)
+{
+ return g_object_new (FC_TYPE_MONITOR, NULL);
+}
+
+static void
+fc_monitor_init (FcMonitor *self G_GNUC_UNUSED)
+{
+ FcInit ();
+}
+
+static void
+fc_monitor_finalize (GObject *object)
+{
+ FcMonitor *self = FC_MONITOR (object);
+
+ if (self->timeout)
+ g_source_remove (self->timeout);
+ self->timeout = 0;
+
+ g_clear_pointer (&self->monitors, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (fc_monitor_parent_class)->finalize (object);
+}
+
+void
+fc_monitor_start (FcMonitor *self)
+{
+ g_return_if_fail (FC_IS_MONITOR (self));
+ g_return_if_fail (self->monitors == NULL);
+
+ self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
+
+ monitor_files (self, FcConfigGetConfigFiles (NULL));
+ monitor_files (self, FcConfigGetFontDirs (NULL));
+}
+
+void
+fc_monitor_stop (FcMonitor *self)
+{
+ g_return_if_fail (FC_IS_MONITOR (self));
+ g_clear_pointer (&self->monitors, g_ptr_array_unref);
+}
+
+static void
+monitor_files (FcMonitor *self,
+ FcStrList *list)
+{
+ const char *str;
+
+ while ((str = (const char *) FcStrListNext (list))) {
+ GFile *file;
+ GFileMonitor *monitor;
+
+ file = g_file_new_for_path (str);
+
+ monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL);
+
+ g_object_unref (file);
+
+ if (!monitor)
+ continue;
+
+ g_signal_connect (monitor, "changed", G_CALLBACK (stuff_changed), self);
+
+ g_ptr_array_add (self->monitors, monitor);
+ }
+
+ FcStrListDone (list);
+}
+
+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)
+{
+ FcMonitor *self = FC_MONITOR (data);
+ const gchar *event_name = g_enum_to_string (G_TYPE_FILE_MONITOR_EVENT, event_type);
+
+ switch (self->state) {
+ case UPDATE_IDLE:
+ g_debug ("Got %-38s: starting fontconfig update timeout", event_name);
+ start_timeout (self);
+ break;
+
+ case UPDATE_PENDING:
+ /* wait for quiescence */
+ g_debug ("Got %-38s: restarting fontconfig update timeout", event_name);
+ g_source_remove (self->timeout);
+ start_timeout (self);
+ break;
+
+ case UPDATE_RUNNING:
+ g_debug ("Got %-38s: restarting fontconfig update", event_name);
+ self->state = UPDATE_RESTART;
+ break;
+
+ case UPDATE_RESTART:
+ g_debug ("Got %-38s: waiting on fontconfig update", event_name);
+ break;
+ }
+}
+
+static void
+start_timeout (FcMonitor *self)
+{
+ self->state = UPDATE_PENDING;
+ self->timeout = g_timeout_add (TIMEOUT_MILLISECONDS, start_update, self);
+ g_source_set_name_by_id (self->timeout, "[gnome-settings-daemon] update");
+}
+
+static gboolean
+start_update (gpointer data)
+{
+ FcMonitor *self = FC_MONITOR (data);
+
+ self->state = UPDATE_RUNNING;
+ self->timeout = 0;
+
+ g_debug ("Timeout completed: starting fontconfig update");
+ fontconfig_cache_update_async (update_done, g_object_ref (self));
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+update_done (GObject *source_object G_GNUC_UNUSED,
+ GAsyncResult *result,
+ gpointer data)
+{
+ FcMonitor *self = FC_MONITOR (data);
+ gboolean restart = self->state == UPDATE_RESTART;
+ GError *error = NULL;
+
+ self->state = UPDATE_IDLE;
+
+ if (fontconfig_cache_update_finish (result, &error)) {
+ g_debug ("Fontconfig update successful");
+ /* Remember we had a successful update even if we have to restart it */
+ self->notify = TRUE;
+ } else if (error) {
+ g_warning ("Fontconfig update failed: %s", error->message);
+ g_error_free (error);
+ } else
+ g_debug ("Fontconfig update was unnecessary");
+
+ if (restart) {
+ g_debug ("Concurrent change: restarting fontconfig update timeout");
+ start_timeout (self);
+ } else if (self->notify) {
+ self->notify = FALSE;
+
+ if (self->monitors) {
+ fc_monitor_stop (self);
+ fc_monitor_start (self);
+ }
+
+ /* we finish modifying self before emitting the signal,
+ * allowing the callback to stop us if it decides to. */
+ g_signal_emit (self, signals[SIGNAL_UPDATED], 0);
+ }
+
+ /* release ref taken in start_update */
+ g_object_unref (self);
+}
+
+#ifdef FONTCONFIG_MONITOR_TEST
+static void
+yay (void)
+{
+ g_message ("yay");
+}
+
+int
+main (void)
+{
+ GMainLoop *loop = g_main_loop_new (NULL, TRUE);
+ FcMonitor *monitor = fc_monitor_new ();
+
+ fc_monitor_start (monitor);
+ g_signal_connect (monitor, "updated", G_CALLBACK (yay), NULL);
+
+ g_main_loop_run (loop);
+ return 0;
+}
+#endif