[mutter] color-store: Monitor for non-EDID color profiles



commit 69df2a368d59f04f71b8fbe160327a410e3d8851
Author: Jonas Ã…dahl <jadahl gmail com>
Date:   Sat Dec 4 00:33:04 2021 +0100

    color-store: Monitor for non-EDID color profiles
    
    This means that e.g. custom profiles or calibrated profiles will be
    added and registered with colord. This does not use CdIccStore for two
    reasons: don't want to generate duplicate entries for auto-generated
    EDID or EFI profiles, and we want to store profiles as MetaColorProfile.
    It also happens to be the case that CdIcc does synchronous I/O, which
    should be avoided everywhere except on startup.
    
    Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2165>

 src/backends/meta-color-store.c | 352 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 352 insertions(+)
---
diff --git a/src/backends/meta-color-store.c b/src/backends/meta-color-store.c
index d267890175..088049d32d 100644
--- a/src/backends/meta-color-store.c
+++ b/src/backends/meta-color-store.c
@@ -35,9 +35,14 @@ struct _MetaColorStore
 
   MetaColorManager *color_manager;
 
+  GFileMonitor *icc_directory_monitor;
+
   GHashTable *profiles;
   GHashTable *device_profiles;
   GHashTable *pending_device_profiles;
+  GHashTable *pending_local_profiles;
+
+  GCancellable *cancellable;
 };
 
 typedef struct
@@ -54,9 +59,14 @@ meta_color_store_finalize (GObject *object)
 {
   MetaColorStore *color_store = META_COLOR_STORE (object);
 
+  g_cancellable_cancel (color_store->cancellable);
+  g_clear_object (&color_store->cancellable);
+
+  g_clear_object (&color_store->icc_directory_monitor);
   g_clear_pointer (&color_store->profiles, g_hash_table_unref);
   g_clear_pointer (&color_store->device_profiles, g_hash_table_unref);
   g_clear_pointer (&color_store->pending_device_profiles, g_hash_table_unref);
+  g_clear_pointer (&color_store->pending_local_profiles, g_hash_table_unref);
 
   G_OBJECT_CLASS (meta_color_store_parent_class)->finalize (object);
 }
@@ -72,12 +82,349 @@ meta_color_store_class_init (MetaColorStoreClass *klass)
 static void
 meta_color_store_init (MetaColorStore *color_store)
 {
+  color_store->cancellable = g_cancellable_new ();
+}
+
+static void
+on_directory_profile_ready (MetaColorProfile *color_profile,
+                            MetaColorStore   *color_store)
+{
+  g_object_ref (color_profile);
+
+  if (!g_hash_table_steal (color_store->pending_local_profiles,
+                           meta_color_profile_get_file_path (color_profile)))
+    g_warn_if_reached ();
+
+  g_hash_table_insert (color_store->profiles,
+                       g_strdup (meta_color_profile_get_id (color_profile)),
+                       color_profile);
+
+  meta_topic (META_DEBUG_COLOR, "Created colord profile '%s' from '%s'",
+              meta_color_profile_get_id (color_profile),
+              meta_color_profile_get_file_path (color_profile));
+
+  g_object_unref (color_profile);
+}
+
+static void
+create_profile_from_contents (MetaColorStore *color_store,
+                              const char     *file_path,
+                              const char     *contents,
+                              size_t          size)
+{
+  g_autoptr (CdIcc) cd_icc = NULL;
+  g_autoptr (GError) error = NULL;
+  g_autoptr (GBytes) bytes = NULL;
+  g_autofree char *file_md5_checksum = NULL;
+  MetaColorProfile *color_profile;
+
+  cd_icc = cd_icc_new ();
+  if (!cd_icc_load_data (cd_icc,
+                         (uint8_t *) contents,
+                         size,
+                         CD_ICC_LOAD_FLAGS_METADATA,
+                         &error))
+    {
+      g_warning ("Failed to parse ICC profile '%s': %s", file_path, error->message);
+      return;
+    }
+
+  bytes = g_bytes_new (contents, size);
+
+  /* Set metadata needed by colord */
+  cd_icc_add_metadata (cd_icc, CD_PROFILE_PROPERTY_FILENAME,
+                       file_path);
+
+  file_md5_checksum = g_compute_checksum_for_bytes (G_CHECKSUM_MD5,
+                                                    bytes);
+  cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_FILE_CHECKSUM,
+                       file_md5_checksum);
+  color_profile =
+    meta_color_profile_new_from_icc (color_store->color_manager,
+                                     g_steal_pointer (&cd_icc),
+                                     g_steal_pointer (&bytes));
+
+  g_signal_connect (color_profile, "ready",
+                    G_CALLBACK (on_directory_profile_ready),
+                    color_store);
+
+  g_hash_table_insert (color_store->pending_local_profiles,
+                       g_strdup (file_path),
+                       color_profile);
+}
+
+static gboolean
+should_ignore_store_file (GFile *file)
+{
+  g_autofree char *file_name = NULL;
+
+  /* Ignore profiles from EDID, as they will always be generated on demand. */
+  file_name = g_file_get_basename (file);
+  return g_str_has_prefix (file_name, "edid-");
+}
+
+static void
+process_icc_directory_file (MetaColorStore *color_store,
+                            GFile          *file)
+{
+  g_autoptr (GError) error = NULL;
+  g_autofree char *contents = NULL;
+  size_t size;
+
+  if (should_ignore_store_file (file))
+    return;
+
+  if (!g_file_load_contents (file, NULL, &contents, &size, NULL, &error))
+    {
+      g_warning ("Failed to read '%s': %s",
+                 g_file_peek_path (file), error->message);
+      return;
+    }
+
+  create_profile_from_contents (color_store, g_file_peek_path (file),
+                                contents, size);
+}
+
+static gboolean
+is_file_info_icc_profile (GFileInfo *info)
+{
+  const char *content_type;
+
+  content_type =
+    g_file_info_get_attribute_string (info,
+                                      G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+  if (g_strcmp0 (content_type, "application/vnd.iccprofile") != 0)
+    return FALSE;
+
+  if (g_file_info_get_attribute_boolean (info,
+                                         G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN))
+    return FALSE;
+
+  if (g_file_info_get_attribute_boolean (info,
+                                         G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+is_file_icc_profile (GFile *file)
+{
+  g_autoptr (GFileInfo) info = NULL;
+  g_autoptr (GError) error = NULL;
+
+  info = g_file_query_info (file,
+                            G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                            G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+                            G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
+                            G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP ","
+                            G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                            G_FILE_QUERY_INFO_NONE,
+                            NULL,
+                            &error);
+  if (!info)
+    {
+      g_warning ("Failed to query file info on '%s': %s",
+                 g_file_get_path (file),
+                 error->message);
+      return FALSE;
+    }
+
+  return is_file_info_icc_profile (info);
+}
+
+static void
+on_store_file_read (GObject      *source_object,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+  GFile *file = G_FILE (source_object);
+  MetaColorStore *color_store = user_data;
+  g_autoptr (GError) error = NULL;
+  g_autofree char *contents = NULL;
+  size_t size;
+
+  if (!g_file_load_contents_finish (file, res,
+                                    &contents,
+                                    &size,
+                                    NULL,
+                                    &error))
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_warning ("Failed to read '%s': %s",
+                     g_file_peek_path (file), error->message);
+        }
+      return;
+    }
+
+  create_profile_from_contents (color_store, g_file_peek_path (file),
+                                contents, size);
+}
+
+static void
+query_file_info_cb (GObject      *source_object,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+  GFile *file = G_FILE (source_object);
+  g_autoptr (GFileInfo) info = NULL;
+  g_autoptr (GError) error = NULL;
+  MetaColorStore *color_store = user_data;
+
+  info = g_file_query_info_finish (file, res, &error);
+  if (!info)
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+      g_warning ("Failed to query file info on '%s': %s",
+                 g_file_get_path (file),
+                 error->message);
+      return;
+    }
+
+  if (!is_file_info_icc_profile (info))
+    return;
+
+  if (should_ignore_store_file (file))
+    return;
+
+  g_file_load_contents_async (file, color_store->cancellable,
+                              on_store_file_read, color_store);
+}
+
+static void
+on_icc_directory_change (GFileMonitor      *monitor,
+                         GFile             *file,
+                         GFile             *other_file,
+                         GFileMonitorEvent  event_type,
+                         MetaColorStore    *color_store)
+{
+  switch (event_type)
+    {
+    case G_FILE_MONITOR_EVENT_CREATED:
+      break;
+    default:
+      return;
+    }
+
+  g_file_query_info_async (file,
+                           G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                           G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+                           G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
+                           G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP ","
+                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                           G_FILE_QUERY_INFO_NONE,
+                           G_PRIORITY_DEFAULT,
+                           color_store->cancellable,
+                           query_file_info_cb,
+                           color_store);
+}
+
+static gboolean
+init_profile_directory (MetaColorStore  *color_store,
+                        GError         **error)
+{
+  g_autofree char *icc_directory_path = NULL;
+  g_autoptr (GFile) icc_directory = NULL;
+  g_autoptr (GError) local_error = NULL;
+  g_autoptr (GFileEnumerator) enumerator = NULL;
+
+  icc_directory_path = g_build_filename (g_get_user_data_dir (),
+                                         "icc", NULL);
+  icc_directory = g_file_new_for_path (icc_directory_path);
+
+  if (!g_file_query_exists (icc_directory, NULL))
+    {
+      if (!g_file_make_directory_with_parents (icc_directory, NULL, error))
+        return FALSE;
+    }
+
+  color_store->icc_directory_monitor =
+    g_file_monitor (icc_directory,
+                    G_FILE_MONITOR_NONE,
+                    NULL, &local_error);
+  if (color_store->icc_directory_monitor)
+    {
+      g_signal_connect (color_store->icc_directory_monitor,
+                        "changed", G_CALLBACK (on_icc_directory_change),
+                        color_store);
+    }
+  else
+    {
+      g_warning ("Failed to monitor ICC profile directory '%s': %s",
+                 icc_directory_path,
+                 local_error->message);
+      g_clear_error (&local_error);
+    }
+
+  enumerator = g_file_enumerate_children (icc_directory,
+                                          G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                          G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+                                          G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
+                                          G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP ","
+                                          G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                          G_FILE_QUERY_INFO_NONE,
+                                          NULL,
+                                          error);
+  if (!enumerator)
+    return FALSE;
+
+  while (TRUE)
+    {
+      GFileInfo *file_info;
+
+      file_info = g_file_enumerator_next_file (enumerator, NULL, error);
+      if (!file_info)
+        {
+          if (*error)
+            return FALSE;
+          else
+            break;
+        }
+
+     switch (g_file_info_get_file_type (file_info))
+       {
+       case G_FILE_TYPE_REGULAR:
+         {
+           g_autoptr (GFile) file = NULL;
+           g_autofree char *file_path = NULL;
+
+           file_path = g_build_filename (icc_directory_path,
+                                         g_file_info_get_name (file_info),
+                                         NULL);
+           file = g_file_new_for_path (file_path);
+           if (is_file_icc_profile (file))
+             process_icc_directory_file (color_store, file);
+
+           break;
+         }
+       case G_FILE_TYPE_SYMBOLIC_LINK:
+         {
+           const char *target_path;
+           GFile *target;
+
+           target_path = g_file_info_get_symlink_target (file_info);
+           target = g_file_new_for_path (target_path);
+
+           if (is_file_icc_profile (target))
+             process_icc_directory_file (color_store, target);
+           break;
+         }
+       default:
+         break;
+       }
+    }
+
+  return TRUE;
 }
 
 MetaColorStore *
 meta_color_store_new (MetaColorManager *color_manager)
 {
   MetaColorStore *color_store;
+  g_autoptr (GError) error = NULL;
 
   color_store = g_object_new (META_TYPE_COLOR_STORE, NULL);
   color_store->color_manager = color_manager;
@@ -87,6 +434,11 @@ meta_color_store_new (MetaColorManager *color_manager)
     g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
   color_store->pending_device_profiles =
     g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  color_store->pending_local_profiles =
+    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+  if (!init_profile_directory (color_store, &error))
+    g_warning ("Failed to monitor ICC directory: %s", error->message);
 
   return color_store;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]