/* -*- 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) * * 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 . * * Author: Behdad Esfahbod, Red Hat, Inc. */ #include "fc-monitor.h" #include #include #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